From db22d5ee85a666b0b6257008e5253f993e986f56 Mon Sep 17 00:00:00 2001 From: Owen McGirr Date: Thu, 24 Sep 2026 10:21:50 +0100 Subject: [PATCH 1/2] Put mouse click actions first and scroll beside movement Co-Authored-By: Claude Opus 5.5 (1M context) --- src-tauri/src/scan_mouse.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/scan_mouse.rs b/src-tauri/src/scan_mouse.rs index a1eea663..9b836275 100644 --- a/src-tauri/src/scan_mouse.rs +++ b/src-tauri/src/scan_mouse.rs @@ -53,14 +53,14 @@ impl MousePanel { use Key::*; if !more { vec![ - vec![Move(-1, -1), Move(0, -1), Move(1, -1)], - vec![Move(-1, 0), Click, Move(1, 0)], - vec![Move(-1, 1), Move(0, 1), Move(1, 1)], - vec![RightClick, DoubleClick, Drag], + vec![Click, RightClick, DoubleClick, Drag], + vec![Move(-1, -1), Move(0, -1), Move(1, -1), Scroll(1)], + vec![Move(-1, 0), Move(1, 0)], + vec![Move(-1, 1), Move(0, 1), Move(1, 1), Scroll(-1)], vec![More, Keyboard, Dock, Close], ] } else { - let mut rows = vec![vec![Scroll(1), Scroll(-1)], vec![Speed(-1), Speed(1)]]; + let mut rows = vec![vec![Speed(-1), Speed(1)]]; if displays > 1 { rows.push(vec![ Monitor(-1, 0), @@ -221,18 +221,22 @@ mod tests { #[test] fn pages_and_monitor_controls_follow_available_displays() { let mut panel = MousePanel::new(Resolved::default(), 1, 100); - assert_eq!(panel.rows[1][1], Key::Click); - assert_eq!(panel.rows[0].len(), 3); + assert_eq!( + panel.rows[0], + [Key::Click, Key::RightClick, Key::DoubleClick, Key::Drag] + ); + assert_eq!(panel.rows[1][3], Key::Scroll(1)); + assert_eq!(panel.rows[3][3], Key::Scroll(-1)); panel.choose(Key::More); assert!(!panel .rows .iter() .flatten() - .any(|key| matches!(key, Key::Monitor(..)))); + .any(|key| matches!(key, Key::Monitor(..) | Key::Scroll(..)))); panel.set_displays(2); - assert_eq!(panel.rows[2].len(), 4); + assert_eq!(panel.rows[1].len(), 4); panel.choose(Key::Movement); - assert_eq!(panel.rows[1][1], Key::Click); + assert_eq!(panel.rows[0][0], Key::Click); } #[test] @@ -276,10 +280,9 @@ mod tests { panel.handle(Action::Select); let status = panel.frame(screen, 1.0, color, false).tiles.pop().unwrap(); - assert_eq!(status.text, "Movement · Select ↖"); + assert_eq!(status.text, "Movement · Select Left click"); panel.choose(Key::More); - panel.handle(Action::Next); panel.handle(Action::Select); let status = panel.frame(screen, 1.0, color, false).tiles.pop().unwrap(); assert_eq!(status.text, "More controls · Select Slower 100%"); From 2588ea6ad60b33e19e79a9f17c4d52279999ad7f Mon Sep 17 00:00:00 2001 From: Owen McGirr Date: Thu, 24 Sep 2026 10:25:48 +0100 Subject: [PATCH 2/2] Align horizontal mouse arrows with the diagonal columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Panel rows can reserve blank cells, so ← and → sit under ↖ and ↗ instead of stretching across the Scroll column. Tests pin the full layout and the first row after motion stops. Co-Authored-By: Claude Opus 5.5 (1M context) --- src-tauri/src/point_workflow.rs | 13 ++++--- src-tauri/src/scan_keyboard.rs | 1 + src-tauri/src/scan_mouse.rs | 64 ++++++++++++++++++++++++++++----- src-tauri/src/scan_panel.rs | 23 +++++++++++- 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/src-tauri/src/point_workflow.rs b/src-tauri/src/point_workflow.rs index 4d7974d7..eeefd408 100644 --- a/src-tauri/src/point_workflow.rs +++ b/src-tauri/src/point_workflow.rs @@ -921,14 +921,19 @@ mod tests { session.technique.phase(), Phase::Workflow(WorkflowPhase::Mouse) ); - assert!(session + let selected: Vec<_> = session .technique .mouse .frame(screen, 1.0, Default::default(), false) .tiles - .iter() - .skip(1) - .any(|tile| tile.selected)); + .into_iter() + .filter(|tile| tile.selected) + .map(|tile| tile.text) + .collect(); + assert_eq!( + selected, + ["Left click", "Right click", "Double click", "Start drag"] + ); } #[test] diff --git a/src-tauri/src/scan_keyboard.rs b/src-tauri/src/scan_keyboard.rs index 5efbca00..dfdca2be 100644 --- a/src-tauri/src/scan_keyboard.rs +++ b/src-tauri/src/scan_keyboard.rs @@ -542,6 +542,7 @@ impl Keyboard { weight: Self::weight(key), role: style.role, active: style.active, + blank: false, } }) .collect() diff --git a/src-tauri/src/scan_mouse.rs b/src-tauri/src/scan_mouse.rs index 9b836275..d2a3cd5d 100644 --- a/src-tauri/src/scan_mouse.rs +++ b/src-tauri/src/scan_mouse.rs @@ -194,11 +194,17 @@ impl MousePanel { .iter() .map(|row| { row.iter() - .map(|&key| PanelKey { - text: self.label(key), - weight: 1.0, - role: Self::role(key), - active: key == Key::Drag && self.dragging, + .flat_map(|&key| { + let tile = PanelKey { + text: self.label(key), + weight: 1.0, + role: Self::role(key), + active: key == Key::Drag && self.dragging, + blank: false, + }; + // Keep ← and → under the diagonal arrows. + let blank = matches!(key, Key::Move(_, 0)).then(PanelKey::blank); + std::iter::once(tile).chain(blank) }) .collect() }) @@ -221,12 +227,17 @@ mod tests { #[test] fn pages_and_monitor_controls_follow_available_displays() { let mut panel = MousePanel::new(Resolved::default(), 1, 100); + use Key::*; assert_eq!( - panel.rows[0], - [Key::Click, Key::RightClick, Key::DoubleClick, Key::Drag] + panel.rows, + [ + vec![Click, RightClick, DoubleClick, Drag], + vec![Move(-1, -1), Move(0, -1), Move(1, -1), Scroll(1)], + vec![Move(-1, 0), Move(1, 0)], + vec![Move(-1, 1), Move(0, 1), Move(1, 1), Scroll(-1)], + vec![More, Keyboard, Dock, Close], + ] ); - assert_eq!(panel.rows[1][3], Key::Scroll(1)); - assert_eq!(panel.rows[3][3], Key::Scroll(-1)); panel.choose(Key::More); assert!(!panel .rows @@ -256,6 +267,41 @@ mod tests { .all(|tile| tile.rect.x >= screen.x && tile.rect.y >= screen.y)); } + #[test] + fn horizontal_arrows_align_with_the_diagonals_and_stay_selectable() { + let screen = Rect { + x: 0.0, + y: 0.0, + width: 1920.0, + height: 1080.0, + }; + let mut panel = MousePanel::new(Resolved::default(), 1, 100); + let tile = |frame: &Frame, text: &str| { + frame + .tiles + .iter() + .find(|tile| tile.text == text) + .unwrap() + .rect + }; + let frame = panel.frame(screen, 1.0, ScannerColor::default(), false); + assert_eq!(tile(&frame, "←").x, tile(&frame, "↖").x); + assert_eq!(tile(&frame, "←").width, tile(&frame, "↖").width); + assert_eq!(tile(&frame, "→").x, tile(&frame, "↗").x); + assert_eq!(tile(&frame, "→").width, tile(&frame, "↗").width); + assert_eq!(frame.tiles.len(), 1 + 18 + 1, "spacers draw no tiles"); + + panel.handle(Action::Next); + panel.handle(Action::Next); + panel.handle(Action::Select); + panel.handle(Action::Next); + let frame = panel.frame(screen, 1.0, ScannerColor::default(), false); + let selected: Vec<_> = frame.tiles.iter().filter(|tile| tile.selected).collect(); + assert_eq!(selected.len(), 1); + assert_eq!(selected[0].text, "→"); + assert_eq!(panel.handle(Action::Select), Some(Key::Move(1, 0))); + } + #[test] fn panel_matches_keyboard_geometry_and_reports_its_state() { let screen = Rect { diff --git a/src-tauri/src/scan_panel.rs b/src-tauri/src/scan_panel.rs index cb30dd8e..46d52e86 100644 --- a/src-tauri/src/scan_panel.rs +++ b/src-tauri/src/scan_panel.rs @@ -11,6 +11,20 @@ pub struct PanelKey { pub weight: f64, pub role: TileRole, pub active: bool, + /// Reserves a cell without drawing it or counting it as a scanner column. + pub blank: bool, +} + +impl PanelKey { + pub fn blank() -> Self { + Self { + text: String::new(), + weight: 1.0, + role: TileRole::Utility, + active: false, + blank: true, + } + } } pub struct Panel { @@ -61,8 +75,13 @@ impl Panel { let total_weight: f64 = row.iter().map(|key| key.weight).sum(); let cell_unit = (width - gap * (row.len() - 1) as f64).max(1.0) / total_weight; let mut left = x; - for (c, key) in row.iter().enumerate() { + let mut c = 0; + for key in row { let key_width = cell_unit * key.weight; + if key.blank { + left += key_width + gap; + continue; + } frame.tiles.push(FrameTile { thickness: self.thickness, color, @@ -85,6 +104,7 @@ impl Panel { }), }); left += key_width + gap; + c += 1; } } frame.tiles.push(FrameTile { @@ -130,6 +150,7 @@ mod tests { weight, role: TileRole::Utility, active: false, + blank: false, } }