mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 18:10:01 -06:00
Cursors: Caps Lock gives the drawing tools the crosshair
While Caps Lock is on, the tools whose pointer General > Use Precise Cursors changes show the crosshair, and the saved preference does not change. The desktop app reads the lock state on macOS, Windows and Linux X11 and passes each change to the new view.capsLock command. A change to view.capsLock or usePreciseCursors through app.run refreshes the canvas pointer. The view.preciseCursors command is labeled Use Precise Cursors, so command search finds it by the preference's name.
This commit is contained in:
@@ -9,6 +9,7 @@ use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, f64_req, fi
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
cmd!(query "view.capsLock", "Caps Lock Cursor State", [], None, "{on?: bool} set or read the Caps Lock state the desktop app reports; while it is on, the drawing tools show the precise crosshair, and Use Precise Cursors keeps its saved value → {on}", always, caps_lock),
|
||||
cmd!(
|
||||
"view.saved.new",
|
||||
"New View…",
|
||||
@@ -54,6 +55,13 @@ fn glyph_snap_options(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!(s.prefs.glyph_snap))
|
||||
}
|
||||
|
||||
fn caps_lock(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some(on) = p.get("on") {
|
||||
s.caps_lock = on.as_bool().ok_or_else(|| bad("view.capsLock", "on must be true or false"))?;
|
||||
}
|
||||
Ok(json!({ "on": s.caps_lock }))
|
||||
}
|
||||
|
||||
fn transparency_grid(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let st = s.doc_mut()?;
|
||||
st.transparency_grid = p.get("on").and_then(Value::as_bool).unwrap_or(!st.transparency_grid);
|
||||
|
||||
@@ -48,6 +48,8 @@ pub struct Session {
|
||||
docs: Vec<DocState>,
|
||||
active: Option<usize>,
|
||||
pub prefs: Prefs,
|
||||
/// Whether Caps Lock is on, as the desktop host reports it; never saved with the preferences.
|
||||
pub caps_lock: bool,
|
||||
/// Fill/stroke for new art (the toolbar proxy).
|
||||
pub paint: PaintDefaults,
|
||||
/// Which proxy is in front (true = Fill, false = Stroke) — the X key toggles.
|
||||
|
||||
@@ -28,6 +28,7 @@ impl Session {
|
||||
docs: vec![],
|
||||
active: None,
|
||||
prefs: Prefs::default(),
|
||||
caps_lock: false,
|
||||
paint: PaintDefaults::default(),
|
||||
fill_active: true,
|
||||
clipboard: Clipboard::default(),
|
||||
|
||||
@@ -389,6 +389,31 @@ fn use_precise_cursors_makes_drawing_cursors_crosshairs() {
|
||||
assert_eq!(cursor(&mut s, "eyedropper"), Cursor::Eyedropper);
|
||||
}
|
||||
|
||||
/// Caps Lock gives the drawing tools the precise crosshair while it is on, and leaves the saved
|
||||
/// Use Precise Cursors preference as it was.
|
||||
#[test]
|
||||
fn caps_lock_gives_drawing_tools_the_crosshair() {
|
||||
use vectorcraft_tools::{Cursor, Mods};
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
|
||||
let cursor = |s: &mut Session, tool: &str| {
|
||||
s.select_tool(tool, ViewInfo::default()).unwrap();
|
||||
s.cursor(vectorcraft_geom::Point::new(300.0, 300.0), Mods::default(), ViewInfo::default())
|
||||
};
|
||||
assert_eq!(s.execute("view.capsLock", &json!({})).unwrap(), json!({"on": false}));
|
||||
assert_eq!(s.execute("view.capsLock", &json!({"on": true})).unwrap(), json!({"on": true}));
|
||||
assert!(!s.prefs.use_precise_cursors, "Caps Lock leaves the preference off");
|
||||
assert_eq!(cursor(&mut s, "pen"), Cursor::Crosshair);
|
||||
assert_eq!(cursor(&mut s, "lasso"), Cursor::Crosshair);
|
||||
assert_eq!(cursor(&mut s, "eyedropper"), Cursor::Crosshair);
|
||||
assert_eq!(cursor(&mut s, "selection"), Cursor::Arrow);
|
||||
assert_eq!(s.execute("view.capsLock", &json!({"on": false})).unwrap(), json!({"on": false}));
|
||||
assert_eq!(cursor(&mut s, "pen"), Cursor::Pen);
|
||||
assert_eq!(cursor(&mut s, "eyedropper"), Cursor::Eyedropper);
|
||||
assert!(s.execute("view.capsLock", &json!({"on": 1})).is_err());
|
||||
assert_eq!(s.execute("view.capsLock", &json!({})).unwrap(), json!({"on": false}));
|
||||
}
|
||||
|
||||
/// Use Precise Cursors changes the pointer of the tools that have one of their own: off, each shows its own
|
||||
/// pointer; on, a crosshair. Every other drawing tool shows a crosshair either way, and the Type tool and the
|
||||
/// Scissors over no path keep theirs.
|
||||
|
||||
@@ -604,8 +604,8 @@ impl Session {
|
||||
v
|
||||
}
|
||||
|
||||
/// The pointer the active tool shows at `p` (General › Use Precise Cursors makes the drawing
|
||||
/// tools' a crosshair, [`Cursor::precise`]). With Cmd held, the pointer of the selection tool a
|
||||
/// The pointer the active tool shows at `p` (General › Use Precise Cursors, or Caps Lock while
|
||||
/// it is on, makes the drawing tools' a crosshair, [`Cursor::precise`]). With Cmd held, the pointer of the selection tool a
|
||||
/// press would borrow ([`Self::lend_selection_tool`]).
|
||||
pub fn cursor(&mut self, p: Point, mods: Mods, view: ViewInfo) -> Cursor {
|
||||
let lent = if mods.cmd && self.lender.is_none() { self.lent_tool(self.tool.id(), &mods) } else { None };
|
||||
@@ -619,7 +619,7 @@ impl Session {
|
||||
}
|
||||
None => self.with_tool_cx(view, |t, cx| t.cursor(cx, p, mods)),
|
||||
};
|
||||
if self.prefs.use_precise_cursors { c.precise() } else { c }
|
||||
if self.prefs.use_precise_cursors || self.caps_lock { c.precise() } else { c }
|
||||
}
|
||||
|
||||
pub fn tool_options(&self) -> Value {
|
||||
|
||||
@@ -31,6 +31,16 @@ mod view;
|
||||
pub use fonts::MAX_RECENT_FONTS;
|
||||
|
||||
impl VectorcraftApp {
|
||||
/// Takes the host's Caps Lock state, which gives the drawing tools the crosshair while it is on
|
||||
/// without changing the saved Use Precise Cursors preference.
|
||||
pub fn set_caps_lock(&mut self, on: bool) {
|
||||
if self.session.caps_lock != on
|
||||
&& let Err(err) = self.run("view.capsLock", json!({ "on": on }))
|
||||
{
|
||||
log::error!("Could not update Caps Lock cursor state: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The app around `session`, reaching the platform through `services` (file pickers, the
|
||||
/// clipboard, files and the like). It starts cataloguing the installed fonts at once, gives
|
||||
/// Data Recovery the host's store and makes a view for each open document.
|
||||
|
||||
@@ -92,7 +92,11 @@ impl VectorcraftApp {
|
||||
let ime_view = self.view_info();
|
||||
let ime_target = crate::ime::target(&mut self.session, ime_view);
|
||||
let ime_before = crate::ime::stamp(&mut self.session, ime_view);
|
||||
let cursor_settings = (self.session.caps_lock, self.session.prefs.use_precise_cursors);
|
||||
let r = self.session.execute(id, ¶ms).map_err(|e| e.to_string());
|
||||
if r.is_ok() && cursor_settings != (self.session.caps_lock, self.session.prefs.use_precise_cursors) {
|
||||
self.canvas.hover_cursor = None;
|
||||
}
|
||||
if r.is_ok()
|
||||
&& let Some(ctx) = self.ui_ctx.clone()
|
||||
{
|
||||
|
||||
@@ -75,6 +75,26 @@ fn system_cursors_show_the_same_crosshair_with_precise_cursors_on_or_off() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A change to Caps Lock or to Use Precise Cursors through `app.run` (the host, the control channel,
|
||||
/// MCP) refreshes the pointer over the canvas without the pointer moving.
|
||||
#[test]
|
||||
fn command_cursor_settings_refresh_hover_cursor_while_view_moves() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
app.session.select_tool("pen", vectorcraft_engine::ViewInfo::default()).unwrap();
|
||||
app.hover_doc = Some(Point::new(200.0, 150.0));
|
||||
let mods = Mods::default();
|
||||
let view = vectorcraft_engine::ViewInfo::default();
|
||||
assert_eq!(hover_cursor(&mut app, mods, view, false), Some(Cursor::Pen));
|
||||
|
||||
app.run("view.capsLock", json!({"on": true})).unwrap();
|
||||
assert_eq!(hover_cursor(&mut app, mods, view, false), Some(Cursor::Crosshair));
|
||||
app.run("view.capsLock", json!({"on": false})).unwrap();
|
||||
assert_eq!(hover_cursor(&mut app, mods, view, false), Some(Cursor::Pen));
|
||||
app.run("prefs.set", json!({"key": "usePreciseCursors", "value": true})).unwrap();
|
||||
assert_eq!(hover_cursor(&mut app, mods, view, false), Some(Cursor::Crosshair));
|
||||
}
|
||||
|
||||
/// #826: in a turned view each ruler still has ticks along its whole length; at 0° the top one
|
||||
/// reads x where the canvas has it.
|
||||
#[test]
|
||||
|
||||
@@ -524,7 +524,7 @@ fn precise_cursors_command_toggles_the_preference() {
|
||||
fn precise_cursors_command_is_listed_and_assignable_in_the_shortcut_editor() {
|
||||
use crate::shortcut_editor as ed;
|
||||
let e = ed::entry("view.preciseCursors").expect("listed in the editor");
|
||||
assert_eq!((e.label.as_str(), e.group.as_str(), e.is_tool), ("Precise Cursors", "View", false));
|
||||
assert_eq!((e.label.as_str(), e.group.as_str(), e.is_tool), ("Use Precise Cursors", "View", false));
|
||||
assert_eq!(ed::default_of("view.preciseCursors"), None, "no default key");
|
||||
let mut overrides = craft_keymap::Overrides::new();
|
||||
assert_eq!(ed::effective_in(&overrides, "view.preciseCursors"), None);
|
||||
|
||||
@@ -187,7 +187,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
),
|
||||
(
|
||||
"view.preciseCursors",
|
||||
"Precise Cursors",
|
||||
"Use Precise Cursors",
|
||||
"",
|
||||
"{} toggle General › Use Precise Cursors: the drawing tools' pointers become a crosshair → true when on",
|
||||
),
|
||||
|
||||
@@ -176,6 +176,24 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Searching for the preference's name, Use Precise Cursors, finds the command that toggles it,
|
||||
/// and choosing the first result changes the preference.
|
||||
#[test]
|
||||
fn use_precise_cursors_palette_result_toggles_the_preference() {
|
||||
let ctx = egui::Context::default();
|
||||
let query = "use precise cursors";
|
||||
let entries = entries(&ctx, Lang::EN);
|
||||
let matches: Vec<&Entry> = entries.iter().filter(|e| query.split_whitespace().all(|word| e.haystack.contains(word))).take(14).collect();
|
||||
assert!(matches.iter().any(|e| e.id == "view.preciseCursors"), "preference toggle appears for its name");
|
||||
assert!(entries.iter().filter(|e| e.id == "view.capsLock").all(|e| e.shown != "Use Precise Cursors"));
|
||||
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default());
|
||||
let before = app.session.prefs.use_precise_cursors;
|
||||
let first = matches.first().expect("query has a result");
|
||||
menus::invoke(&mut app, &first.id, json!({}));
|
||||
assert_ne!(app.session.prefs.use_precise_cursors, before, "first result toggles the preference");
|
||||
}
|
||||
|
||||
/// The palette lists `select.key` once, as Cancel Key Object (#541): a pick runs it with no id,
|
||||
/// which cancels the key object. `engine.commands` gives agents the same label.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user