Serenity Operating System
at master 214 lines 8.4 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org> 4 * Copyright (c) 2022, the SerenityOS developers. 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include "EllipseTool.h" 10#include "../ImageEditor.h" 11#include "../Layer.h" 12#include <LibGUI/Action.h> 13#include <LibGUI/BoxLayout.h> 14#include <LibGUI/CheckBox.h> 15#include <LibGUI/Label.h> 16#include <LibGUI/Menu.h> 17#include <LibGUI/Painter.h> 18#include <LibGUI/RadioButton.h> 19#include <LibGUI/TextBox.h> 20#include <LibGUI/ValueSlider.h> 21#include <LibGfx/AntiAliasingPainter.h> 22#include <LibGfx/Rect.h> 23 24namespace PixelPaint { 25 26void EllipseTool::draw_using(GUI::Painter& painter, Gfx::IntPoint start_position, Gfx::IntPoint end_position, int thickness) 27{ 28 Gfx::IntRect ellipse_intersecting_rect; 29 if (m_draw_mode == DrawMode::FromCenter) { 30 auto delta = end_position - start_position; 31 ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position - delta, end_position); 32 } else { 33 ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position, end_position); 34 } 35 36 Gfx::AntiAliasingPainter aa_painter { painter }; 37 38 switch (m_fill_mode) { 39 case FillMode::Outline: 40 if (m_antialias_enabled) 41 aa_painter.draw_ellipse(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), thickness); 42 else 43 painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), thickness); 44 break; 45 case FillMode::Fill: 46 if (m_antialias_enabled) 47 aa_painter.fill_ellipse(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button)); 48 else 49 painter.fill_ellipse(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button)); 50 break; 51 default: 52 VERIFY_NOT_REACHED(); 53 } 54} 55 56void EllipseTool::on_mousedown(Layer* layer, MouseEvent& event) 57{ 58 if (!layer) 59 return; 60 61 auto& layer_event = event.layer_event(); 62 if (layer_event.button() != GUI::MouseButton::Primary && layer_event.button() != GUI::MouseButton::Secondary) 63 return; 64 65 if (m_drawing_button != GUI::MouseButton::None) 66 return; 67 68 m_drawing_button = layer_event.button(); 69 m_ellipse_start_position = layer_event.position(); 70 m_ellipse_end_position = layer_event.position(); 71 m_editor->update(); 72} 73 74void EllipseTool::on_mouseup(Layer* layer, MouseEvent& event) 75{ 76 if (!layer) 77 return; 78 79 if (event.layer_event().button() == m_drawing_button) { 80 GUI::Painter painter(layer->get_scratch_edited_bitmap()); 81 draw_using(painter, m_ellipse_start_position, m_ellipse_end_position, m_thickness); 82 m_drawing_button = GUI::MouseButton::None; 83 layer->did_modify_bitmap(layer->get_scratch_edited_bitmap().rect()); 84 m_editor->update(); 85 m_editor->did_complete_action(tool_name()); 86 } 87} 88 89void EllipseTool::on_mousemove(Layer*, MouseEvent& event) 90{ 91 if (m_drawing_button == GUI::MouseButton::None) 92 return; 93 94 m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner; 95 96 if (event.layer_event().shift()) 97 m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0); 98 else if (m_aspect_ratio.has_value()) 99 m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value()); 100 else 101 m_ellipse_end_position = event.layer_event().position(); 102 103 m_editor->update(); 104} 105 106void EllipseTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) 107{ 108 if (!layer || m_drawing_button == GUI::MouseButton::None) 109 return; 110 111 GUI::Painter painter(*m_editor); 112 painter.add_clip_rect(event.rect()); 113 painter.translate(editor_layer_location(*layer)); 114 auto preview_start = m_editor->content_to_frame_position(m_ellipse_start_position).to_type<int>(); 115 auto preview_end = m_editor->content_to_frame_position(m_ellipse_end_position).to_type<int>(); 116 draw_using(painter, preview_start, preview_end, AK::max(m_thickness * m_editor->scale(), 1)); 117} 118 119bool EllipseTool::on_keydown(GUI::KeyEvent& event) 120{ 121 if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { 122 m_drawing_button = GUI::MouseButton::None; 123 m_editor->update(); 124 return true; 125 } 126 return Tool::on_keydown(event); 127} 128 129ErrorOr<GUI::Widget*> EllipseTool::get_properties_widget() 130{ 131 if (!m_properties_widget) { 132 auto properties_widget = TRY(GUI::Widget::try_create()); 133 (void)TRY(properties_widget->try_set_layout<GUI::VerticalBoxLayout>()); 134 135 auto thickness_container = TRY(properties_widget->try_add<GUI::Widget>()); 136 thickness_container->set_fixed_height(20); 137 (void)TRY(thickness_container->try_set_layout<GUI::HorizontalBoxLayout>()); 138 139 auto thickness_label = TRY(thickness_container->try_add<GUI::Label>("Thickness:")); 140 thickness_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 141 thickness_label->set_fixed_size(80, 20); 142 143 auto thickness_slider = TRY(thickness_container->try_add<GUI::ValueSlider>(Orientation::Horizontal, "px"_short_string)); 144 thickness_slider->set_range(1, 10); 145 thickness_slider->set_value(m_thickness); 146 147 thickness_slider->on_change = [this](int value) { 148 m_thickness = value; 149 }; 150 set_primary_slider(thickness_slider); 151 152 auto mode_container = TRY(properties_widget->try_add<GUI::Widget>()); 153 mode_container->set_fixed_height(70); 154 (void)TRY(mode_container->try_set_layout<GUI::HorizontalBoxLayout>()); 155 auto mode_label = TRY(mode_container->try_add<GUI::Label>("Mode:")); 156 mode_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 157 158 auto mode_radio_container = TRY(mode_container->try_add<GUI::Widget>()); 159 (void)TRY(mode_radio_container->try_set_layout<GUI::VerticalBoxLayout>()); 160 auto outline_mode_radio = TRY(mode_radio_container->try_add<GUI::RadioButton>("Outline"_short_string)); 161 auto fill_mode_radio = TRY(mode_radio_container->try_add<GUI::RadioButton>("Fill"_short_string)); 162 auto aa_enable_checkbox = TRY(mode_radio_container->try_add<GUI::CheckBox>(TRY("Anti-alias"_string))); 163 164 aa_enable_checkbox->on_checked = [this](bool checked) { 165 m_antialias_enabled = checked; 166 }; 167 outline_mode_radio->on_checked = [this](bool checked) { 168 if (checked) 169 m_fill_mode = FillMode::Outline; 170 }; 171 fill_mode_radio->on_checked = [this](bool checked) { 172 if (checked) 173 m_fill_mode = FillMode::Fill; 174 }; 175 176 aa_enable_checkbox->set_checked(true); 177 outline_mode_radio->set_checked(true); 178 179 auto aspect_container = TRY(properties_widget->try_add<GUI::Widget>()); 180 aspect_container->set_fixed_height(20); 181 (void)TRY(aspect_container->try_set_layout<GUI::HorizontalBoxLayout>()); 182 183 auto aspect_label = TRY(aspect_container->try_add<GUI::Label>("Aspect Ratio:")); 184 aspect_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 185 aspect_label->set_fixed_size(80, 20); 186 187 m_aspect_w_textbox = TRY(aspect_container->try_add<GUI::TextBox>()); 188 m_aspect_w_textbox->set_fixed_height(20); 189 m_aspect_w_textbox->set_fixed_width(25); 190 m_aspect_w_textbox->on_change = [this] { 191 auto x = m_aspect_w_textbox->text().to_int().value_or(0); 192 auto y = m_aspect_h_textbox->text().to_int().value_or(0); 193 if (x > 0 && y > 0) { 194 m_aspect_ratio = (float)x / (float)y; 195 } else { 196 m_aspect_ratio = {}; 197 } 198 }; 199 200 auto multiply_label = TRY(aspect_container->try_add<GUI::Label>("x")); 201 multiply_label->set_text_alignment(Gfx::TextAlignment::Center); 202 multiply_label->set_fixed_size(10, 20); 203 204 m_aspect_h_textbox = TRY(aspect_container->try_add<GUI::TextBox>()); 205 m_aspect_h_textbox->set_fixed_height(20); 206 m_aspect_h_textbox->set_fixed_width(25); 207 m_aspect_h_textbox->on_change = [this] { m_aspect_w_textbox->on_change(); }; 208 m_properties_widget = properties_widget; 209 } 210 211 return m_properties_widget.ptr(); 212} 213 214}