From c85bdff57a1f6d3c47c804dda46e22bd14e93888 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Fri, 27 Dec 2019 11:24:13 +1300 Subject: [PATCH] PaintBrush: Add an "ellipse tool" The tool currently supports drawing an elliptical line of a specified thickness. Further improvements can include adding a fill mode, and holding down shift to draw a perfect circle. Closes #375. --- Applications/PaintBrush/EllipseTool.cpp | 106 ++++++++++++++++++++++ Applications/PaintBrush/EllipseTool.h | 36 ++++++++ Applications/PaintBrush/Makefile | 1 + Applications/PaintBrush/ToolboxWidget.cpp | 2 + Base/res/icons/paintbrush/circle.png | Bin 0 -> 1005 bytes 5 files changed, 145 insertions(+) create mode 100644 Applications/PaintBrush/EllipseTool.cpp create mode 100644 Applications/PaintBrush/EllipseTool.h create mode 100644 Base/res/icons/paintbrush/circle.png diff --git a/Applications/PaintBrush/EllipseTool.cpp b/Applications/PaintBrush/EllipseTool.cpp new file mode 100644 index 0000000000..9e87812f5e --- /dev/null +++ b/Applications/PaintBrush/EllipseTool.cpp @@ -0,0 +1,106 @@ +#include "EllipseTool.h" +#include "PaintableWidget.h" +#include +#include +#include +#include +#include + +EllipseTool::EllipseTool() +{ +} + +EllipseTool::~EllipseTool() +{ +} + +void EllipseTool::draw_using(Painter& painter) +{ + auto ellipse_intersecting_rect = Rect::from_two_points(m_ellipse_start_position, m_ellipse_end_position); + switch (m_mode) { + case Mode::Outline: + painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_widget->color_for(m_drawing_button), m_thickness); + break; + default: + ASSERT_NOT_REACHED(); + } +} + +void EllipseTool::on_mousedown(GMouseEvent& event) +{ + if (event.button() != GMouseButton::Left && event.button() != GMouseButton::Right) + return; + + if (m_drawing_button != GMouseButton::None) + return; + + m_drawing_button = event.button(); + m_ellipse_start_position = event.position(); + m_ellipse_end_position = event.position(); + m_widget->update(); +} + +void EllipseTool::on_mouseup(GMouseEvent& event) +{ + if (event.button() == m_drawing_button) { + GPainter painter(m_widget->bitmap()); + draw_using(painter); + m_drawing_button = GMouseButton::None; + m_widget->update(); + } +} + +void EllipseTool::on_mousemove(GMouseEvent& event) +{ + if (m_drawing_button == GMouseButton::None) + return; + + if (!m_widget->rect().contains(event.position())) + return; + + m_ellipse_end_position = event.position(); + m_widget->update(); +} + +void EllipseTool::on_second_paint(GPaintEvent& event) +{ + if (m_drawing_button == GMouseButton::None) + return; + + GPainter painter(*m_widget); + painter.add_clip_rect(event.rect()); + draw_using(painter); +} + +void EllipseTool::on_keydown(GKeyEvent& event) +{ + if (event.key() == Key_Escape && m_drawing_button != GMouseButton::None) { + m_drawing_button = GMouseButton::None; + m_widget->update(); + event.accept(); + } +} + +void EllipseTool::on_contextmenu(GContextMenuEvent& event) +{ + if (!m_context_menu) { + m_context_menu = GMenu::construct(); + m_context_menu->add_action(GAction::create("Outline", [this](auto&) { + m_mode = Mode::Outline; + })); + m_context_menu->add_separator(); + m_context_menu->add_action(GAction::create("1", [this](auto&) { + m_thickness = 1; + })); + m_context_menu->add_action(GAction::create("2", [this](auto&) { + m_thickness = 2; + })); + m_context_menu->add_action(GAction::create("3", [this](auto&) { + m_thickness = 3; + })); + m_context_menu->add_action(GAction::create("4", [this](auto&) { + m_thickness = 4; + })); + } + m_context_menu->popup(event.screen_position()); +} diff --git a/Applications/PaintBrush/EllipseTool.h b/Applications/PaintBrush/EllipseTool.h new file mode 100644 index 0000000000..bc2f59782b --- /dev/null +++ b/Applications/PaintBrush/EllipseTool.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Tool.h" +#include + +class GMenu; +class Painter; + +class EllipseTool final : public Tool { +public: + EllipseTool(); + virtual ~EllipseTool() override; + + virtual void on_mousedown(GMouseEvent&) override; + virtual void on_mousemove(GMouseEvent&) override; + virtual void on_mouseup(GMouseEvent&) override; + virtual void on_contextmenu(GContextMenuEvent&) override; + virtual void on_second_paint(GPaintEvent&) override; + virtual void on_keydown(GKeyEvent&) override; + +private: + enum class Mode { + Outline, + // FIXME: Add Mode::Fill + }; + + virtual const char* class_name() const override { return "EllipseTool"; } + void draw_using(Painter& painter); + + GMouseButton m_drawing_button { GMouseButton::None }; + Point m_ellipse_start_position; + Point m_ellipse_end_position; + RefPtr m_context_menu; + int m_thickness { 1 }; + Mode m_mode { Mode::Outline }; +}; diff --git a/Applications/PaintBrush/Makefile b/Applications/PaintBrush/Makefile index 0953e04f50..29534b135f 100644 --- a/Applications/PaintBrush/Makefile +++ b/Applications/PaintBrush/Makefile @@ -6,6 +6,7 @@ OBJS = \ PenTool.o \ LineTool.o \ RectangleTool.o \ + EllipseTool.o \ EraseTool.o \ BucketTool.o \ ColorDialog.o \ diff --git a/Applications/PaintBrush/ToolboxWidget.cpp b/Applications/PaintBrush/ToolboxWidget.cpp index a46a302838..9627a88b13 100644 --- a/Applications/PaintBrush/ToolboxWidget.cpp +++ b/Applications/PaintBrush/ToolboxWidget.cpp @@ -1,5 +1,6 @@ #include "ToolboxWidget.h" #include "BucketTool.h" +#include "EllipseTool.h" #include "EraseTool.h" #include "LineTool.h" #include "PaintableWidget.h" @@ -72,6 +73,7 @@ ToolboxWidget::ToolboxWidget(GWidget* parent) add_tool("Erase", "eraser", make()); add_tool("Line", "line", make()); add_tool("Rectangle", "rectangle", make()); + add_tool("Ellipse", "circle", make()); } ToolboxWidget::~ToolboxWidget() diff --git a/Base/res/icons/paintbrush/circle.png b/Base/res/icons/paintbrush/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..aa56e05083d038a693812aba8d15801ccd3d2cd6 GIT binary patch literal 1005 zcmeAS@N?(olHy`uVBq!ia0y~yV2}b~4mJh`hLv7E=NK56ZdZjwlmsP~D-;yvr)B1( zDwI?fq$;FVWTr7NRNQ(yJ#f=)EuOafk*?DlR9cP{8O)fuyLtDYe+jSVJegCr>}~B1 zena7&)E}(BSUeB?d~dH@bJ*22$s=o?_c_6Et(66?haZ>5mUm@dz3zPX{fnD&@5Ff~ zKhV3LZ1=uVbLegj=e@gCov~|f>z!Z7 zVfdc!r>fg+zh6nyZ}Zv*S69?kB^K@eA+peZUcL6&b^|3;k3TKKpz;FQ#~ zKpuJ5+hLj-eX3>xuNPD<*y7MCaZzT($UBZ{>fY)Z%L)pax1ue zSUZD-e%7*-Z7H#p#?!rb@yORu5ZYJKhaG#Oong_Djp zut{@l2nu@Auj>jUS$k{1*Ct^a7a=X%VaHf!6RHI>?H%idku__z9< z*skxFUVEwsz4Tc=@8_1w=Zmg2#z-4ox-hYwS3dR1>|4uz`Siw}_&ZA~H*0c4)ZaIb zylI=|g-rT|=bmxs5pLY!%jA(U@64iLFPoFTX&Vj9T8&mr^s|_&W+>pJzL9U&P1_TH zCq-ZQ5`E`K`jbO0-{U863*S}yKJ&Ktm8#hL8k-o-##$sxr9Mb-ZojNuJ@>w6eGPxT z_6M;l?!B$|obnfLzYzW6zp}}P$Bgx+hszhlPORIlox6&Gf#IyDi(`n#@uyP_`3@`a zxXSZ95t(V>YR-2_I!)8L|6uB>b4$2AI0Bh?Q#)0~4C*B8%wjIeHL!ovKg>RVRcG_p zy`KXXnRl0$WpUqO}J8TWxX5C5NRmI?O{hp@Y3Py#g%}>@?g)yyVezZ<^ z!EJ^B(_`^Z=WcJ-lrM|ooD~(qsaoeX`$VF%)cq}CdeUaMm=n%K6lnftcv+TZ(rs?w R$iTqB;OXk;vd$@?2>=(F%=Q2P literal 0 HcmV?d00001