Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src-tauri/src/point_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ impl Config {
keyboard_scan: self.resolved(crate::scan_preferences::Area::Keyboard),
mouse_scan: self.resolved(crate::scan_preferences::Area::Mouse),
word_prediction: self.word_prediction,
keyboard_wait_after_typing: self.keyboard_wait_after_typing,
panel_avoids_pointer: self.panel_avoids_pointer,
scanner_color: self.resolved(crate::scan_preferences::Area::Point).color,
mode: self.mode,
Expand All @@ -149,7 +148,6 @@ pub struct PointSettings {
pub keyboard_scan: crate::scan_preferences::Resolved,
pub mouse_scan: crate::scan_preferences::Resolved,
pub word_prediction: bool,
pub keyboard_wait_after_typing: bool,
pub panel_avoids_pointer: bool,
pub scanner_color: crate::scanning::ScannerColor,
pub mode: Mode,
Expand Down Expand Up @@ -703,7 +701,10 @@ mod tests {
let restored: Config =
serde_json::from_value(serde_json::to_value(config).unwrap()).unwrap();
assert_eq!(restored.keyboard_wait_after_typing, enabled);
assert_eq!(restored.point().keyboard_wait_after_typing, enabled);
assert_eq!(
restored.point().keyboard_scan.next_scan == crate::scan_preferences::NextScan::Wait,
enabled
);
}
}
#[test]
Expand Down
51 changes: 48 additions & 3 deletions src-tauri/src/point_workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ impl Workflow {
cfg!(target_os = "macos"),
keyboard_options,
)
.with_wait_after_typing(config.keyboard_wait_after_typing),
.with_wait_after_typing(
config.keyboard_scan.next_scan == crate::scan_preferences::NextScan::Wait,
),
keyboard_area: screen,
mouse: crate::scan_mouse::MousePanel::new(mouse_options, 1, 100),
mouse_area: screen,
Expand Down Expand Up @@ -176,7 +178,9 @@ impl Workflow {
cfg!(target_os = "macos"),
self.point.config.keyboard_scan,
)
.with_wait_after_typing(self.point.config.keyboard_wait_after_typing);
.with_wait_after_typing(
self.point.config.keyboard_scan.next_scan == crate::scan_preferences::NextScan::Wait,
);
keyboard.dock = self.dock;
keyboard
}
Expand Down Expand Up @@ -622,7 +626,7 @@ impl Technique for Workflow {
self.move_repeat = None;
self.scroll_repeat = None;
self.pending = None;
self.mouse.restart();
self.mouse.repeat_stopped();
true
}
fn auto_selecting(&self) -> bool {
Expand Down Expand Up @@ -988,6 +992,47 @@ mod tests {
assert!(matches!(session.technique.phase(), Phase::Point(_)));
}

#[test]
fn stopped_mouse_motion_waits_at_the_arrow_where_the_user_chose_to() {
use crate::scan_preferences::{NextScan, StartFrom};
let mut config = Config::default();
config.scan_preferences.mouse.next_scan = Some(NextScan::Wait);
config.scan_preferences.mouse.start_from = Some(StartFrom::Selection);
let screen = Rect {
x: 0.0,
y: 0.0,
width: 1280.0,
height: 720.0,
};
let mut session = Session::new(Workflow::new(config.point(), screen, 1.0).unwrap(), true);
session.action(Action::OpenMouse);
for action in [Action::Next, Action::Next, Action::Select, Action::Next] {
assert_eq!(session.action(action), None);
}
assert!(matches!(
session.action(Action::Select),
Some(Request::MouseMove { dy: 0, .. })
));
assert!(session.technique.switch_pressed());
let status = |session: &Session<Workflow>| {
let mut frame = session.technique.frame();
frame.tiles.pop().unwrap().text
};
assert_eq!(status(&session), "Mouse waiting · Select to continue");
session.tick(5000, false);
assert_eq!(session.action(Action::Select), None);
assert_eq!(status(&session), "Movement · Select →");
// A move that fails starts again from the top.
assert!(session.action(Action::Select).is_some());
session.technique.switch_pressed();
session.action(Action::Select);
session.technique.mouse_repeat_enabled = false;
assert!(session.action(Action::Select).is_some());
session.technique.execution_failed(String::new());
assert_eq!(status(&session), "Mouse action failed · Select to resume");
session.action(Action::Select);
assert_eq!(status(&session), "Movement · Select a row");
}
#[test]
fn mouse_motion_stops_on_press_and_returns_to_first_row() {
let screen = Rect {
Expand Down
130 changes: 128 additions & 2 deletions src-tauri/src/scan_items.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
scan_preferences::{Direction, Pattern, Resolved},
scan_preferences::{Direction, NextScan, Pattern, Resolved, StartFrom},
scan_tree::{Navigator, Node, Selection},
scanning::{Action, Interval},
};
Expand Down Expand Up @@ -30,6 +30,8 @@ pub struct ItemScanner<T> {
policy: Policy,
pending: bool,
revision: u64,
/// Suspended by the user's choice to wait after a selection.
waiting: bool,
}
impl<T: Clone> ItemScanner<T> {
pub fn new(nodes: Vec<Node<T>>, policy: Policy) -> Self {
Expand All @@ -43,6 +45,7 @@ impl<T: Clone> ItemScanner<T> {
policy,
pending: false,
revision: 0,
waiting: false,
}
}
pub fn nodes(rows: &[Vec<T>]) -> Vec<Node<T>> {
Expand Down Expand Up @@ -121,6 +124,38 @@ impl<T: Clone> ItemScanner<T> {
self.interval.reset();
self.cycles = 0;
self.suspended = false;
self.waiting = false;
}
pub fn waiting(&self) -> bool {
self.waiting
}
pub fn stays_at_selection(&self) -> bool {
self.options.start_from == StartFrom::Selection
}
pub fn waits_after_selection(&self) -> bool {
self.options.next_scan == NextScan::Wait && self.options.automatic
}
/// Carries on after a selection that did something, as the user chose.
/// Left alone, a panel starts again from the beginning by itself.
pub fn continue_after_selection(&mut self) {
if self.stays_at_selection() {
self.restart_in_place();
} else {
self.restart();
}
if self.waits_after_selection() {
self.wait();
}
}
/// Starts again from the highlighted item, in the chosen direction.
pub fn restart_in_place(&mut self) {
self.pending = false;
self.forward = self.options.direction == Direction::Forward;
self.restart_interval();
}
pub fn wait(&mut self) {
self.waiting = true;
self.suspended = true;
}
pub fn reset_clock(&mut self) {
self.interval.reset();
Expand Down Expand Up @@ -156,7 +191,9 @@ impl<T: Clone> ItemScanner<T> {
}
if self.suspended {
if action == Action::Select {
if self.policy.resume_at_root {
if self.waiting && self.stays_at_selection() {
self.restart_interval();
} else if self.policy.resume_at_root {
self.restart();
} else {
self.restart_interval();
Expand Down Expand Up @@ -283,6 +320,95 @@ mod tests {
assert!(!scan.advance(499, 500));
}

#[test]
fn after_a_selection_scanning_continues_as_the_user_chose() {
let rows = vec![vec!["copy", "paste"], vec!["save", "close"]];
for next_scan in [NextScan::Standard, NextScan::Automatic, NextScan::Wait] {
for start_from in [
StartFrom::Standard,
StartFrom::Beginning,
StartFrom::Selection,
] {
for automatic in [false, true] {
let mut scan = ItemScanner::configured_rows(
&rows,
Policy::KEYBOARD,
Resolved {
automatic,
next_scan,
start_from,
..Default::default()
},
);
scan.handle(Action::Next);
scan.handle(Action::Select);
scan.handle(Action::Next);
scan.advance(499, 500);
assert_eq!(scan.handle(Action::Select), Some("close"));
scan.continue_after_selection();
let stays = start_from == StartFrom::Selection;
let place = if stays { (1, Some(1)) } else { (0, None) };
assert_eq!(scan.position(&rows), place);
let waits = automatic && next_scan == NextScan::Wait;
assert_eq!((scan.suspended, scan.waiting()), (waits, waits));
if waits {
assert!(!scan.advance(500, 500));
assert_eq!(scan.handle(Action::Next), None);
assert_eq!(scan.handle(Action::Select), None);
assert!(!scan.suspended && !scan.waiting());
assert_eq!(scan.position(&rows), place);
}
assert!(!scan.advance(499, 500));
assert!(scan.advance(1, 500));
}
}
}
}
#[test]
fn staying_at_a_selection_keeps_the_direction_and_pass_limit() {
let rows = vec![vec!["copy", "paste", "undo"], vec!["save", "close"]];
let mut scan = ItemScanner::configured_rows(
&rows,
Policy::KEYBOARD,
Resolved {
start_from: StartFrom::Selection,
pass_limit: 1,
..Default::default()
},
);
scan.handle(Action::Select);
scan.handle(Action::Next);
scan.handle(Action::Next);
scan.handle(Action::Back);
assert_eq!(scan.handle(Action::Select), Some("paste"));
scan.continue_after_selection();
// A step back does not turn automatic scanning around.
assert!(scan.advance(500, 500));
assert_eq!(scan.position(&rows), (0, Some(2)));
// Past the last item and the way out of the row is one pass.
assert!(scan.advance(500, 500));
assert!(scan.nav.escaping() && !scan.suspended);
assert!(scan.advance(500, 500));
assert!(scan.suspended && !scan.waiting());
}
#[test]
fn a_pause_that_was_not_chosen_still_resumes_at_the_start() {
let mut scan = ItemScanner::configured_rows(
&[vec!["copy", "paste"], vec!["save", "close"]],
Policy::KEYBOARD,
Resolved {
next_scan: NextScan::Wait,
start_from: StartFrom::Selection,
..Default::default()
},
);
scan.handle(Action::Next);
scan.handle(Action::Select);
scan.suspended = true;
scan.handle(Action::Select);
assert!(scan.nav.path().is_empty());
assert_eq!(scan.nav.index(), 0);
}
#[test]
fn adapters_preserve_their_existing_resume_policy() {
for policy in [Policy::MENU, Policy::KEYBOARD] {
Expand Down
Loading
Loading