From e563bcb8e58a90de0acfb0da60c3465cee138b10 Mon Sep 17 00:00:00 2001 From: devmobasa <4170275+devmobasa@users.noreply.github.com> Date: Sat, 24 Jan 2026 23:02:20 +0100 Subject: [PATCH 1/2] Add highlight ring toggle in toolbar --- config.example.toml | 3 + .../src/app/view/ui/click_highlight.rs | 6 ++ .../src/models/config/draft/from_config.rs | 4 ++ configurator/src/models/config/draft/mod.rs | 1 + configurator/src/models/config/setters.rs | 3 + .../src/models/config/to_config/ui.rs | 2 + configurator/src/models/fields/toggles.rs | 1 + docs/CONFIG.md | 2 + .../wayland/handlers/pointer/enter_leave.rs | 11 ++- .../wayland/handlers/pointer/motion.rs | 10 +++ .../wayland/state/render/canvas/mod.rs | 2 + .../wayland/state/toolbar/drag/base.rs | 2 +- src/backend/wayland/state/toolbar/events.rs | 13 ++++ .../state/toolbar/visibility/access.rs | 15 +++++ .../wayland/toolbar/layout/top/icons.rs | 15 +++++ .../render/top_strip/icons/utility_row.rs | 33 +++++++++ src/config/types/click_highlight.rs | 9 +++ src/input/state/core/highlight_controls.rs | 29 ++++++++ src/input/state/core/menus/lifecycle.rs | 4 +- src/input/state/core/utility/interaction.rs | 13 ++++ src/input/state/highlight/settings.rs | 3 + src/input/state/highlight/state/mod.rs | 67 +++++++++++++++++++ src/ui/toolbar/apply/mod.rs | 3 + src/ui/toolbar/apply/tools.rs | 4 ++ src/ui/toolbar/events.rs | 2 + src/ui/toolbar/snapshot.rs | 2 + 26 files changed, 255 insertions(+), 4 deletions(-) diff --git a/config.example.toml b/config.example.toml index bbceb1a5..e8eb5c1c 100644 --- a/config.example.toml +++ b/config.example.toml @@ -385,6 +385,9 @@ dot_radius = 6.0 # Start with the highlight effect enabled when the overlay launches enabled = false +# Show a persistent ring while the highlight tool is active +show_on_highlight_tool = false + # Radius of the highlight circle in pixels (16 - 160) radius = 24.0 diff --git a/configurator/src/app/view/ui/click_highlight.rs b/configurator/src/app/view/ui/click_highlight.rs index 3b174e00..9514e273 100644 --- a/configurator/src/app/view/ui/click_highlight.rs +++ b/configurator/src/app/view/ui/click_highlight.rs @@ -20,6 +20,12 @@ impl ConfiguratorApp { self.defaults.click_highlight_enabled, ToggleField::UiClickHighlightEnabled, ), + toggle_row( + "Show ring while highlight tool is active", + self.draft.click_highlight_show_on_highlight_tool, + self.defaults.click_highlight_show_on_highlight_tool, + ToggleField::UiClickHighlightShowOnHighlightTool, + ), toggle_row( "Link highlight color to current pen", self.draft.click_highlight_use_pen_color, diff --git a/configurator/src/models/config/draft/from_config.rs b/configurator/src/models/config/draft/from_config.rs index b4b6276c..ce08cdc1 100644 --- a/configurator/src/models/config/draft/from_config.rs +++ b/configurator/src/models/config/draft/from_config.rs @@ -104,6 +104,10 @@ impl ConfigDraft { status_dot_radius: format_float(config.ui.status_bar_style.dot_radius), click_highlight_enabled: config.ui.click_highlight.enabled, + click_highlight_show_on_highlight_tool: config + .ui + .click_highlight + .show_on_highlight_tool, click_highlight_use_pen_color: config.ui.click_highlight.use_pen_color, click_highlight_radius: format_float(config.ui.click_highlight.radius), click_highlight_outline_thickness: format_float( diff --git a/configurator/src/models/config/draft/mod.rs b/configurator/src/models/config/draft/mod.rs index d0511215..e7614e83 100644 --- a/configurator/src/models/config/draft/mod.rs +++ b/configurator/src/models/config/draft/mod.rs @@ -89,6 +89,7 @@ pub struct ConfigDraft { pub status_dot_radius: String, pub click_highlight_enabled: bool, + pub click_highlight_show_on_highlight_tool: bool, pub click_highlight_use_pen_color: bool, pub click_highlight_radius: String, pub click_highlight_outline_thickness: String, diff --git a/configurator/src/models/config/setters.rs b/configurator/src/models/config/setters.rs index 2e6d44ef..6ad9acc7 100644 --- a/configurator/src/models/config/setters.rs +++ b/configurator/src/models/config/setters.rs @@ -88,6 +88,9 @@ impl ConfigDraft { self.ui_toolbar_force_inline = value; } ToggleField::UiClickHighlightEnabled => self.click_highlight_enabled = value, + ToggleField::UiClickHighlightShowOnHighlightTool => { + self.click_highlight_show_on_highlight_tool = value; + } ToggleField::UiClickHighlightUsePenColor => self.click_highlight_use_pen_color = value, ToggleField::PresenterHideStatusBar => self.presenter_hide_status_bar = value, ToggleField::PresenterHideToolbars => self.presenter_hide_toolbars = value, diff --git a/configurator/src/models/config/to_config/ui.rs b/configurator/src/models/config/to_config/ui.rs index 29760d3e..648394f1 100644 --- a/configurator/src/models/config/to_config/ui.rs +++ b/configurator/src/models/config/to_config/ui.rs @@ -97,6 +97,8 @@ impl ConfigDraft { ); config.ui.click_highlight.enabled = self.click_highlight_enabled; + config.ui.click_highlight.show_on_highlight_tool = + self.click_highlight_show_on_highlight_tool; config.ui.click_highlight.use_pen_color = self.click_highlight_use_pen_color; parse_field( &self.click_highlight_radius, diff --git a/configurator/src/models/fields/toggles.rs b/configurator/src/models/fields/toggles.rs index 2900ca88..dc6271b4 100644 --- a/configurator/src/models/fields/toggles.rs +++ b/configurator/src/models/fields/toggles.rs @@ -27,6 +27,7 @@ pub enum ToggleField { UiToolbarShowToolPreview, UiToolbarForceInline, UiClickHighlightEnabled, + UiClickHighlightShowOnHighlightTool, UiClickHighlightUsePenColor, UiShowStatusBoardBadge, UiShowStatusPageBadge, diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 132517fa..90fd8064 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -267,6 +267,7 @@ text_color = [0.95, 0.96, 0.98, 1.0] # Near-white # Click highlight styling (visual feedback for mouse clicks) [ui.click_highlight] enabled = false +show_on_highlight_tool = false radius = 24.0 outline_thickness = 4.0 duration_ms = 750 @@ -295,6 +296,7 @@ enabled = true - **Colors**: All RGBA values (0.0-1.0 range) with transparency control - **Layout**: Padding, line height, dot size, border width all configurable - **Click highlight**: Enable presenter-style click halos with adjustable radius, colors, and duration; by default the halo follows your current pen color (set `use_pen_color = false` to keep a fixed color) +- **Highlight tool ring**: `show_on_highlight_tool = true` keeps a persistent halo visible while the highlight tool is active - **Context menu**: `ui.context_menu.enabled` toggles right-click / keyboard menus - **GNOME fallback**: `preferred_output` pins the xdg-shell overlay to a specific monitor; `xdg_fullscreen` requests fullscreen instead of maximized diff --git a/src/backend/wayland/handlers/pointer/enter_leave.rs b/src/backend/wayland/handlers/pointer/enter_leave.rs index f0fbf5a0..8c548a46 100644 --- a/src/backend/wayland/handlers/pointer/enter_leave.rs +++ b/src/backend/wayland/handlers/pointer/enter_leave.rs @@ -23,12 +23,21 @@ impl WaylandState { ); self.set_pointer_focus(true); self.set_pointer_over_toolbar(on_toolbar); - self.set_current_mouse(event.position.0 as i32, event.position.1 as i32); if on_toolbar { + if let Some((sx, sy)) = + self.toolbar_surface_screen_coords(&event.surface, event.position) + { + self.set_current_mouse(sx as i32, sy as i32); + let (wx, wy) = self.zoomed_world_coords(sx, sy); + self.input_state.update_pointer_position(wx, wy); + } else { + self.set_current_mouse(event.position.0 as i32, event.position.1 as i32); + } // Ensure pointer-driven visuals (e.g. eraser hover) update once on enter. self.input_state.needs_redraw = true; } if !on_toolbar { + self.set_current_mouse(event.position.0 as i32, event.position.1 as i32); let (wx, wy) = self.zoomed_world_coords(event.position.0, event.position.1); self.input_state.update_pointer_position(wx, wy); if self.input_state.eraser_mode == EraserMode::Stroke diff --git a/src/backend/wayland/handlers/pointer/motion.rs b/src/backend/wayland/handlers/pointer/motion.rs index dce07444..bbb8aa94 100644 --- a/src/backend/wayland/handlers/pointer/motion.rs +++ b/src/backend/wayland/handlers/pointer/motion.rs @@ -41,6 +41,13 @@ impl WaylandState { } if on_toolbar { self.set_pointer_over_toolbar(true); + if let Some((sx, sy)) = + self.toolbar_surface_screen_coords(&event.surface, event.position) + { + self.set_current_mouse(sx as i32, sy as i32); + let (wx, wy) = self.zoomed_world_coords(sx, sy); + self.input_state.update_pointer_position(wx, wy); + } let evt = self.toolbar.pointer_motion(&event.surface, event.position); if self.toolbar_dragging() { // Use move_drag_intent if pointer_motion didn't return an intent @@ -63,6 +70,9 @@ impl WaylandState { return; } if self.pointer_over_toolbar() { + self.set_current_mouse(event.position.0 as i32, event.position.1 as i32); + let (wx, wy) = self.zoomed_world_coords(event.position.0, event.position.1); + self.input_state.update_pointer_position(wx, wy); let evt = self.toolbar.pointer_motion(&event.surface, event.position); if self.toolbar_dragging() { // Use move_drag_intent if pointer_motion didn't return an intent diff --git a/src/backend/wayland/state/render/canvas/mod.rs b/src/backend/wayland/state/render/canvas/mod.rs index 6c0708e2..e3cbd427 100644 --- a/src/backend/wayland/state/render/canvas/mod.rs +++ b/src/backend/wayland/state/render/canvas/mod.rs @@ -153,6 +153,8 @@ impl WaylandState { // Render text cursor/buffer if in text mode self.render_text_input_preview(ctx); + self.input_state.render_highlight_tool_ring(ctx, mx, my); + // Render click highlight overlays before UI so status/help remain legible self.input_state.render_click_highlights(ctx, now); diff --git a/src/backend/wayland/state/toolbar/drag/base.rs b/src/backend/wayland/state/toolbar/drag/base.rs index 73d13ac2..46800876 100644 --- a/src/backend/wayland/state/toolbar/drag/base.rs +++ b/src/backend/wayland/state/toolbar/drag/base.rs @@ -43,7 +43,7 @@ impl WaylandState { /// Convert a toolbar-local coordinate into a screen-relative coordinate so that /// dragging continues to work even after the surface has moved. - pub(super) fn local_to_screen_coords( + pub(in crate::backend::wayland) fn local_to_screen_coords( &self, kind: MoveDragKind, local_coord: (f64, f64), diff --git a/src/backend/wayland/state/toolbar/events.rs b/src/backend/wayland/state/toolbar/events.rs index f163acac..a35d33fe 100644 --- a/src/backend/wayland/state/toolbar/events.rs +++ b/src/backend/wayland/state/toolbar/events.rs @@ -98,6 +98,7 @@ impl WaylandState { | ToolbarEvent::ToggleFill(_) | ToolbarEvent::ApplyPreset(_) ); + let persist_click_highlight = matches!(event, ToolbarEvent::ToggleHighlightToolRing(_)); if self.input_state.apply_toolbar_event(event) { self.toolbar.mark_dirty(); @@ -121,6 +122,10 @@ impl WaylandState { if persist_drawing { self.save_drawing_preferences(); } + + if persist_click_highlight { + self.save_click_highlight_preferences(); + } } if let Some(action) = self.input_state.take_pending_preset_action() { self.handle_preset_action(action); @@ -200,6 +205,14 @@ impl WaylandState { } } + pub(in crate::backend::wayland) fn save_click_highlight_preferences(&mut self) { + self.config.ui.click_highlight.show_on_highlight_tool = + self.input_state.highlight_tool_ring_enabled(); + if let Err(err) = self.config.save() { + log::warn!("Failed to persist click highlight preferences: {}", err); + } + } + pub(in crate::backend::wayland) fn handle_preset_action( &mut self, action: crate::input::state::PresetAction, diff --git a/src/backend/wayland/state/toolbar/visibility/access.rs b/src/backend/wayland/state/toolbar/visibility/access.rs index aaf32338..8ff7452d 100644 --- a/src/backend/wayland/state/toolbar/visibility/access.rs +++ b/src/backend/wayland/state/toolbar/visibility/access.rs @@ -1,4 +1,6 @@ use super::*; +use crate::backend::wayland::toolbar::ToolbarFocusTarget; +use wayland_client::protocol::wl_surface; impl WaylandState { pub(in crate::backend::wayland) fn pointer_over_toolbar(&self) -> bool { @@ -56,4 +58,17 @@ impl WaylandState { pub(in crate::backend::wayland) fn inline_toolbars_render_active(&self) -> bool { self.inline_toolbars_active() || self.toolbar_drag_preview_active() } + + pub(in crate::backend::wayland) fn toolbar_surface_screen_coords( + &self, + surface: &wl_surface::WlSurface, + position: (f64, f64), + ) -> Option<(f64, f64)> { + let target = self.toolbar.focus_target_for_surface(surface)?; + let kind = match target { + ToolbarFocusTarget::Top => MoveDragKind::Top, + ToolbarFocusTarget::Side => MoveDragKind::Side, + }; + Some(self.local_to_screen_coords(kind, position)) + } } diff --git a/src/backend/wayland/toolbar/layout/top/icons.rs b/src/backend/wayland/toolbar/layout/top/icons.rs index f996aa2f..679b0a92 100644 --- a/src/backend/wayland/toolbar/layout/top/icons.rs +++ b/src/backend/wayland/toolbar/layout/top/icons.rs @@ -124,6 +124,7 @@ pub(super) fn build_hits( }); x += btn_size + gap; + let highlight_x = x; hits.push(HitRegion { rect: (x, y, btn_size, btn_size), event: ToolbarEvent::ToggleAllHighlight(!snapshot.any_highlight_active), @@ -135,6 +136,20 @@ pub(super) fn build_hits( .binding_for_action(Action::ToggleHighlightTool), )), }); + if snapshot.highlight_tool_active { + let ring_y = y + btn_size + ToolbarLayoutSpec::TOP_ICON_FILL_OFFSET; + hits.push(HitRegion { + rect: ( + highlight_x, + ring_y, + btn_size, + ToolbarLayoutSpec::TOP_ICON_FILL_HEIGHT, + ), + event: ToolbarEvent::ToggleHighlightToolRing(!snapshot.highlight_tool_ring_enabled), + kind: HitKind::Click, + tooltip: Some("Highlight ring".to_string()), + }); + } x += btn_size + gap; } diff --git a/src/backend/wayland/toolbar/render/top_strip/icons/utility_row.rs b/src/backend/wayland/toolbar/render/top_strip/icons/utility_row.rs index e1ff04d5..f816c449 100644 --- a/src/backend/wayland/toolbar/render/top_strip/icons/utility_row.rs +++ b/src/backend/wayland/toolbar/render/top_strip/icons/utility_row.rs @@ -1,10 +1,13 @@ use crate::backend::wayland::toolbar::events::HitKind; use crate::backend::wayland::toolbar::format_binding_label; use crate::backend::wayland::toolbar::hit::HitRegion; +use crate::backend::wayland::toolbar::layout::ToolbarLayoutSpec; use crate::config::{Action, action_label}; use crate::toolbar_icons; use crate::ui::toolbar::ToolbarEvent; +use crate::ui_text::UiTextStyle; +use super::super::super::widgets::constants::{FONT_FAMILY_DEFAULT, FONT_SIZE_SMALL}; use super::super::super::widgets::*; use super::TopStripLayout; @@ -19,6 +22,12 @@ pub(super) fn draw_utility_row( ) -> f64 { let snapshot = layout.snapshot; let hover = layout.hover; + let mini_label_style = UiTextStyle { + family: FONT_FAMILY_DEFAULT, + slant: cairo::FontSlant::Normal, + weight: cairo::FontWeight::Normal, + size: FONT_SIZE_SMALL, + }; let is_hover = hover .map(|(hx, hy)| point_in_rect(hx, hy, x, y, btn_size, btn_size)) @@ -139,6 +148,30 @@ pub(super) fn draw_utility_row( .binding_for_action(Action::ToggleHighlightTool), )), }); + if snapshot.highlight_tool_active { + let ring_y = y + btn_size + ToolbarLayoutSpec::TOP_ICON_FILL_OFFSET; + let ring_h = ToolbarLayoutSpec::TOP_ICON_FILL_HEIGHT; + let ring_hover = hover + .map(|(hx, hy)| point_in_rect(hx, hy, x, ring_y, btn_size, ring_h)) + .unwrap_or(false); + draw_mini_checkbox( + layout.ctx, + x, + ring_y, + btn_size, + ring_h, + snapshot.highlight_tool_ring_enabled, + ring_hover, + mini_label_style, + "Ring", + ); + layout.hits.push(HitRegion { + rect: (x, ring_y, btn_size, ring_h), + event: ToolbarEvent::ToggleHighlightToolRing(!snapshot.highlight_tool_ring_enabled), + kind: HitKind::Click, + tooltip: Some("Highlight ring".to_string()), + }); + } x += btn_size + gap; } diff --git a/src/config/types/click_highlight.rs b/src/config/types/click_highlight.rs index 8035c0eb..ad2e0852 100644 --- a/src/config/types/click_highlight.rs +++ b/src/config/types/click_highlight.rs @@ -8,6 +8,10 @@ pub struct ClickHighlightConfig { #[serde(default = "default_click_highlight_enabled")] pub enabled: bool, + /// Show a persistent ring while the highlight tool is active + #[serde(default = "default_click_highlight_show_on_highlight_tool")] + pub show_on_highlight_tool: bool, + /// Radius of the highlight circle in pixels #[serde(default = "default_click_highlight_radius")] pub radius: f64, @@ -37,6 +41,7 @@ impl Default for ClickHighlightConfig { fn default() -> Self { Self { enabled: default_click_highlight_enabled(), + show_on_highlight_tool: default_click_highlight_show_on_highlight_tool(), radius: default_click_highlight_radius(), outline_thickness: default_click_highlight_outline(), duration_ms: default_click_highlight_duration_ms(), @@ -51,6 +56,10 @@ fn default_click_highlight_enabled() -> bool { false } +fn default_click_highlight_show_on_highlight_tool() -> bool { + false +} + fn default_click_highlight_radius() -> f64 { 24.0 } diff --git a/src/input/state/core/highlight_controls.rs b/src/input/state/core/highlight_controls.rs index 542aa10f..3a266974 100644 --- a/src/input/state/core/highlight_controls.rs +++ b/src/input/state/core/highlight_controls.rs @@ -10,6 +10,28 @@ impl InputState { self.click_highlight.enabled() } + /// Returns whether the persistent highlight ring is enabled. + pub fn highlight_tool_ring_enabled(&self) -> bool { + self.click_highlight.show_on_highlight_tool() + } + + /// Enables or disables the persistent highlight ring. + pub fn set_highlight_tool_ring_enabled(&mut self, enabled: bool) -> bool { + let (x, y) = self.last_pointer_position; + if self.click_highlight.set_show_on_highlight_tool( + enabled, + self.highlight_tool_active(), + x, + y, + &mut self.dirty_tracker, + ) { + self.needs_redraw = true; + true + } else { + false + } + } + /// Toggle the click highlight feature and mark the frame for redraw. pub fn toggle_click_highlight(&mut self) -> bool { let enabled = self.click_highlight.toggle(&mut self.dirty_tracker); @@ -49,6 +71,13 @@ impl InputState { self.click_highlight.render(ctx, now); } + /// Render a persistent highlight ring while the highlight tool is active. + pub fn render_highlight_tool_ring(&self, ctx: &CairoContext, x: i32, y: i32) { + if self.highlight_tool_active() { + self.click_highlight.render_tool_ring(ctx, x, y); + } + } + /// Returns the active tool considering overrides and drawing state. pub fn active_tool(&self) -> Tool { if let DrawingState::Drawing { tool, .. } = &self.state { diff --git a/src/input/state/core/menus/lifecycle.rs b/src/input/state/core/menus/lifecycle.rs index 7b2127b9..4ee495a0 100644 --- a/src/input/state/core/menus/lifecycle.rs +++ b/src/input/state/core/menus/lifecycle.rs @@ -56,7 +56,7 @@ impl InputState { let selection = self.selected_shape_ids().to_vec(); if selection.is_empty() { let anchor = self.keyboard_canvas_menu_anchor(); - self.update_pointer_position(anchor.0, anchor.1); + self.update_pointer_position_synthetic(anchor.0, anchor.1); self.open_context_menu(anchor, Vec::new(), ContextMenuKind::Canvas, None); self.pending_menu_hover_recalc = false; self.set_context_menu_focus(None); @@ -75,7 +75,7 @@ impl InputState { }) .unwrap_or(false); let anchor = self.keyboard_shape_menu_anchor(&selection); - self.update_pointer_position(anchor.0, anchor.1); + self.update_pointer_position_synthetic(anchor.0, anchor.1); self.open_context_menu(anchor, selection, ContextMenuKind::Shape, None); self.pending_menu_hover_recalc = false; if !focus_edit || !self.focus_context_menu_command(MenuCommand::EditText) { diff --git a/src/input/state/core/utility/interaction.rs b/src/input/state/core/utility/interaction.rs index dede0d2d..94188326 100644 --- a/src/input/state/core/utility/interaction.rs +++ b/src/input/state/core/utility/interaction.rs @@ -5,6 +5,19 @@ impl InputState { /// Updates the cached pointer location. pub fn update_pointer_position(&mut self, x: i32, y: i32) { self.last_pointer_position = (x, y); + if self.click_highlight.update_tool_ring( + self.highlight_tool_active(), + x, + y, + &mut self.dirty_tracker, + ) { + self.needs_redraw = true; + } + } + + /// Updates the cached pointer location without triggering pointer-driven visuals. + pub fn update_pointer_position_synthetic(&mut self, x: i32, y: i32) { + self.last_pointer_position = (x, y); } /// Updates the undo stack limit for subsequent actions. diff --git a/src/input/state/highlight/settings.rs b/src/input/state/highlight/settings.rs index c920a599..99cfaf5f 100644 --- a/src/input/state/highlight/settings.rs +++ b/src/input/state/highlight/settings.rs @@ -7,6 +7,7 @@ use crate::draw::Color; #[derive(Clone)] pub struct ClickHighlightSettings { pub enabled: bool, + pub show_on_highlight_tool: bool, pub radius: f64, pub outline_thickness: f64, pub duration: Duration, @@ -34,6 +35,7 @@ impl ClickHighlightSettings { }; Self { enabled: false, + show_on_highlight_tool: false, radius: 24.0, outline_thickness: 4.0, duration: Duration::from_millis(750), @@ -62,6 +64,7 @@ impl From<&ClickHighlightConfig> for ClickHighlightSettings { }; ClickHighlightSettings { enabled: cfg.enabled, + show_on_highlight_tool: cfg.show_on_highlight_tool, radius: cfg.radius, outline_thickness: cfg.outline_thickness, duration: Duration::from_millis(cfg.duration_ms), diff --git a/src/input/state/highlight/state/mod.rs b/src/input/state/highlight/state/mod.rs index 79897e19..7795a2fc 100644 --- a/src/input/state/highlight/state/mod.rs +++ b/src/input/state/highlight/state/mod.rs @@ -11,6 +11,7 @@ pub struct ClickHighlightState { settings: ClickHighlightSettings, enabled: bool, highlights: Vec, + tool_ring_bounds: Option, } struct ActiveHighlight { @@ -38,6 +39,7 @@ impl ClickHighlightState { settings, enabled, highlights: Vec::new(), + tool_ring_bounds: None, } } @@ -49,6 +51,26 @@ impl ClickHighlightState { self.settings.use_pen_color } + pub fn show_on_highlight_tool(&self) -> bool { + self.settings.show_on_highlight_tool + } + + pub fn set_show_on_highlight_tool( + &mut self, + enabled: bool, + tool_active: bool, + x: i32, + y: i32, + tracker: &mut DirtyTracker, + ) -> bool { + if self.settings.show_on_highlight_tool == enabled { + return false; + } + self.settings.show_on_highlight_tool = enabled; + let _ = self.update_tool_ring(tool_active, x, y, tracker); + true + } + pub fn toggle(&mut self, tracker: &mut DirtyTracker) -> bool { self.enabled = !self.enabled; if !self.enabled { @@ -94,6 +116,34 @@ impl ClickHighlightState { !self.highlights.is_empty() } + pub fn update_tool_ring( + &mut self, + active: bool, + x: i32, + y: i32, + tracker: &mut DirtyTracker, + ) -> bool { + let should_show = active && self.settings.show_on_highlight_tool; + let new_bounds = if should_show { + Self::bounds_for(&self.settings, x, y) + } else { + None + }; + + if new_bounds == self.tool_ring_bounds { + return false; + } + + if let Some(bounds) = self.tool_ring_bounds { + tracker.mark_rect(bounds); + } + if let Some(bounds) = new_bounds { + tracker.mark_rect(bounds); + } + self.tool_ring_bounds = new_bounds; + true + } + pub fn advance(&mut self, now: Instant, tracker: &mut DirtyTracker) -> bool { if self.highlights.is_empty() { return false; @@ -149,6 +199,23 @@ impl ClickHighlightState { } } + pub fn render_tool_ring(&self, ctx: &cairo::Context, x: i32, y: i32) { + if !self.settings.show_on_highlight_tool { + return; + } + + crate::draw::render_click_highlight( + ctx, + x as f64, + y as f64, + self.settings.radius, + self.settings.outline_thickness, + self.settings.fill_color, + self.settings.outline_color, + 1.0, + ); + } + fn bounds_for(settings: &ClickHighlightSettings, x: i32, y: i32) -> Option { let radius = settings.radius + settings.outline_thickness; let extent = radius.ceil() as i32 + 2; // small padding for anti-aliased edges diff --git a/src/ui/toolbar/apply/mod.rs b/src/ui/toolbar/apply/mod.rs index b72d4a6a..bdb11ad9 100644 --- a/src/ui/toolbar/apply/mod.rs +++ b/src/ui/toolbar/apply/mod.rs @@ -71,6 +71,9 @@ impl InputState { ToolbarEvent::ToggleAllHighlight(enable) => { self.apply_toolbar_toggle_all_highlight(enable) } + ToolbarEvent::ToggleHighlightToolRing(enable) => { + self.apply_toolbar_toggle_highlight_tool_ring(enable) + } ToolbarEvent::ToggleFreeze => self.apply_toolbar_toggle_freeze(), ToolbarEvent::ZoomIn => self.apply_toolbar_zoom_in(), ToolbarEvent::ZoomOut => self.apply_toolbar_zoom_out(), diff --git a/src/ui/toolbar/apply/tools.rs b/src/ui/toolbar/apply/tools.rs index bd5936ca..babbaf22 100644 --- a/src/ui/toolbar/apply/tools.rs +++ b/src/ui/toolbar/apply/tools.rs @@ -83,6 +83,10 @@ impl InputState { } } + pub(super) fn apply_toolbar_toggle_highlight_tool_ring(&mut self, enable: bool) -> bool { + self.set_highlight_tool_ring_enabled(enable) + } + pub(super) fn apply_toolbar_apply_preset(&mut self, slot: usize) -> bool { self.apply_preset(slot) } diff --git a/src/ui/toolbar/events.rs b/src/ui/toolbar/events.rs index 1a13e9c7..2f51fdbe 100644 --- a/src/ui/toolbar/events.rs +++ b/src/ui/toolbar/events.rs @@ -43,6 +43,8 @@ pub enum ToolbarEvent { EnterStickyNoteMode, /// Toggle both highlight tool and click highlight together ToggleAllHighlight(bool), + /// Toggle highlight tool ring visibility while the highlight tool is active + ToggleHighlightToolRing(bool), ToggleFreeze, ZoomIn, ZoomOut, diff --git a/src/ui/toolbar/snapshot.rs b/src/ui/toolbar/snapshot.rs index 6f3a80a8..9adfca2e 100644 --- a/src/ui/toolbar/snapshot.rs +++ b/src/ui/toolbar/snapshot.rs @@ -66,6 +66,7 @@ pub struct ToolbarSnapshot { pub page_count: usize, pub click_highlight_enabled: bool, pub highlight_tool_active: bool, + pub highlight_tool_ring_enabled: bool, /// Whether any highlight feature is active (tool or click) pub any_highlight_active: bool, pub undo_all_delay_ms: u64, @@ -265,6 +266,7 @@ impl ToolbarSnapshot { page_count, click_highlight_enabled: state.click_highlight_enabled(), highlight_tool_active: state.highlight_tool_active(), + highlight_tool_ring_enabled: state.highlight_tool_ring_enabled(), any_highlight_active: state.click_highlight_enabled() || state.highlight_tool_active(), undo_all_delay_ms: state.undo_all_delay_ms, redo_all_delay_ms: state.redo_all_delay_ms, From 08b177ec66ff215c8304d75a9949286a3a282248 Mon Sep 17 00:00:00 2001 From: devmobasa <4170275+devmobasa@users.noreply.github.com> Date: Sat, 24 Jan 2026 23:08:00 +0100 Subject: [PATCH 2/2] update gitignore file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 50c55e48..5f249a8e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # Local agent instructions AGENTS.md +CLAUDE.MD