Serenity Operating System
at master 264 lines 11 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, MacDue <macdue@dueutil.tech> 5 * Copyright (c) 2022, the SerenityOS developers. 6 * 7 * SPDX-License-Identifier: BSD-2-Clause 8 */ 9 10#include "RectangleTool.h" 11#include "../ImageEditor.h" 12#include "../Layer.h" 13#include <LibGUI/Action.h> 14#include <LibGUI/BoxLayout.h> 15#include <LibGUI/CheckBox.h> 16#include <LibGUI/Label.h> 17#include <LibGUI/Menu.h> 18#include <LibGUI/Painter.h> 19#include <LibGUI/RadioButton.h> 20#include <LibGUI/TextBox.h> 21#include <LibGUI/ValueSlider.h> 22#include <LibGfx/AntiAliasingPainter.h> 23#include <LibGfx/Rect.h> 24 25namespace PixelPaint { 26 27void RectangleTool::draw_using(GUI::Painter& painter, Gfx::IntPoint start_position, Gfx::IntPoint end_position, int thickness, int corner_radius) 28{ 29 Gfx::IntRect rect; 30 if (m_draw_mode == DrawMode::FromCenter) { 31 auto delta = end_position - start_position; 32 rect = Gfx::IntRect::from_two_points(start_position - delta, end_position); 33 } else { 34 rect = Gfx::IntRect::from_two_points(start_position, end_position); 35 } 36 37 switch (m_fill_mode) { 38 case FillMode::Fill: 39 painter.fill_rect(rect, m_editor->color_for(m_drawing_button)); 40 break; 41 case FillMode::Outline: 42 painter.draw_rect_with_thickness(rect, m_editor->color_for(m_drawing_button), thickness); 43 break; 44 case FillMode::Gradient: 45 painter.fill_rect_with_gradient(rect, m_editor->primary_color(), m_editor->secondary_color()); 46 break; 47 case FillMode::RoundedCorners: { 48 int min_dimension = corner_radius * 2; 49 if (rect.width() < min_dimension) 50 rect.set_width(min_dimension); 51 if (rect.height() < min_dimension) 52 rect.set_height(min_dimension); 53 if (m_antialias_enabled) { 54 Gfx::AntiAliasingPainter aa_painter { painter }; 55 aa_painter.fill_rect_with_rounded_corners(rect, m_editor->color_for(m_drawing_button), corner_radius); 56 } else { 57 painter.fill_rect_with_rounded_corners(rect, m_editor->color_for(m_drawing_button), corner_radius); 58 } 59 break; 60 } 61 default: 62 VERIFY_NOT_REACHED(); 63 } 64} 65 66void RectangleTool::on_mousedown(Layer* layer, MouseEvent& event) 67{ 68 if (!layer) 69 return; 70 71 auto& layer_event = event.layer_event(); 72 if (layer_event.button() != GUI::MouseButton::Primary && layer_event.button() != GUI::MouseButton::Secondary) 73 return; 74 75 if (m_drawing_button != GUI::MouseButton::None) 76 return; 77 78 m_drawing_button = layer_event.button(); 79 m_rectangle_start_position = layer_event.position(); 80 m_rectangle_end_position = layer_event.position(); 81 m_editor->update(); 82} 83 84void RectangleTool::on_mouseup(Layer* layer, MouseEvent& event) 85{ 86 if (!layer) 87 return; 88 89 if (event.layer_event().button() == m_drawing_button) { 90 GUI::Painter painter(layer->get_scratch_edited_bitmap()); 91 draw_using(painter, m_rectangle_start_position, m_rectangle_end_position, m_thickness, m_corner_radius); 92 m_drawing_button = GUI::MouseButton::None; 93 auto modified_rect = Gfx::IntRect::from_two_points(m_rectangle_start_position, m_rectangle_end_position).inflated(m_thickness * 2, m_thickness * 2); 94 layer->did_modify_bitmap(modified_rect); 95 m_editor->update(); 96 m_editor->did_complete_action(tool_name()); 97 } 98} 99 100void RectangleTool::on_mousemove(Layer* layer, MouseEvent& event) 101{ 102 if (!layer) 103 return; 104 105 if (m_drawing_button == GUI::MouseButton::None) 106 return; 107 108 m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner; 109 110 if (event.layer_event().shift()) 111 m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0); 112 else if (m_aspect_ratio.has_value()) 113 m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value()); 114 else 115 m_rectangle_end_position = event.layer_event().position(); 116 117 m_editor->update(); 118} 119 120void RectangleTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) 121{ 122 if (!layer || m_drawing_button == GUI::MouseButton::None) 123 return; 124 125 GUI::Painter painter(*m_editor); 126 painter.add_clip_rect(event.rect()); 127 painter.translate(editor_layer_location(*layer)); 128 auto start_position = editor_stroke_position(m_rectangle_start_position, m_thickness); 129 auto end_position = editor_stroke_position(m_rectangle_end_position, m_thickness); 130 draw_using(painter, start_position, end_position, AK::max(m_thickness * m_editor->scale(), 1), m_corner_radius * m_editor->scale()); 131} 132 133bool RectangleTool::on_keydown(GUI::KeyEvent& event) 134{ 135 if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { 136 m_drawing_button = GUI::MouseButton::None; 137 m_editor->update(); 138 return true; 139 } 140 return Tool::on_keydown(event); 141} 142 143ErrorOr<GUI::Widget*> RectangleTool::get_properties_widget() 144{ 145 if (!m_properties_widget) { 146 auto properties_widget = TRY(GUI::Widget::try_create()); 147 (void)TRY(properties_widget->try_set_layout<GUI::VerticalBoxLayout>()); 148 149 auto thickness_or_radius_container = TRY(properties_widget->try_add<GUI::Widget>()); 150 thickness_or_radius_container->set_fixed_height(20); 151 (void)TRY(thickness_or_radius_container->try_set_layout<GUI::HorizontalBoxLayout>()); 152 153 auto thickness_or_radius_label = TRY(thickness_or_radius_container->try_add<GUI::Label>()); 154 thickness_or_radius_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 155 thickness_or_radius_label->set_fixed_size(80, 20); 156 157 auto thickness_or_radius_slider = TRY(thickness_or_radius_container->try_add<GUI::ValueSlider>(Orientation::Horizontal, "px"_short_string)); 158 159 thickness_or_radius_slider->on_change = [&](int value) { 160 if (m_fill_mode == FillMode::RoundedCorners) { 161 m_corner_radius = value; 162 } else 163 m_thickness = value; 164 }; 165 166 auto update_slider = [this, thickness_or_radius_label, thickness_or_radius_slider] { 167 auto update_values = [&](auto label, int value, int range_min, int range_max = 10) { 168 thickness_or_radius_label->set_text(label); 169 thickness_or_radius_slider->set_range(range_min, range_max); 170 thickness_or_radius_slider->set_value(value); 171 }; 172 if (m_fill_mode == FillMode::RoundedCorners) 173 update_values("Radius:", m_corner_radius, 0, 50); 174 else 175 update_values("Thickness:", m_thickness, 1); 176 }; 177 178 update_slider(); 179 set_primary_slider(thickness_or_radius_slider); 180 181 auto mode_container = TRY(properties_widget->try_add<GUI::Widget>()); 182 mode_container->set_fixed_height(90); 183 (void)TRY(mode_container->try_set_layout<GUI::HorizontalBoxLayout>()); 184 auto mode_label = TRY(mode_container->try_add<GUI::Label>("Mode:")); 185 mode_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 186 mode_label->set_fixed_size(30, 20); 187 188 auto mode_radio_container = TRY(mode_container->try_add<GUI::Widget>()); 189 (void)TRY(mode_radio_container->try_set_layout<GUI::VerticalBoxLayout>()); 190 auto outline_mode_radio = TRY(mode_radio_container->try_add<GUI::RadioButton>("Outline"_short_string)); 191 auto fill_mode_radio = TRY(mode_radio_container->try_add<GUI::RadioButton>("Fill"_short_string)); 192 auto gradient_mode_radio = TRY(mode_radio_container->try_add<GUI::RadioButton>(TRY("Gradient"_string))); 193 mode_radio_container->set_fixed_width(70); 194 195 auto rounded_corners_mode_radio = TRY(mode_radio_container->try_add<GUI::RadioButton>("Rounded"_short_string)); 196 197 outline_mode_radio->on_checked = [this, update_slider](bool) { 198 m_fill_mode = FillMode::Outline; 199 update_slider(); 200 }; 201 fill_mode_radio->on_checked = [this, update_slider](bool) { 202 m_fill_mode = FillMode::Fill; 203 update_slider(); 204 }; 205 gradient_mode_radio->on_checked = [this, update_slider](bool) { 206 m_fill_mode = FillMode::Gradient; 207 update_slider(); 208 }; 209 rounded_corners_mode_radio->on_checked = [this, update_slider](bool) { 210 m_fill_mode = FillMode::RoundedCorners; 211 update_slider(); 212 }; 213 outline_mode_radio->set_checked(true); 214 215 auto mode_extras_container = TRY(mode_container->try_add<GUI::Widget>()); 216 (void)TRY(mode_extras_container->try_set_layout<GUI::VerticalBoxLayout>()); 217 218 auto aa_enable_checkbox = TRY(mode_extras_container->try_add<GUI::CheckBox>(TRY("Anti-alias"_string))); 219 aa_enable_checkbox->on_checked = [this](bool checked) { 220 m_antialias_enabled = checked; 221 }; 222 aa_enable_checkbox->set_checked(true); 223 224 auto aspect_container = TRY(mode_extras_container->try_add<GUI::Widget>()); 225 (void)TRY(aspect_container->try_set_layout<GUI::VerticalBoxLayout>()); 226 aspect_container->set_fixed_width(75); 227 228 auto aspect_label = TRY(aspect_container->try_add<GUI::Label>("Aspect Ratio:")); 229 aspect_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 230 aspect_label->set_fixed_size(75, 20); 231 232 auto aspect_fields_container = TRY(aspect_container->try_add<GUI::Widget>()); 233 aspect_fields_container->set_fixed_width(75); 234 (void)TRY(aspect_fields_container->try_set_layout<GUI::HorizontalBoxLayout>()); 235 236 m_aspect_w_textbox = TRY(aspect_fields_container->try_add<GUI::TextBox>()); 237 m_aspect_w_textbox->set_fixed_height(20); 238 m_aspect_w_textbox->set_fixed_width(25); 239 m_aspect_w_textbox->on_change = [this] { 240 auto x = m_aspect_w_textbox->text().to_int().value_or(0); 241 auto y = m_aspect_h_textbox->text().to_int().value_or(0); 242 if (x > 0 && y > 0) { 243 m_aspect_ratio = (float)x / (float)y; 244 } else { 245 m_aspect_ratio = {}; 246 } 247 }; 248 249 auto multiply_label = TRY(aspect_fields_container->try_add<GUI::Label>("x")); 250 multiply_label->set_text_alignment(Gfx::TextAlignment::Center); 251 multiply_label->set_fixed_size(10, 20); 252 253 m_aspect_h_textbox = TRY(aspect_fields_container->try_add<GUI::TextBox>()); 254 m_aspect_h_textbox->set_fixed_height(20); 255 m_aspect_h_textbox->set_fixed_width(25); 256 m_aspect_h_textbox->on_change = [this] { m_aspect_w_textbox->on_change(); }; 257 258 m_properties_widget = properties_widget; 259 } 260 261 return m_properties_widget.ptr(); 262} 263 264}