From e56764bd746a7cae58a585e33f4aed1bc9126aa8 Mon Sep 17 00:00:00 2001 From: David Isaksson Date: Sun, 5 Sep 2021 12:22:27 +0200 Subject: [PATCH] PixelPaint: Add rulers --- .../Applications/PixelPaint/ImageEditor.cpp | 79 +++++++++++++++++++ .../Applications/PixelPaint/ImageEditor.h | 9 +++ .../Applications/PixelPaint/MainWidget.cpp | 14 ++++ Userland/Applications/PixelPaint/MainWidget.h | 1 + 4 files changed, 103 insertions(+) diff --git a/Userland/Applications/PixelPaint/ImageEditor.cpp b/Userland/Applications/PixelPaint/ImageEditor.cpp index 3729a8b730a..81013674866 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.cpp +++ b/Userland/Applications/PixelPaint/ImageEditor.cpp @@ -117,6 +117,72 @@ void ImageEditor::paint_event(GUI::PaintEvent& event) if (!m_selection.is_empty()) m_selection.paint(painter); + + if (m_show_rulers) { + const auto ruler_bg_color = palette().color(Gfx::ColorRole::InactiveSelection); + const auto ruler_fg_color = palette().color(Gfx::ColorRole::Ruler); + const auto ruler_text_color = palette().color(Gfx::ColorRole::InactiveSelectionText); + + // Ruler background + painter.fill_rect({ { 0, 0 }, { m_ruler_thickness, rect().height() } }, ruler_bg_color); + painter.fill_rect({ { 0, 0 }, { rect().width(), m_ruler_thickness } }, ruler_bg_color); + + const auto ruler_step = calculate_ruler_step_size(); + const auto editor_origin_to_image = editor_position_to_image_position({ 0, 0 }); + const auto editor_max_to_image = editor_position_to_image_position({ width(), height() }); + + // Horizontal ruler + painter.draw_line({ 0, m_ruler_thickness }, { rect().width(), m_ruler_thickness }, ruler_fg_color); + const auto x_start = floor(editor_origin_to_image.x()) - ((int)floor(editor_origin_to_image.x()) % ruler_step) - ruler_step; + for (int x = x_start; x < editor_max_to_image.x(); x += ruler_step) { + const int num_sub_divisions = min(ruler_step, 10); + for (int x_sub = 0; x_sub < num_sub_divisions; ++x_sub) { + const int x_pos = x + (int)(ruler_step * x_sub / num_sub_divisions); + const int editor_x_sub = image_position_to_editor_position({ x_pos, 0 }).x(); + const int line_length = (x_sub % 2 == 0) ? m_ruler_thickness / 3 : m_ruler_thickness / 6; + painter.draw_line({ editor_x_sub, m_ruler_thickness - line_length }, { editor_x_sub, m_ruler_thickness }, ruler_fg_color); + } + + const int editor_x = image_position_to_editor_position({ x, 0 }).x(); + painter.draw_line({ editor_x, 0 }, { editor_x, m_ruler_thickness }, ruler_fg_color); + painter.draw_text({ { editor_x + 2, 0 }, { m_ruler_thickness, m_ruler_thickness - 2 } }, String::formatted("{}", x), painter.font(), Gfx::TextAlignment::CenterLeft, ruler_text_color); + } + + // Vertical ruler + painter.draw_line({ m_ruler_thickness, 0 }, { m_ruler_thickness, rect().height() }, ruler_fg_color); + const auto y_start = floor(editor_origin_to_image.y()) - ((int)floor(editor_origin_to_image.y()) % ruler_step) - ruler_step; + for (int y = y_start; y < editor_max_to_image.y(); y += ruler_step) { + const int num_sub_divisions = min(ruler_step, 10); + for (int y_sub = 0; y_sub < num_sub_divisions; ++y_sub) { + const int y_pos = y + (int)(ruler_step * y_sub / num_sub_divisions); + const int editor_y_sub = image_position_to_editor_position({ 0, y_pos }).y(); + const int line_length = (y_sub % 2 == 0) ? m_ruler_thickness / 3 : m_ruler_thickness / 6; + painter.draw_line({ m_ruler_thickness - line_length, editor_y_sub }, { m_ruler_thickness, editor_y_sub }, ruler_fg_color); + } + + const int editor_y = image_position_to_editor_position({ 0, y }).y(); + painter.draw_line({ 0, editor_y }, { m_ruler_thickness, editor_y }, ruler_fg_color); + painter.draw_text({ { 0, editor_y - m_ruler_thickness }, { m_ruler_thickness, m_ruler_thickness } }, String::formatted("{}", y), painter.font(), Gfx::TextAlignment::BottomRight, ruler_text_color); + } + + // Top left square + painter.fill_rect({ { 0, 0 }, { m_ruler_thickness, m_ruler_thickness } }, ruler_bg_color); + } +} + +int ImageEditor::calculate_ruler_step_size() const +{ + const auto step_target = 80 / m_scale; + const auto max_factor = 5; + for (int factor = 0; factor < max_factor; ++factor) { + if (step_target <= 1 * (float)pow(10, factor)) + return 1 * pow(10, factor); + if (step_target <= 2 * (float)pow(10, factor)) + return 2 * pow(10, factor); + if (step_target <= 5 * (float)pow(10, factor)) + return 5 * pow(10, factor); + } + return 1 * pow(10, max_factor); } Gfx::FloatRect ImageEditor::layer_rect_to_editor_rect(Layer const& layer, Gfx::IntRect const& layer_rect) const @@ -355,6 +421,19 @@ void ImageEditor::set_guide_visibility(bool show_guides) update(); } +void ImageEditor::set_ruler_visibility(bool show_rulers) +{ + if (m_show_rulers == show_rulers) + return; + + m_show_rulers = show_rulers; + + if (on_set_ruler_visibility) + on_set_ruler_visibility(m_show_rulers); + + update(); +} + void ImageEditor::set_pixel_grid_visibility(bool show_pixel_grid) { if (m_show_pixel_grid == show_pixel_grid) diff --git a/Userland/Applications/PixelPaint/ImageEditor.h b/Userland/Applications/PixelPaint/ImageEditor.h index 695152dffb8..5fdf3c39ec1 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.h +++ b/Userland/Applications/PixelPaint/ImageEditor.h @@ -95,6 +95,10 @@ public: void set_guide_visibility(bool show_guides); Function on_set_guide_visibility; + bool ruler_visibility() { return m_show_rulers; } + void set_ruler_visibility(bool); + Function on_set_ruler_visibility; + bool pixel_grid_visibility() const { return m_show_pixel_grid; } void set_pixel_grid_visibility(bool show_pixel_grid); @@ -125,12 +129,15 @@ private: void clamped_scale(float); void relayout(); + int calculate_ruler_step_size() const; + NonnullRefPtr m_image; RefPtr m_active_layer; OwnPtr m_undo_stack; NonnullRefPtrVector m_guides; bool m_show_guides { true }; + bool m_show_rulers { true }; bool m_show_pixel_grid { true }; Tool* m_active_tool { nullptr }; @@ -144,6 +151,8 @@ private: Gfx::FloatPoint m_saved_pan_origin; Gfx::IntPoint m_click_position; + int m_ruler_thickness { 20 }; + Gfx::StandardCursor m_active_cursor { Gfx::StandardCursor::None }; Selection m_selection; diff --git a/Userland/Applications/PixelPaint/MainWidget.cpp b/Userland/Applications/PixelPaint/MainWidget.cpp index 4ea0ceec390..b24c121f63c 100644 --- a/Userland/Applications/PixelPaint/MainWidget.cpp +++ b/Userland/Applications/PixelPaint/MainWidget.cpp @@ -80,6 +80,7 @@ MainWidget::MainWidget() } }); m_show_guides_action->set_checked(image_editor.guide_visibility()); + m_show_rulers_action->set_checked(image_editor.ruler_visibility()); }; } @@ -367,6 +368,15 @@ void MainWidget::initialize_menubar(GUI::Window& window) show_pixel_grid_action->set_checked(true); view_menu.add_action(*show_pixel_grid_action); + m_show_rulers_action = GUI::Action::create_checkable( + "Show Rulers", { Mod_Ctrl, Key_R }, [&](auto& action) { + if (auto* editor = current_image_editor()) { + editor->set_ruler_visibility(action.is_checked()); + } + }); + m_show_rulers_action->set_checked(true); + view_menu.add_action(*m_show_rulers_action); + auto& tool_menu = window.add_menu("&Tool"); m_toolbox->for_each_tool([&](auto& tool) { if (tool.action()) @@ -776,6 +786,10 @@ ImageEditor& MainWidget::create_new_editor(NonnullRefPtr image) m_show_guides_action->set_checked(show_guides); }; + image_editor.on_set_ruler_visibility = [&](bool show_rulers) { + m_show_rulers_action->set_checked(show_rulers); + }; + // NOTE: We invoke the above hook directly here to make sure the tab title is set up. image_editor.on_image_title_change(image->title()); diff --git a/Userland/Applications/PixelPaint/MainWidget.h b/Userland/Applications/PixelPaint/MainWidget.h index 970a157d237..018a2a45358 100644 --- a/Userland/Applications/PixelPaint/MainWidget.h +++ b/Userland/Applications/PixelPaint/MainWidget.h @@ -70,6 +70,7 @@ private: RefPtr m_reset_zoom_action; RefPtr m_add_guide_action; RefPtr m_show_guides_action; + RefPtr m_show_rulers_action; }; }