From 4f9b02e71b7259aebbf5a59218220a415ad1267d Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 01:56:40 +0200 Subject: [PATCH 1/9] add an option to fill shapes --- icons.toml | 2 ++ src/sketch_board.rs | 6 ++++++ src/style.rs | 3 ++- src/tools/rectangle.rs | 6 +++++- src/ui/toolbars.rs | 16 ++++++++++++++++ 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/icons.toml b/icons.toml index b705806..f35f5e2 100644 --- a/icons.toml +++ b/icons.toml @@ -17,4 +17,6 @@ icons = [ "crop-filled", "arrow-up-right-filled", "rectangle-landscape-regular", + "paint-bucket-filled", + "paint-bucket-regular", ] diff --git a/src/sketch_board.rs b/src/sketch_board.rs index 437d224..f4330f4 100644 --- a/src/sketch_board.rs +++ b/src/sketch_board.rs @@ -348,6 +348,12 @@ impl SketchBoard { } ToolbarEvent::Undo => self.handle_undo(), ToolbarEvent::Redo => self.handle_redo(), + ToolbarEvent::ToggleFill => { + self.style.fill = !self.style.fill; + self.active_tool + .borrow_mut() + .handle_event(ToolEvent::StyleChanged(self.style)) + }, } } } diff --git a/src/style.rs b/src/style.rs index 1dcc045..017b4b4 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use femtovg::Paint; +use femtovg::{Paint}; use gdk_pixbuf::{ glib::{Variant, VariantTy}, prelude::{StaticVariantType, ToVariant}, @@ -15,6 +15,7 @@ use crate::configuration::APP_CONFIG; pub struct Style { pub color: Color, pub size: Size, + pub fill: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/tools/rectangle.rs b/src/tools/rectangle.rs index 3928893..ed6fc93 100644 --- a/src/tools/rectangle.rs +++ b/src/tools/rectangle.rs @@ -32,7 +32,11 @@ impl Drawable for Rectangle { let mut path = Path::new(); path.rect(self.top_left.x, self.top_left.y, size.x, size.y); - canvas.stroke_path(&path, &self.style.into()); + if self.style.fill { + canvas.fill_path(&path, &self.style.into()); + } else { + canvas.stroke_path(&path, &self.style.into()); + } canvas.restore(); Ok(()) diff --git a/src/ui/toolbars.rs b/src/ui/toolbars.rs index 2d22e72..722c19a 100644 --- a/src/ui/toolbars.rs +++ b/src/ui/toolbars.rs @@ -37,6 +37,7 @@ pub enum ToolbarEvent { Undo, SaveFile, CopyClipboard, + ToggleFill, } #[derive(Debug, Copy, Clone)] @@ -398,7 +399,22 @@ impl Component for StyleToolbar { set_tooltip: "Large size", ActionablePlus::set_action::: Size::Large, }, + gtk::Button { + set_focusable: false, + set_hexpand: false, + set_icon_name: "paint-bucket-regular", + set_tooltip: "Fill shape", + connect_clicked[sender] => move |button| { + sender.output_sender().emit(ToolbarEvent::ToggleFill); + let new_icon = if button.icon_name() == Some("paint-bucket-regular".into()) { + "paint-bucket-filled" + } else { + "paint-bucket-regular" + }; + button.set_icon_name(new_icon); + }, + }, }, } From 2c8a8540538089efae03dbdfc1ef512489eb73ea Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 01:59:07 +0200 Subject: [PATCH 2/9] satisfy formatter once more --- src/sketch_board.rs | 2 +- src/style.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sketch_board.rs b/src/sketch_board.rs index f4330f4..224695d 100644 --- a/src/sketch_board.rs +++ b/src/sketch_board.rs @@ -353,7 +353,7 @@ impl SketchBoard { self.active_tool .borrow_mut() .handle_event(ToolEvent::StyleChanged(self.style)) - }, + } } } } diff --git a/src/style.rs b/src/style.rs index 017b4b4..a0203b5 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use femtovg::{Paint}; +use femtovg::Paint; use gdk_pixbuf::{ glib::{Variant, VariantTy}, prelude::{StaticVariantType, ToVariant}, From b9a3c5e4c99c8917936fd353b8c741f00dc1c101 Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sat, 13 Jul 2024 23:07:54 +0200 Subject: [PATCH 3/9] add short cli option for --output-filename --- README.md | 2 +- src/command_line.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dffd121..babe48b 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Options: Path to input image or '-' to read from stdin --fullscreen Start Satty in fullscreen mode - --output-filename + -o, --output-filename Filename to use for saving action. Omit to disable saving to file. Might contain format specifiers: --early-exit Exit directly after copy/save action diff --git a/src/command_line.rs b/src/command_line.rs index 3c0e9d6..552a1a7 100644 --- a/src/command_line.rs +++ b/src/command_line.rs @@ -17,7 +17,7 @@ pub struct CommandLine { /// Filename to use for saving action. Omit to disable saving to file. Might contain format /// specifiers: . - #[arg(long)] + #[arg(short, long)] pub output_filename: Option, /// Exit directly after copy/save action From 49b7ff036ff036035d537ff76968198a534eac38 Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 00:49:17 +0200 Subject: [PATCH 4/9] add ellipse tool --- icons.toml | 1 + src/command_line.rs | 2 + src/tools/ellipse.rs | 115 +++++++++++++++++++++++++++++++++++++++++++ src/tools/mod.rs | 29 +++++++---- src/ui/toolbars.rs | 8 +++ 5 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 src/tools/ellipse.rs diff --git a/icons.toml b/icons.toml index f35f5e2..31cd238 100644 --- a/icons.toml +++ b/icons.toml @@ -14,6 +14,7 @@ icons = [ "text-font-regular", "minus-large", "checkbox-unchecked-regular", + "circle-regular", "crop-filled", "arrow-up-right-filled", "rectangle-landscape-regular", diff --git a/src/command_line.rs b/src/command_line.rs index 552a1a7..fe5c868 100644 --- a/src/command_line.rs +++ b/src/command_line.rs @@ -65,6 +65,7 @@ pub enum Tools { Line, Arrow, Rectangle, + Ellipse, Text, Marker, Blur, @@ -88,6 +89,7 @@ impl std::fmt::Display for Tools { Line => "line", Arrow => "arrow", Rectangle => "rectangle", + Ellipse => "ellipse", Text => "text", Marker => "marker", Blur => "blur", diff --git a/src/tools/ellipse.rs b/src/tools/ellipse.rs new file mode 100644 index 0000000..068f506 --- /dev/null +++ b/src/tools/ellipse.rs @@ -0,0 +1,115 @@ +use anyhow::Result; +use femtovg::{FontId, Path}; +use relm4::gtk::gdk::Key; + +use crate::{ + math::Vec2D, + sketch_board::{MouseEventMsg, MouseEventType}, + style::Style, +}; + +use super::{Drawable, DrawableClone, Tool, ToolUpdateResult}; + +#[derive(Clone, Copy, Debug)] +pub struct Ellipse { + middle: Vec2D, + radii: Option, + style: Style, +} + +impl Drawable for Ellipse { + fn draw( + &self, + canvas: &mut femtovg::Canvas, + _font: FontId, + ) -> Result<()> { + let radii = match self.radii { + Some(s) => s, + None => return Ok(()), // early exit if none + }; + + canvas.save(); + let mut path = Path::new(); + path.ellipse(self.middle.x, self.middle.y, radii.x, radii.y); + + canvas.stroke_path(&path, &self.style.into()); + canvas.restore(); + + Ok(()) + } +} + +#[derive(Default)] +pub struct EllipseTool { + ellipse: Option, + style: Style, +} + +impl Tool for EllipseTool { + fn handle_mouse_event(&mut self, event: MouseEventMsg) -> ToolUpdateResult { + match event.type_ { + MouseEventType::BeginDrag => { + // start new + self.ellipse = Some(Ellipse { + middle: event.pos, + radii: None, + style: self.style, + }); + + ToolUpdateResult::Redraw + } + MouseEventType::EndDrag => { + if let Some(ellipse) = &mut self.ellipse { + if event.pos == Vec2D::zero() { + self.ellipse = None; + + ToolUpdateResult::Redraw + } else { + ellipse.radii = Some(event.pos); + let result = ellipse.clone_box(); + self.ellipse = None; + + ToolUpdateResult::Commit(result) + } + } else { + ToolUpdateResult::Unmodified + } + } + MouseEventType::UpdateDrag => { + if let Some(ellipse) = &mut self.ellipse { + if event.pos == Vec2D::zero() { + return ToolUpdateResult::Unmodified; + } + ellipse.radii = Some(event.pos); + + ToolUpdateResult::Redraw + } else { + ToolUpdateResult::Unmodified + } + } + _ => ToolUpdateResult::Unmodified, + } + } + + fn handle_key_event(&mut self, event: crate::sketch_board::KeyEventMsg) -> ToolUpdateResult { + if event.key == Key::Escape && self.ellipse.is_some() { + self.ellipse = None; + ToolUpdateResult::Redraw + } else { + ToolUpdateResult::Unmodified + } + } + + fn handle_style_event(&mut self, style: Style) -> ToolUpdateResult { + self.style = style; + ToolUpdateResult::Unmodified + } + + fn get_drawable(&self) -> Option<&dyn Drawable> { + match &self.ellipse { + Some(d) => Some(d), + None => None, + } + } +} + diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 91e26e1..da85b3f 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -20,6 +20,7 @@ mod arrow; mod blur; mod brush; mod crop; +mod ellipse; mod highlight; mod line; mod marker; @@ -132,6 +133,7 @@ pub enum ToolUpdateResult { pub use arrow::ArrowTool; pub use blur::BlurTool; pub use crop::CropTool; +pub use ellipse::EllipseTool; pub use highlight::{HighlightTool, Highlighters}; pub use line::LineTool; pub use rectangle::RectangleTool; @@ -147,11 +149,12 @@ pub enum Tools { Line = 2, Arrow = 3, Rectangle = 4, - Text = 5, - Marker = 6, - Blur = 7, - Highlight = 8, - Brush = 9, + Ellipse = 5, + Text = 6, + Marker = 7, + Blur = 8, + Highlight = 9, + Brush = 10, } pub struct ToolsManager { @@ -173,6 +176,10 @@ impl ToolsManager { Tools::Rectangle, Rc::new(RefCell::new(RectangleTool::default())), ); + tools.insert( + Tools::Ellipse, + Rc::new(RefCell::new(EllipseTool::default())), + ); tools.insert(Tools::Text, Rc::new(RefCell::new(TextTool::default()))); tools.insert(Tools::Blur, Rc::new(RefCell::new(BlurTool::default()))); tools.insert( @@ -224,11 +231,12 @@ impl FromVariant for Tools { 2 => Some(Tools::Line), 3 => Some(Tools::Arrow), 4 => Some(Tools::Rectangle), - 5 => Some(Tools::Text), - 6 => Some(Tools::Marker), - 7 => Some(Tools::Blur), - 8 => Some(Tools::Highlight), - 9 => Some(Tools::Brush), + 5 => Some(Tools::Ellipse), + 6 => Some(Tools::Text), + 7 => Some(Tools::Marker), + 8 => Some(Tools::Blur), + 9 => Some(Tools::Highlight), + 10 => Some(Tools::Brush), _ => None, }) } @@ -242,6 +250,7 @@ impl From for Tools { command_line::Tools::Line => Self::Line, command_line::Tools::Arrow => Self::Arrow, command_line::Tools::Rectangle => Self::Rectangle, + command_line::Tools::Ellipse => Self::Ellipse, command_line::Tools::Text => Self::Text, command_line::Tools::Marker => Self::Marker, command_line::Tools::Blur => Self::Blur, diff --git a/src/ui/toolbars.rs b/src/ui/toolbars.rs index 722c19a..fbe26d3 100644 --- a/src/ui/toolbars.rs +++ b/src/ui/toolbars.rs @@ -150,6 +150,14 @@ impl SimpleComponent for ToolsToolbar { set_focusable: false, set_hexpand: false, + set_icon_name: "circle-regular", + set_tooltip: "Ellipse tool", + ActionablePlus::set_action::: Tools::Ellipse, + }, + gtk::ToggleButton { + set_focusable: false, + set_hexpand: false, + set_icon_name: "text-case-title-regular", set_tooltip: "Text tool", ActionablePlus::set_action::: Tools::Text, From 86692fc945e5ebc5e8c6b4ecf9aeec3337acb797 Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 00:54:28 +0200 Subject: [PATCH 5/9] remove a single empty line to satisfy linter --- src/tools/ellipse.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/ellipse.rs b/src/tools/ellipse.rs index 068f506..ba1c9ee 100644 --- a/src/tools/ellipse.rs +++ b/src/tools/ellipse.rs @@ -112,4 +112,3 @@ impl Tool for EllipseTool { } } } - From e6796385d07bfaceed6d2c3d7ed5874a4eef3346 Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 10:16:30 +0200 Subject: [PATCH 6/9] turn ellipse into a circle when shift is pressed --- src/tools/ellipse.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tools/ellipse.rs b/src/tools/ellipse.rs index ba1c9ee..c7d0e14 100644 --- a/src/tools/ellipse.rs +++ b/src/tools/ellipse.rs @@ -1,6 +1,6 @@ use anyhow::Result; use femtovg::{FontId, Path}; -use relm4::gtk::gdk::Key; +use relm4::gtk::gdk::{Key, ModifierType}; use crate::{ math::Vec2D, @@ -65,7 +65,11 @@ impl Tool for EllipseTool { ToolUpdateResult::Redraw } else { - ellipse.radii = Some(event.pos); + if event.modifier.contains(ModifierType::SHIFT_MASK) { + ellipse.radii = Some(Vec2D::new(event.pos.x, event.pos.x)); + } else { + ellipse.radii = Some(event.pos); + } let result = ellipse.clone_box(); self.ellipse = None; @@ -80,7 +84,11 @@ impl Tool for EllipseTool { if event.pos == Vec2D::zero() { return ToolUpdateResult::Unmodified; } - ellipse.radii = Some(event.pos); + if event.modifier.contains(ModifierType::SHIFT_MASK) { + ellipse.radii = Some(Vec2D::new(event.pos.x, event.pos.x)); + } else { + ellipse.radii = Some(event.pos); + } ToolUpdateResult::Redraw } else { From b7db2bd960ece7d0d26743c5e2aa6c2c0fba9cca Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 10:35:39 +0200 Subject: [PATCH 7/9] calculate the circle size better --- src/tools/ellipse.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/tools/ellipse.rs b/src/tools/ellipse.rs index c7d0e14..dfac064 100644 --- a/src/tools/ellipse.rs +++ b/src/tools/ellipse.rs @@ -47,6 +47,7 @@ pub struct EllipseTool { impl Tool for EllipseTool { fn handle_mouse_event(&mut self, event: MouseEventMsg) -> ToolUpdateResult { + let shift_pressed = event.modifier.intersects(ModifierType::SHIFT_MASK); match event.type_ { MouseEventType::BeginDrag => { // start new @@ -65,8 +66,12 @@ impl Tool for EllipseTool { ToolUpdateResult::Redraw } else { - if event.modifier.contains(ModifierType::SHIFT_MASK) { - ellipse.radii = Some(Vec2D::new(event.pos.x, event.pos.x)); + if shift_pressed { + let max_size = event.pos.x.abs().max(event.pos.y.abs()); + ellipse.radii = Some(Vec2D { + x: max_size * event.pos.x.signum(), + y: max_size * event.pos.y.signum(), + }); } else { ellipse.radii = Some(event.pos); } @@ -84,8 +89,12 @@ impl Tool for EllipseTool { if event.pos == Vec2D::zero() { return ToolUpdateResult::Unmodified; } - if event.modifier.contains(ModifierType::SHIFT_MASK) { - ellipse.radii = Some(Vec2D::new(event.pos.x, event.pos.x)); + if shift_pressed { + let max_size = event.pos.x.abs().max(event.pos.y.abs()); + ellipse.radii = Some(Vec2D { + x: max_size * event.pos.x.signum(), + y: max_size * event.pos.y.signum(), + }); } else { ellipse.radii = Some(event.pos); } From 3eeae0c40d609d745778d4a23602869c01eb280d Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 10:32:55 +0200 Subject: [PATCH 8/9] fix Fith typo --- src/ui/toolbars.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/toolbars.rs b/src/ui/toolbars.rs index fbe26d3..ea87a8b 100644 --- a/src/ui/toolbars.rs +++ b/src/ui/toolbars.rs @@ -253,7 +253,7 @@ pub enum ColorButtons { Second = 1, Third = 2, Fourth = 3, - Fith = 4, + Fifth = 4, Custom = 5, } @@ -296,7 +296,7 @@ impl StyleToolbar { ColorButtons::Second => config.color_palette().second(), ColorButtons::Third => config.color_palette().third(), ColorButtons::Fourth => config.color_palette().fourth(), - ColorButtons::Fith => config.color_palette().fifth(), + ColorButtons::Fifth => config.color_palette().fifth(), ColorButtons::Custom => self.custom_color, } } @@ -359,7 +359,7 @@ impl Component for StyleToolbar { create_icon(APP_CONFIG.read().color_palette().fifth()), - ActionablePlus::set_action::: ColorButtons::Fith, + ActionablePlus::set_action::: ColorButtons::Fifth, }, gtk::Separator {}, gtk::ToggleButton { @@ -546,7 +546,7 @@ impl FromVariant for ColorButtons { 1 => Some(ColorButtons::Second), 2 => Some(ColorButtons::Third), 3 => Some(ColorButtons::Fourth), - 4 => Some(ColorButtons::Fith), + 4 => Some(ColorButtons::Fifth), 5 => Some(ColorButtons::Custom), _ => None, }) From 6c9c32ff86b0752d8790fd9bec7e0e26e7aafb75 Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 14 Jul 2024 14:58:56 +0200 Subject: [PATCH 9/9] add fill support for ellipse tool --- src/tools/ellipse.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/ellipse.rs b/src/tools/ellipse.rs index dfac064..2c818c4 100644 --- a/src/tools/ellipse.rs +++ b/src/tools/ellipse.rs @@ -32,7 +32,11 @@ impl Drawable for Ellipse { let mut path = Path::new(); path.ellipse(self.middle.x, self.middle.y, radii.x, radii.y); - canvas.stroke_path(&path, &self.style.into()); + if self.style.fill { + canvas.fill_path(&path, &self.style.into()); + } else { + canvas.stroke_path(&path, &self.style.into()); + } canvas.restore(); Ok(())