From 78dfd13487958af88a353c376721121298666888 Mon Sep 17 00:00:00 2001 From: DerpDays <34582078+DerpDays@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:42:17 +0100 Subject: [PATCH 1/6] feat: draft freehand highlighter --- src/style.rs | 9 +++-- src/tools/highlight.rs | 92 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 16 deletions(-) diff --git a/src/style.rs b/src/style.rs index ccbcc15..1dcc045 100644 --- a/src/style.rs +++ b/src/style.rs @@ -205,11 +205,12 @@ impl Size { } } - pub fn to_highlight_opacity(self) -> u8 { + pub fn to_highlight_width(self) -> f32 { + let size_factor = APP_CONFIG.read().annotation_size_factor(); match self { - Size::Small => 50, - Size::Medium => 100, - Size::Large => 150, + Size::Small => 15.0 * size_factor, + Size::Medium => 30.0 * size_factor, + Size::Large => 45.0 * size_factor, } } } diff --git a/src/tools/highlight.rs b/src/tools/highlight.rs index e67d375..72eb86b 100644 --- a/src/tools/highlight.rs +++ b/src/tools/highlight.rs @@ -1,7 +1,7 @@ use anyhow::Result; use femtovg::{Paint, Path}; -use relm4::gtk::gdk::Key; +use relm4::gtk::gdk::{Key, ModifierType}; use crate::{ math::{self, Vec2D}, @@ -17,14 +17,41 @@ pub struct Highlight { size: Option, style: Style, editing: bool, + points: Option>, + shift_pressed: bool, } -impl Drawable for Highlight { - fn draw( +impl Highlight { + // This is triggered when a user does not press shift before highlighting. + fn draw_free_hand( &self, canvas: &mut femtovg::Canvas, - _font: femtovg::FontId, ) -> Result<()> { + canvas.save(); + let mut path = Path::new(); + if let Some(points) = &self.points { + let first = points.first().expect("atleast one point"); + path.move_to(first.x, first.y); + for p in points.iter().skip(1) { + path.line_to(first.x + p.x, first.y + p.y); + } + + let mut paint = Paint::color(femtovg::Color::rgba( + self.style.color.r, + self.style.color.g, + self.style.color.b, + (255.0 * 0.4) as u8, + )); + paint.set_line_width(self.style.size.to_highlight_width()); + + canvas.stroke_path(&path, &paint); + } + canvas.restore(); + Ok(()) + } + + /// This is triggered when the user presses shift *before* highlighting. + fn draw_aligned(&self, canvas: &mut femtovg::Canvas) -> Result<()> { let size = match self.size { Some(s) => s, None => return Ok(()), // early exit if size is none @@ -48,7 +75,7 @@ impl Drawable for Highlight { self.style.color.r, self.style.color.g, self.style.color.b, - self.style.size.to_highlight_opacity(), + (255.0 * 0.4) as u8, )); canvas.fill_path(&shadow_path, &shadow_paint); @@ -56,6 +83,21 @@ impl Drawable for Highlight { } } +impl Drawable for Highlight { + fn draw( + &self, + canvas: &mut femtovg::Canvas, + _font: femtovg::FontId, + ) -> Result<()> { + if self.points.is_some() { + self.draw_free_hand(canvas)?; + } else { + self.draw_aligned(canvas)?; + } + Ok(()) + } +} + #[derive(Default)] pub struct HighlightTool { highlight: Option, @@ -64,6 +106,8 @@ pub struct HighlightTool { impl Tool for HighlightTool { fn handle_mouse_event(&mut self, event: MouseEventMsg) -> ToolUpdateResult { + let shift_pressed = event.modifier.intersects(ModifierType::SHIFT_MASK); + let ctrl_pressed = event.modifier.intersects(ModifierType::CONTROL_MASK); match event.type_ { MouseEventType::BeginDrag => { self.highlight = Some(Highlight { @@ -71,21 +115,36 @@ impl Tool for HighlightTool { size: None, style: self.style, editing: true, + points: if !ctrl_pressed { + Some(vec![event.pos]) + } else { + None + }, + shift_pressed, }); ToolUpdateResult::Redraw } MouseEventType::EndDrag => { - if let Some(a) = &mut self.highlight { + if let Some(highlight) = &mut self.highlight { if event.pos == Vec2D::zero() { self.highlight = None; ToolUpdateResult::Redraw } else { - a.size = Some(event.pos); - a.editing = false; - - let result = a.clone_box(); + if let Some(points) = &mut highlight.points { + if shift_pressed { + let last = points.last().expect("should have atleast one point"); + points.push(Vec2D::new(event.pos.x, last.y)); + } else { + points.push(event.pos); + } + } + + highlight.shift_pressed = shift_pressed; + highlight.editing = false; + + let result = highlight.clone_box(); self.highlight = None; ToolUpdateResult::Commit(result) @@ -95,11 +154,20 @@ impl Tool for HighlightTool { } } MouseEventType::UpdateDrag => { - if let Some(a) = &mut self.highlight { + if let Some(highlight) = &mut self.highlight { if event.pos == Vec2D::zero() { return ToolUpdateResult::Unmodified; } - a.size = Some(event.pos); + if let Some(points) = &mut highlight.points { + if shift_pressed { + let last = points.last().expect("should have atleast one point"); + points.push(Vec2D::new(event.pos.x, last.y)); + } else { + points.push(event.pos); + } + } + highlight.size = Some(event.pos); + highlight.shift_pressed = shift_pressed; ToolUpdateResult::Redraw } else { From a9230bdf9ed46ac5f5727eb3b443b2b43c64021a Mon Sep 17 00:00:00 2001 From: DerpDays <34582078+DerpDays@users.noreply.github.com> Date: Sun, 2 Jun 2024 05:06:16 +0100 Subject: [PATCH 2/6] feat: line/pen highlighter refactor + extra features --- config.toml | 3 + src/command_line.rs | 4 + src/configuration.rs | 13 ++ src/main.rs | 11 ++ src/sketch_board.rs | 5 + src/style.rs | 4 + src/tools/highlight.rs | 297 +++++++++++++++++++++++++---------------- src/tools/mod.rs | 6 + 8 files changed, 230 insertions(+), 113 deletions(-) diff --git a/config.toml b/config.toml index 67515d9..b7f723f 100644 --- a/config.toml +++ b/config.toml @@ -15,6 +15,9 @@ output-filename = "/tmp/test-%Y-%m-%d_%H:%M:%S.png" save-after-copy = false # Hide toolbars by default default-hide-toolbars = false +# Whether to set block or line/pen as the default highlighter, other mode is accessible using CTRL. +default-block-highlight = true + # Font to use for text annotations [font] family = "Roboto" diff --git a/src/command_line.rs b/src/command_line.rs index 0aa9008..ef0c199 100644 --- a/src/command_line.rs +++ b/src/command_line.rs @@ -51,6 +51,10 @@ pub struct CommandLine { /// Font style to use for text annotations #[arg(long)] pub font_style: Option, + + /// Change the default highlighter to the line/pen highlighter. + #[arg(long)] + pub default_line_highlight: bool, } #[derive(Debug, Clone, Copy, Default, ValueEnum)] diff --git a/src/configuration.rs b/src/configuration.rs index acbd546..216f0f1 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -39,6 +39,7 @@ pub struct Configuration { color_palette: ColorPalette, default_hide_toolbars: bool, font: FontConfiguration, + default_block_highlight: bool, } #[derive(Default)] @@ -170,6 +171,9 @@ impl Configuration { if let Some(v) = general.default_hide_toolbars { self.default_hide_toolbars = v; } + if let Some(v) = general.default_block_highlight { + self.default_block_highlight = v; + } } fn merge(&mut self, file: Option, command_line: CommandLine) { // input_filename is required and needs to be overwritten @@ -219,6 +223,10 @@ impl Configuration { if let Some(v) = command_line.font_style { self.font.style = Some(v); } + + if command_line.default_line_highlight { + self.default_block_highlight = !command_line.default_line_highlight; + } } pub fn early_exit(&self) -> bool { @@ -261,6 +269,9 @@ impl Configuration { self.default_hide_toolbars } + pub fn default_block_highlight(&self) -> bool { + self.default_block_highlight + } pub fn font(&self) -> &FontConfiguration { &self.font } @@ -280,6 +291,7 @@ impl Default for Configuration { color_palette: ColorPalette::default(), default_hide_toolbars: false, font: FontConfiguration::default(), + default_block_highlight: true, } } } @@ -323,6 +335,7 @@ struct ConfiguationFileGeneral { output_filename: Option, save_after_copy: Option, default_hide_toolbars: Option, + default_block_highlight: Option, } #[derive(Deserialize)] diff --git a/src/main.rs b/src/main.rs index 9132343..32ad989 100644 --- a/src/main.rs +++ b/src/main.rs @@ -169,6 +169,17 @@ impl Component for App { glib::Propagation::Stop }, + connect_key_released[sketch_board_sender] => move |controller, key, code, modifier | { + if let Some(im_context) = controller.im_context() { + im_context.focus_in(); + if !im_context.filter_keypress(controller.current_event().unwrap()) { + sketch_board_sender.emit(SketchBoardInput::new_key_release_event(KeyEventMsg::new(key, code, modifier))); + } + } else { + sketch_board_sender.emit(SketchBoardInput::new_key_release_event(KeyEventMsg::new(key, code, modifier))); + } + }, + #[wrap(Some)] set_im_context = >k::IMMulticontext { connect_commit[sketch_board_sender] => move |_cx, txt| { diff --git a/src/sketch_board.rs b/src/sketch_board.rs index dbddc70..437d224 100644 --- a/src/sketch_board.rs +++ b/src/sketch_board.rs @@ -47,6 +47,7 @@ pub enum SketchBoardOutput { pub enum InputEvent { Mouse(MouseEventMsg), Key(KeyEventMsg), + KeyRelease(KeyEventMsg), Text(TextEventMsg), } @@ -105,6 +106,10 @@ impl SketchBoardInput { SketchBoardInput::InputEvent(InputEvent::Key(event)) } + pub fn new_key_release_event(event: KeyEventMsg) -> SketchBoardInput { + SketchBoardInput::InputEvent(InputEvent::KeyRelease(event)) + } + pub fn new_text_event(event: TextEventMsg) -> SketchBoardInput { SketchBoardInput::InputEvent(InputEvent::Text(event)) } diff --git a/src/style.rs b/src/style.rs index 1dcc045..51e9dd2 100644 --- a/src/style.rs +++ b/src/style.rs @@ -213,4 +213,8 @@ impl Size { Size::Large => 45.0 * size_factor, } } + + pub fn default_block_highlight(self) -> bool { + APP_CONFIG.read().default_block_highlight() + } } diff --git a/src/tools/highlight.rs b/src/tools/highlight.rs index 72eb86b..51eded3 100644 --- a/src/tools/highlight.rs +++ b/src/tools/highlight.rs @@ -1,72 +1,84 @@ +use std::ops::{Add, Sub}; + use anyhow::Result; use femtovg::{Paint, Path}; use relm4::gtk::gdk::{Key, ModifierType}; use crate::{ + configuration::APP_CONFIG, math::{self, Vec2D}, sketch_board::{MouseEventMsg, MouseEventType}, - style::{Size, Style}, + style::Style, + tools::DrawableClone, }; -use super::{Drawable, DrawableClone, Tool, ToolUpdateResult}; +use super::{Drawable, Tool, ToolUpdateResult}; + +const HIGHLIGHT_OPACITY: f64 = 0.4; #[derive(Clone, Debug)] -pub struct Highlight { +struct BlockHighlight { top_left: Vec2D, size: Option, - style: Style, - editing: bool, - points: Option>, +} + +#[derive(Clone, Debug)] +struct LineHighlight { + points: Vec, shift_pressed: bool, } -impl Highlight { - // This is triggered when a user does not press shift before highlighting. - fn draw_free_hand( - &self, - canvas: &mut femtovg::Canvas, - ) -> Result<()> { +#[derive(Clone, Debug)] +struct Highlighter { + data: T, + style: Style, +} + +trait Highlight { + fn highlight(&self, canvas: &mut femtovg::Canvas) -> Result<()>; +} + +impl Highlight for Highlighter { + fn highlight(&self, canvas: &mut femtovg::Canvas) -> Result<()> { canvas.save(); - let mut path = Path::new(); - if let Some(points) = &self.points { - let first = points.first().expect("atleast one point"); - path.move_to(first.x, first.y); - for p in points.iter().skip(1) { - path.line_to(first.x + p.x, first.y + p.y); - } - let mut paint = Paint::color(femtovg::Color::rgba( - self.style.color.r, - self.style.color.g, - self.style.color.b, - (255.0 * 0.4) as u8, - )); - paint.set_line_width(self.style.size.to_highlight_width()); + let mut path = Path::new(); + let first = self + .data + .points + .first() + .expect("should exist atleast one point in highlight instance."); - canvas.stroke_path(&path, &paint); + path.move_to(first.x, first.y); + for p in self.data.points.iter().skip(1) { + path.line_to(first.x + p.x, first.y + p.y); } + + let mut paint = Paint::color(femtovg::Color::rgba( + self.style.color.r, + self.style.color.g, + self.style.color.b, + (255.0 * HIGHLIGHT_OPACITY) as u8, + )); + paint.set_line_width(self.style.size.to_highlight_width()); + paint.set_line_join(femtovg::LineJoin::Round); + paint.set_line_cap(femtovg::LineCap::Square); + + canvas.stroke_path(&path, &paint); canvas.restore(); Ok(()) } +} - /// This is triggered when the user presses shift *before* highlighting. - fn draw_aligned(&self, canvas: &mut femtovg::Canvas) -> Result<()> { - let size = match self.size { +impl Highlight for Highlighter { + fn highlight(&self, canvas: &mut femtovg::Canvas) -> Result<()> { + let size = match self.data.size { Some(s) => s, None => return Ok(()), // early exit if size is none }; - let (pos, size) = math::rect_ensure_positive_size(self.top_left, size); - - if self.editing { - // include a border when selecting an area. - let border_paint = - Paint::color(self.style.color.into()).with_line_width(Size::Small.to_line_width()); - let mut border_path = Path::new(); - border_path.rect(pos.x, pos.y, size.x, size.y); - canvas.stroke_path(&border_path, &border_paint); - } + let (pos, size) = math::rect_ensure_positive_size(self.data.top_left, size); let mut shadow_path = Path::new(); shadow_path.rect(pos.x, pos.y, size.x, size.y); @@ -75,7 +87,7 @@ impl Highlight { self.style.color.r, self.style.color.g, self.style.color.b, - (255.0 * 0.4) as u8, + (255.0 * HIGHLIGHT_OPACITY) as u8, )); canvas.fill_path(&shadow_path, &shadow_paint); @@ -83,108 +95,167 @@ impl Highlight { } } -impl Drawable for Highlight { +#[derive(Clone, Debug)] +enum HighlightKind { + Block(Highlighter), + Line(Highlighter), +} +impl HighlightKind { + fn highlight(&self, canvas: &mut femtovg::Canvas) { + let _ = match self { + HighlightKind::Block(highlighter) => highlighter.highlight(canvas), + HighlightKind::Line(highlighter) => highlighter.highlight(canvas), + }; + } +} + +#[derive(Default, Clone, Debug)] +pub struct HighlightTool { + highlighter: Option, + style: Style, +} + +impl Drawable for HighlightKind { fn draw( &self, canvas: &mut femtovg::Canvas, _font: femtovg::FontId, ) -> Result<()> { - if self.points.is_some() { - self.draw_free_hand(canvas)?; - } else { - self.draw_aligned(canvas)?; - } + self.highlight(canvas); Ok(()) } } -#[derive(Default)] -pub struct HighlightTool { - highlight: Option, - style: Style, -} - impl Tool for HighlightTool { fn handle_mouse_event(&mut self, event: MouseEventMsg) -> ToolUpdateResult { let shift_pressed = event.modifier.intersects(ModifierType::SHIFT_MASK); let ctrl_pressed = event.modifier.intersects(ModifierType::CONTROL_MASK); + let default_highlight_block = APP_CONFIG.read().default_block_highlight(); match event.type_ { MouseEventType::BeginDrag => { - self.highlight = Some(Highlight { - top_left: event.pos, - size: None, - style: self.style, - editing: true, - points: if !ctrl_pressed { - Some(vec![event.pos]) - } else { - None - }, - shift_pressed, - }); + match (ctrl_pressed, default_highlight_block) { + (false, true) | (true, false) => { + self.highlighter = + Some(HighlightKind::Block(Highlighter:: { + data: BlockHighlight { + top_left: event.pos, + size: None, + }, + style: self.style, + })) + } + (false, false) | (true, true) => { + self.highlighter = Some(HighlightKind::Line(Highlighter:: { + data: LineHighlight { + points: vec![event.pos], + shift_pressed, + }, + style: self.style, + })) + } + } ToolUpdateResult::Redraw } - MouseEventType::EndDrag => { - if let Some(highlight) = &mut self.highlight { - if event.pos == Vec2D::zero() { - self.highlight = None; - - ToolUpdateResult::Redraw - } else { - if let Some(points) = &mut highlight.points { - if shift_pressed { - let last = points.last().expect("should have atleast one point"); - points.push(Vec2D::new(event.pos.x, last.y)); - } else { - points.push(event.pos); - } - } - - highlight.shift_pressed = shift_pressed; - highlight.editing = false; - - let result = highlight.clone_box(); - self.highlight = None; - - ToolUpdateResult::Commit(result) - } - } else { - ToolUpdateResult::Unmodified + MouseEventType::UpdateDrag | MouseEventType::EndDrag => { + if self.highlighter.is_none() { + return ToolUpdateResult::Unmodified; } - } - MouseEventType::UpdateDrag => { - if let Some(highlight) = &mut self.highlight { - if event.pos == Vec2D::zero() { - return ToolUpdateResult::Unmodified; + let mut highlighter_kind = self.highlighter.as_mut().unwrap(); + let update: ToolUpdateResult = match &mut highlighter_kind { + HighlightKind::Block(highlighter) => { + if shift_pressed { + let max_size = event.pos.x.abs().max(event.pos.y.abs()); + highlighter.data.size = Some(Vec2D { + x: max_size * event.pos.x.signum(), + y: max_size * event.pos.y.signum(), + }); + } else { + highlighter.data.size = Some(event.pos); + }; + ToolUpdateResult::Redraw } - if let Some(points) = &mut highlight.points { + HighlightKind::Line(highlighter) => { + if event.pos == Vec2D::zero() { + return ToolUpdateResult::Unmodified; + }; + if shift_pressed { - let last = points.last().expect("should have atleast one point"); - points.push(Vec2D::new(event.pos.x, last.y)); + // if shift was pressed before we remove an extra point which would + // have been the previous aligned point. However ignore if there is + // only one point which means the highlight has just started. + if highlighter.data.shift_pressed && highlighter.data.points.len() >= 2 + { + highlighter + .data + .points + .pop() + .expect("atleast 2 points in highlight path."); + }; + // use the last point to position the snapping guide, or 0 if the point + // is the first one. + let last = if highlighter.data.points.len() == 1 { + Vec2D::zero() + } else { + *highlighter + .data + .points + .last_mut() + .expect("atleast one point") + }; + let snapped_pos = event.pos.sub(last).snapped_vector_15deg().add(last); + highlighter.data.points.push(snapped_pos); } else { - points.push(event.pos); + highlighter.data.points.push(event.pos); } - } - highlight.size = Some(event.pos); - highlight.shift_pressed = shift_pressed; - ToolUpdateResult::Redraw - } else { - ToolUpdateResult::Unmodified - } + highlighter.data.shift_pressed = shift_pressed; + ToolUpdateResult::Redraw + } + }; + if event.type_ == MouseEventType::UpdateDrag { + return update; + }; + let result = highlighter_kind.clone_box(); + self.highlighter = None; + ToolUpdateResult::Commit(result) } + _ => ToolUpdateResult::Unmodified, } } fn handle_key_event(&mut self, event: crate::sketch_board::KeyEventMsg) -> ToolUpdateResult { - if event.key == Key::Escape && self.highlight.is_some() { - self.highlight = None; - ToolUpdateResult::Redraw - } else { - ToolUpdateResult::Unmodified + if event.key == Key::Escape && self.highlighter.is_some() { + self.highlighter = None; + return ToolUpdateResult::Redraw; } + ToolUpdateResult::Unmodified + } + + fn handle_key_release_event( + &mut self, + event: crate::sketch_board::KeyEventMsg, + ) -> ToolUpdateResult { + // add an extra point when shift is unheld, this allows for users to make sharper turns. + // press (aka: release) shift a second time to remove the added point. + if event.key == Key::Shift_L || event.key == Key::Shift_R { + if let Some(HighlightKind::Line(highlighter)) = &mut self.highlighter { + let points = &mut highlighter.data.points; + let last = points + .last() + .expect("line highlight must have atleast one point"); + if points.len() >= 2 { + if *last == points[points.len() - 2] { + points.pop(); + } else { + points.push(*last); + } + return ToolUpdateResult::Redraw; + }; + }; + } + ToolUpdateResult::Unmodified } fn handle_style_event(&mut self, style: Style) -> ToolUpdateResult { @@ -193,7 +264,7 @@ impl Tool for HighlightTool { } fn get_drawable(&self) -> Option<&dyn Drawable> { - match &self.highlight { + match &self.highlighter { Some(d) => Some(d), None => None, } diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 75ecfd6..8dfa8d9 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -56,6 +56,7 @@ pub trait Tool { match event { InputEvent::Mouse(e) => self.handle_mouse_event(e), InputEvent::Key(e) => self.handle_key_event(e), + InputEvent::KeyRelease(e) => self.handle_key_release_event(e), InputEvent::Text(e) => self.handle_text_event(e), } } @@ -75,6 +76,11 @@ pub trait Tool { ToolUpdateResult::Unmodified } + fn handle_key_release_event(&mut self, event: KeyEventMsg) -> ToolUpdateResult { + let _ = event; + ToolUpdateResult::Unmodified + } + fn handle_style_event(&mut self, style: Style) -> ToolUpdateResult { let _ = style; ToolUpdateResult::Unmodified From 3b4a1bb576838f7a7b0eb95620b58159fa662aa3 Mon Sep 17 00:00:00 2001 From: DerpDays <34582078+DerpDays@users.noreply.github.com> Date: Sun, 2 Jun 2024 05:35:54 +0100 Subject: [PATCH 3/6] chore: remove redundant code --- src/tools/highlight.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/tools/highlight.rs b/src/tools/highlight.rs index 51eded3..c061046 100644 --- a/src/tools/highlight.rs +++ b/src/tools/highlight.rs @@ -100,14 +100,6 @@ enum HighlightKind { Block(Highlighter), Line(Highlighter), } -impl HighlightKind { - fn highlight(&self, canvas: &mut femtovg::Canvas) { - let _ = match self { - HighlightKind::Block(highlighter) => highlighter.highlight(canvas), - HighlightKind::Line(highlighter) => highlighter.highlight(canvas), - }; - } -} #[derive(Default, Clone, Debug)] pub struct HighlightTool { @@ -121,8 +113,10 @@ impl Drawable for HighlightKind { canvas: &mut femtovg::Canvas, _font: femtovg::FontId, ) -> Result<()> { - self.highlight(canvas); - Ok(()) + match self { + HighlightKind::Block(highlighter) => highlighter.highlight(canvas), + HighlightKind::Line(highlighter) => highlighter.highlight(canvas), + } } } From 2ad8f93daf7e6bae9013b06049cdebc6e9aeaede Mon Sep 17 00:00:00 2001 From: DerpDays <34582078+DerpDays@users.noreply.github.com> Date: Sun, 2 Jun 2024 05:40:44 +0100 Subject: [PATCH 4/6] docs: add default highlighter option and CLI param to readme --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 116c190..4182d73 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ apk add satty You can download a prebuilt binary for x86-64 on the [Satty Releases](https://github.com/gabm/satty/releases) page. - ## Usage Start by providing a filename or a screenshot via stdin and annotate using the available tools. Save to clipboard or file when finished. Tools and Interface have been kept simple. @@ -78,6 +77,9 @@ output-filename = "/tmp/test-%Y-%m-%d_%H:%M:%S.png" save-after-copy = false # Hide toolbars by default default-hide-toolbars = false +# Whether to set block or line/pen as the default highlighter, other mode is accessible using CTRL. +default-block-highlight = true + # Font to use for text annotations [font] family = "Roboto" @@ -122,6 +124,8 @@ Options: After copying the screenshot, save it to a file as well -d, --default-hide-toolbars Hide toolbars by default + --default-line-highlight + Change the default highlighter to the line/pen highlighter --font-family Font family to use for text annotations --font-style @@ -140,7 +144,6 @@ You can bind a key to the following command: grim -g "$(slurp -o -r -c '#ff0000ff')" - | satty --filename - --fullscreen --output-filename ~/Pictures/Screenshots/satty-$(date '+%Y%m%d-%H:%M:%S').png ``` - ## Build from source You first need to install the native dependencies of Satty (see below) and then run: From 94b58025af996c22a9905d1b125630441a8acc0c Mon Sep 17 00:00:00 2001 From: DerpDays <34582078+DerpDays@users.noreply.github.com> Date: Tue, 4 Jun 2024 03:47:12 +0100 Subject: [PATCH 5/6] chore: rename highlight options, consistent highlight mode naming, make match arms clearer --- README.md | 8 +++--- config.toml | 4 +-- src/command_line.rs | 11 ++++++-- src/configuration.rs | 24 +++++++++------- src/style.rs | 4 --- src/tools/highlight.rs | 62 ++++++++++++++++++++++++++++++------------ src/tools/mod.rs | 2 +- 7 files changed, 75 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4182d73..dffd121 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,8 @@ output-filename = "/tmp/test-%Y-%m-%d_%H:%M:%S.png" save-after-copy = false # Hide toolbars by default default-hide-toolbars = false -# Whether to set block or line/pen as the default highlighter, other mode is accessible using CTRL. -default-block-highlight = true +# The primary highlighter to use, the other is accessible by holding CTRL at the start of a highlight [possible values: block, freehand] +primary-highlighter = "block" # Font to use for text annotations [font] @@ -124,8 +124,8 @@ Options: After copying the screenshot, save it to a file as well -d, --default-hide-toolbars Hide toolbars by default - --default-line-highlight - Change the default highlighter to the line/pen highlighter + --primary-highlighter + The primary highlighter to use, secondary is accessible with CTRL [possible values: block, freehand] --font-family Font family to use for text annotations --font-style diff --git a/config.toml b/config.toml index b7f723f..d72ad50 100644 --- a/config.toml +++ b/config.toml @@ -15,8 +15,8 @@ output-filename = "/tmp/test-%Y-%m-%d_%H:%M:%S.png" save-after-copy = false # Hide toolbars by default default-hide-toolbars = false -# Whether to set block or line/pen as the default highlighter, other mode is accessible using CTRL. -default-block-highlight = true +# The primary highlighter to use, the other is accessible by holding CTRL at the start of a highlight [possible values: block, freehand] +primary-highlighter = "block" # Font to use for text annotations [font] diff --git a/src/command_line.rs b/src/command_line.rs index ef0c199..3c0e9d6 100644 --- a/src/command_line.rs +++ b/src/command_line.rs @@ -52,9 +52,9 @@ pub struct CommandLine { #[arg(long)] pub font_style: Option, - /// Change the default highlighter to the line/pen highlighter. + /// The primary highlighter to use, secondary is accessible with CTRL. #[arg(long)] - pub default_line_highlight: bool, + pub primary_highlighter: Option, } #[derive(Debug, Clone, Copy, Default, ValueEnum)] @@ -72,6 +72,13 @@ pub enum Tools { Brush, } +#[derive(Debug, Clone, Copy, Default, ValueEnum)] +pub enum Highlighters { + #[default] + Block, + Freehand, +} + impl std::fmt::Display for Tools { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use Tools::*; diff --git a/src/configuration.rs b/src/configuration.rs index 216f0f1..ddd87b5 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -11,7 +11,11 @@ use serde_derive::Deserialize; use thiserror::Error; use xdg::{BaseDirectories, BaseDirectoriesError}; -use crate::{command_line::CommandLine, style::Color, tools::Tools}; +use crate::{ + command_line::CommandLine, + style::Color, + tools::{Highlighters, Tools}, +}; pub static APP_CONFIG: SharedState = SharedState::new(); @@ -39,7 +43,7 @@ pub struct Configuration { color_palette: ColorPalette, default_hide_toolbars: bool, font: FontConfiguration, - default_block_highlight: bool, + primary_highlighter: Highlighters, } #[derive(Default)] @@ -171,8 +175,8 @@ impl Configuration { if let Some(v) = general.default_hide_toolbars { self.default_hide_toolbars = v; } - if let Some(v) = general.default_block_highlight { - self.default_block_highlight = v; + if let Some(v) = general.primary_highlighter { + self.primary_highlighter = v; } } fn merge(&mut self, file: Option, command_line: CommandLine) { @@ -224,8 +228,8 @@ impl Configuration { self.font.style = Some(v); } - if command_line.default_line_highlight { - self.default_block_highlight = !command_line.default_line_highlight; + if let Some(v) = command_line.primary_highlighter { + self.primary_highlighter = v.into(); } } @@ -269,8 +273,8 @@ impl Configuration { self.default_hide_toolbars } - pub fn default_block_highlight(&self) -> bool { - self.default_block_highlight + pub fn primary_highlighter(&self) -> Highlighters { + self.primary_highlighter } pub fn font(&self) -> &FontConfiguration { &self.font @@ -291,7 +295,7 @@ impl Default for Configuration { color_palette: ColorPalette::default(), default_hide_toolbars: false, font: FontConfiguration::default(), - default_block_highlight: true, + primary_highlighter: Highlighters::Block, } } } @@ -335,7 +339,7 @@ struct ConfiguationFileGeneral { output_filename: Option, save_after_copy: Option, default_hide_toolbars: Option, - default_block_highlight: Option, + primary_highlighter: Option, } #[derive(Deserialize)] diff --git a/src/style.rs b/src/style.rs index 51e9dd2..1dcc045 100644 --- a/src/style.rs +++ b/src/style.rs @@ -213,8 +213,4 @@ impl Size { Size::Large => 45.0 * size_factor, } } - - pub fn default_block_highlight(self) -> bool { - APP_CONFIG.read().default_block_highlight() - } } diff --git a/src/tools/highlight.rs b/src/tools/highlight.rs index c061046..b46b1ad 100644 --- a/src/tools/highlight.rs +++ b/src/tools/highlight.rs @@ -4,8 +4,10 @@ use anyhow::Result; use femtovg::{Paint, Path}; use relm4::gtk::gdk::{Key, ModifierType}; +use serde_derive::Deserialize; use crate::{ + command_line, configuration::APP_CONFIG, math::{self, Vec2D}, sketch_board::{MouseEventMsg, MouseEventType}, @@ -17,6 +19,22 @@ use super::{Drawable, Tool, ToolUpdateResult}; const HIGHLIGHT_OPACITY: f64 = 0.4; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Highlighters { + Block = 0, + Freehand = 1, +} + +impl From for Highlighters { + fn from(tool: command_line::Highlighters) -> Self { + match tool { + command_line::Highlighters::Block => Self::Block, + command_line::Highlighters::Freehand => Self::Freehand, + } + } +} + #[derive(Clone, Debug)] struct BlockHighlight { top_left: Vec2D, @@ -24,7 +42,7 @@ struct BlockHighlight { } #[derive(Clone, Debug)] -struct LineHighlight { +struct FreehandHighlight { points: Vec, shift_pressed: bool, } @@ -39,7 +57,7 @@ trait Highlight { fn highlight(&self, canvas: &mut femtovg::Canvas) -> Result<()>; } -impl Highlight for Highlighter { +impl Highlight for Highlighter { fn highlight(&self, canvas: &mut femtovg::Canvas) -> Result<()> { canvas.save(); @@ -98,7 +116,7 @@ impl Highlight for Highlighter { #[derive(Clone, Debug)] enum HighlightKind { Block(Highlighter), - Line(Highlighter), + Freehand(Highlighter), } #[derive(Default, Clone, Debug)] @@ -115,7 +133,7 @@ impl Drawable for HighlightKind { ) -> Result<()> { match self { HighlightKind::Block(highlighter) => highlighter.highlight(canvas), - HighlightKind::Line(highlighter) => highlighter.highlight(canvas), + HighlightKind::Freehand(highlighter) => highlighter.highlight(canvas), } } } @@ -124,11 +142,17 @@ impl Tool for HighlightTool { fn handle_mouse_event(&mut self, event: MouseEventMsg) -> ToolUpdateResult { let shift_pressed = event.modifier.intersects(ModifierType::SHIFT_MASK); let ctrl_pressed = event.modifier.intersects(ModifierType::CONTROL_MASK); - let default_highlight_block = APP_CONFIG.read().default_block_highlight(); + let primary_highlighter = APP_CONFIG.read().primary_highlighter(); match event.type_ { MouseEventType::BeginDrag => { - match (ctrl_pressed, default_highlight_block) { - (false, true) | (true, false) => { + // There exists two types of highlighting modes currently: freehand, block + // A user may set a primary highlighter mode, with the other being accessible + // by clicking CTRL when starting a highlight (doesn't need to be held). + match (primary_highlighter, ctrl_pressed) { + // This matches when CTRL is not pressed and the primary highlighting mode + // is block, along with its inverse, CTRL pressed with the freehand mode + // being their primary highlighting mode. + (Highlighters::Block, false) | (Highlighters::Freehand, true) => { self.highlighter = Some(HighlightKind::Block(Highlighter:: { data: BlockHighlight { @@ -138,14 +162,18 @@ impl Tool for HighlightTool { style: self.style, })) } - (false, false) | (true, true) => { - self.highlighter = Some(HighlightKind::Line(Highlighter:: { - data: LineHighlight { - points: vec![event.pos], - shift_pressed, - }, - style: self.style, - })) + // This matches the remaining two cases, which is when the user has the + // freehand mode as the primary mode and CTRL is not pressed, and conversely, + // when CTRL is pressed and the users primary mode is block. + (Highlighters::Freehand, false) | (Highlighters::Block, true) => { + self.highlighter = + Some(HighlightKind::Freehand(Highlighter:: { + data: FreehandHighlight { + points: vec![event.pos], + shift_pressed, + }, + style: self.style, + })) } } @@ -169,7 +197,7 @@ impl Tool for HighlightTool { }; ToolUpdateResult::Redraw } - HighlightKind::Line(highlighter) => { + HighlightKind::Freehand(highlighter) => { if event.pos == Vec2D::zero() { return ToolUpdateResult::Unmodified; }; @@ -234,7 +262,7 @@ impl Tool for HighlightTool { // add an extra point when shift is unheld, this allows for users to make sharper turns. // press (aka: release) shift a second time to remove the added point. if event.key == Key::Shift_L || event.key == Key::Shift_R { - if let Some(HighlightKind::Line(highlighter)) = &mut self.highlighter { + if let Some(HighlightKind::Freehand(highlighter)) = &mut self.highlighter { let points = &mut highlighter.data.points; let last = points .last() diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 8dfa8d9..91e26e1 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -132,7 +132,7 @@ pub enum ToolUpdateResult { pub use arrow::ArrowTool; pub use blur::BlurTool; pub use crop::CropTool; -pub use highlight::HighlightTool; +pub use highlight::{HighlightTool, Highlighters}; pub use line::LineTool; pub use rectangle::RectangleTool; pub use text::TextTool; From 41e2cf0e0279f2e154ea2c1e586aa684c7bf26f9 Mon Sep 17 00:00:00 2001 From: DerpDays <34582078+DerpDays@users.noreply.github.com> Date: Tue, 4 Jun 2024 04:25:16 +0100 Subject: [PATCH 6/6] chore: comment about highlighting controls --- src/tools/highlight.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/tools/highlight.rs b/src/tools/highlight.rs index b46b1ad..dc33ec6 100644 --- a/src/tools/highlight.rs +++ b/src/tools/highlight.rs @@ -186,6 +186,8 @@ impl Tool for HighlightTool { let mut highlighter_kind = self.highlighter.as_mut().unwrap(); let update: ToolUpdateResult = match &mut highlighter_kind { HighlightKind::Block(highlighter) => { + // When shift is pressed when using the block highlighter, it transforms + // the area into a perfect square (in the direction they intended). if shift_pressed { let max_size = event.pos.x.abs().max(event.pos.y.abs()); highlighter.data.size = Some(Vec2D { @@ -202,6 +204,16 @@ impl Tool for HighlightTool { return ToolUpdateResult::Unmodified; }; + // The freehand highlighter has a more complex shift model: + // when pressing shift it begins a straight line, which is aligned + // from the point after shift was pressed, to any 15*n degree rotation. + // + // After releasing shift, it creates an extra point, this is useful since + // it means that users do not need to move their mouse to achieve perfectly + // aligned turns, since they can release, then hold shift again to continue + // another aligned line. + // This extra point can be removed by releasing shift again (if the cursor + // hasn't moved) if shift_pressed { // if shift was pressed before we remove an extra point which would // have been the previous aligned point. However ignore if there is @@ -259,8 +271,9 @@ impl Tool for HighlightTool { &mut self, event: crate::sketch_board::KeyEventMsg, ) -> ToolUpdateResult { - // add an extra point when shift is unheld, this allows for users to make sharper turns. - // press (aka: release) shift a second time to remove the added point. + // Adds an extra point when shift is released in the freehand mode, this + // allows for users to make sharper turns. Release shift a second time + // to remove the added point (only if the cursor has not moved). if event.key == Key::Shift_L || event.key == Key::Shift_R { if let Some(HighlightKind::Freehand(highlighter)) = &mut self.highlighter { let points = &mut highlighter.data.points;