Agent cursor speed: a slider from 1× to 5× with Instant at the far right. Instant no longer breaks: the command runs at once (as with the cursor off) and the pointer jumps to where it acted, with a click ripple. ui.agent_cursor speed takes 1–5 or "instant"; saved "slow" and "normal" read as 1×. Scenario covers 1×, 3× and Instant against the cursor-off result
Fixes #79 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
This commit is contained in:
1 parent
5ec6d3562e
commit
c90070c497
7 files changed
+128
-34
No files matched your search
@@ -21,36 +21,87 @@ use solvecraft_engine::geom::{Vec2, Vec3};
|
|||||||
use crate::SolveApp;
|
use crate::SolveApp;
|
||||||
use crate::control::ControlResponse;
|
use crate::control::ControlResponse;
|
||||||
|
|
||||||
/// How fast the pointer moves.
|
/// How fast the pointer moves: 1× to 5×, or Instant (no animation: the command runs at once
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
/// and the pointer jumps to where it acted, with a click ripple). Saved and sent as a number,
|
||||||
#[serde(rename_all = "lowercase")]
|
/// or "instant"; the old "slow" and "normal" read as 1×.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Speed {
|
pub enum Speed {
|
||||||
/// No animation: the command runs at once.
|
Times(f64),
|
||||||
Instant,
|
Instant,
|
||||||
#[default]
|
}
|
||||||
Normal,
|
|
||||||
Slow,
|
impl Default for Speed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Speed::Times(1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Speed {
|
impl Speed {
|
||||||
pub const ALL: [Speed; 3] = [Speed::Instant, Speed::Normal, Speed::Slow];
|
/// From a number (1–5) or a name ("instant"; "slow" and "normal" are 1×).
|
||||||
pub fn label(self) -> &'static str {
|
pub fn from_value(v: &Value) -> Option<Speed> {
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => n.as_f64().filter(|x| x.is_finite() && (1.0..=5.0).contains(x)).map(Speed::Times),
|
||||||
|
Value::String(s) => match s.to_ascii_lowercase().as_str() {
|
||||||
|
"instant" => Some(Speed::Instant),
|
||||||
|
"slow" | "normal" => Some(Speed::Times(1.0)),
|
||||||
|
"fast" => Some(Speed::Times(2.0)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn label(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Speed::Instant => "Instant",
|
Speed::Instant => "Instant".into(),
|
||||||
Speed::Normal => "Normal",
|
Speed::Times(x) => {
|
||||||
Speed::Slow => "Slow",
|
let s = format!("{x:.1}");
|
||||||
|
format!("{}×", s.trim_end_matches(".0"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Seconds to glide to a stop, and to click there.
|
/// Seconds to glide to a stop, and to click there.
|
||||||
fn times(self) -> (f64, f64) {
|
fn times(self) -> (f64, f64) {
|
||||||
match self {
|
match self {
|
||||||
Speed::Instant => (0.0, 0.0),
|
Speed::Instant => (0.0, 0.0),
|
||||||
Speed::Normal => (0.45, 0.25),
|
Speed::Times(x) => {
|
||||||
Speed::Slow => (0.9, 0.45),
|
let x = x.clamp(1.0, 5.0);
|
||||||
|
(0.45 / x, 0.25 / x)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for Speed {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
match self {
|
||||||
|
Speed::Instant => s.serialize_str("instant"),
|
||||||
|
Speed::Times(x) => s.serialize_f64(*x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Speed {
|
||||||
|
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||||
|
let v = Value::deserialize(d)?;
|
||||||
|
Ok(Speed::from_value(&v).unwrap_or_default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The speed as a slider: 1× to 5× in half steps, and Instant at the far right.
|
||||||
|
pub fn speed_slider(ui: &mut egui::Ui, speed: &mut Speed) -> egui::Response {
|
||||||
|
let mut v = match *speed {
|
||||||
|
Speed::Times(x) => x.clamp(1.0, 5.0),
|
||||||
|
Speed::Instant => 5.5,
|
||||||
|
};
|
||||||
|
let r = ui.add(
|
||||||
|
egui::Slider::new(&mut v, 1.0..=5.5).step_by(0.5).custom_formatter(|v, _| if v > 5.25 { "Instant".into() } else { Speed::Times(v).label() }),
|
||||||
|
);
|
||||||
|
if r.changed() {
|
||||||
|
*speed = if v > 5.25 { Speed::Instant } else { Speed::Times(v) };
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
/// The settings (kept with the other view settings).
|
/// The settings (kept with the other view settings).
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -176,7 +227,7 @@ fn walk<'a>(v: &'a Value, key: Option<&'a str>, f: &mut dyn FnMut(Option<&'a str
|
|||||||
/// back when it should simply run.
|
/// back when it should simply run.
|
||||||
pub fn intercept(app: &mut SolveApp, method: &str, params: &Value, reply: &Sender<ControlResponse>) -> bool {
|
pub fn intercept(app: &mut SolveApp, method: &str, params: &Value, reply: &Sender<ControlResponse>) -> bool {
|
||||||
let s = app.ui.agent_cursor;
|
let s = app.ui.agent_cursor;
|
||||||
if !s.show || s.speed == Speed::Instant || !matches!(method, "engine.execute" | "command") {
|
if !s.show || !matches!(method, "engine.execute" | "command") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if params.get("animate").and_then(Value::as_bool) == Some(false) {
|
if params.get("animate").and_then(Value::as_bool) == Some(false) {
|
||||||
@@ -185,6 +236,14 @@ pub fn intercept(app: &mut SolveApp, method: &str, params: &Value, reply: &Sende
|
|||||||
let Some(cmd) = params.get("command").or(params.get("id")).and_then(Value::as_str) else { return false };
|
let Some(cmd) = params.get("command").or(params.get("id")).and_then(Value::as_str) else { return false };
|
||||||
let inner = params.get("params").cloned().unwrap_or(json!({}));
|
let inner = params.get("params").cloned().unwrap_or(json!({}));
|
||||||
let stops: VecDeque<Pos2> = stops(app, cmd, &inner).into();
|
let stops: VecDeque<Pos2> = stops(app, cmd, &inner).into();
|
||||||
|
if s.speed == Speed::Instant {
|
||||||
|
// No animation: the command runs now; the pointer jumps to where it acted.
|
||||||
|
if let Some(last) = stops.back() {
|
||||||
|
app.agent.pos = Some(*last);
|
||||||
|
app.agent.ripples.push((*last, app.now));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if stops.is_empty() {
|
if stops.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -336,4 +395,25 @@ mod tests {
|
|||||||
// Off-screen places are skipped rather than pointed at outside the view.
|
// Off-screen places are skipped rather than pointed at outside the view.
|
||||||
assert!(stops(&a, "solid.hole", &json!({"position": [1e6, 0, 0]})).iter().all(|p| rect.contains(*p)));
|
assert!(stops(&a, "solid.hole", &json!({"position": [1e6, 0, 0]})).iter().all(|p| rect.contains(*p)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn speeds_read_old_and_new_settings() {
|
||||||
|
// Saved settings from before #79: slow and normal are 1×, instant stays.
|
||||||
|
for (v, want) in [
|
||||||
|
(json!("slow"), Speed::Times(1.0)),
|
||||||
|
(json!("normal"), Speed::Times(1.0)),
|
||||||
|
(json!("instant"), Speed::Instant),
|
||||||
|
(json!(3), Speed::Times(3.0)),
|
||||||
|
] {
|
||||||
|
assert_eq!(Speed::from_value(&v), Some(want), "{v}");
|
||||||
|
let s: Settings = serde_json::from_value(json!({"show": true, "speed": v})).unwrap();
|
||||||
|
assert_eq!(s.speed, want);
|
||||||
|
}
|
||||||
|
assert_eq!(Speed::from_value(&json!(9)), None);
|
||||||
|
assert_eq!(serde_json::to_value(Speed::Instant).unwrap(), json!("instant"));
|
||||||
|
assert_eq!(serde_json::to_value(Speed::Times(2.5)).unwrap(), json!(2.5));
|
||||||
|
assert_eq!((Speed::Times(1.0).label(), Speed::Times(2.5).label(), Speed::Instant.label()), ("1×".into(), "2.5×".into(), "Instant".into()));
|
||||||
|
// Faster glides take less time.
|
||||||
|
assert!(Speed::Times(5.0).times().0 < Speed::Times(1.0).times().0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
//! - `ui.prefs {...}`: the preferences (default_units, nav, zoom_reverse, orbit_cursor, msaa,
|
//! - `ui.prefs {...}`: the preferences (default_units, nav, zoom_reverse, orbit_cursor, msaa,
|
||||||
//! length_decimals, angle_decimals); returns them all
|
//! length_decimals, angle_decimals); returns them all
|
||||||
//! - `ui.capture {action?: capture|revert|cancel}`: the Capture Position question
|
//! - `ui.capture {action?: capture|revert|cancel}`: the Capture Position question
|
||||||
//! - `ui.agent_cursor {show?, speed?: instant|normal|slow, follow_camera?}`: the agent cursor
|
//! - `ui.agent_cursor {show?, speed?: 1–5 (times) | "instant", follow_camera?}`: the agent cursor
|
||||||
//! (#35); while it is shown, `engine.execute` first glides to the command's button and picks
|
//! (#35); while it is shown, `engine.execute` first glides to the command's button and picks
|
||||||
//! and replies once the command has run (`"animate": false` skips that one call)
|
//! and replies once the command has run (`"animate": false` skips that one call)
|
||||||
//! - `ui.resize {width, height}`, `app.quit`
|
//! - `ui.resize {width, height}`, `app.quit`
|
||||||
@@ -107,9 +107,9 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
|||||||
s.follow_camera = v;
|
s.follow_camera = v;
|
||||||
}
|
}
|
||||||
if let Some(v) = p.get("speed") {
|
if let Some(v) = p.get("speed") {
|
||||||
match serde_json::from_value(v.clone()) {
|
match crate::agent_cursor::Speed::from_value(v) {
|
||||||
Ok(sp) => s.speed = sp,
|
Some(sp) => s.speed = sp,
|
||||||
Err(_) => return err("speed must be instant, normal or slow"),
|
None => return err("speed must be a number from 1 to 5, or \"instant\""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ok(json!(app.ui.agent_cursor))
|
ok(json!(app.ui.agent_cursor))
|
||||||
|
|||||||
@@ -507,11 +507,7 @@ fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
|||||||
ui.end_row();
|
ui.end_row();
|
||||||
ui.label("Agent cursor speed");
|
ui.label("Agent cursor speed");
|
||||||
ui.add_enabled_ui(s.show, |ui| {
|
ui.add_enabled_ui(s.show, |ui| {
|
||||||
ui.horizontal(|ui| {
|
crate::agent_cursor::speed_slider(ui, &mut s.speed);
|
||||||
for sp in crate::agent_cursor::Speed::ALL {
|
|
||||||
ui.selectable_value(&mut s.speed, sp, sp.label());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
ui.label("");
|
ui.label("");
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
//! - `{"click": AT, "double"?, "shift"?, "ctrl"?, "button"?}`, `{"move": AT}`,
|
//! - `{"click": AT, "double"?, "shift"?, "ctrl"?, "button"?}`, `{"move": AT}`,
|
||||||
//! `{"drag": [AT, AT], "shift"?, "ctrl"?, "steps"?}` where AT is `[x, y]` (screen), `{"world": [x,y,z]}`
|
//! `{"drag": [AT, AT], "shift"?, "ctrl"?, "steps"?}` where AT is `[x, y]` (screen), `{"world": [x,y,z]}`
|
||||||
//! or `{"sketch": [x,y]}` (the sketch being edited)
|
//! or `{"sketch": [x,y]}` (the sketch being edited)
|
||||||
//! - `{"queued": "engine.execute", "params": {…}, "min_frames"?: n}`: a request sent the way an
|
//! - `{"queued": "engine.execute", "params": {…}, "min_frames"?: n, "max_frames"?: n}`: a request sent the way an
|
||||||
//! agent's arrive (through the app's request queue, answered between frames, so the agent
|
//! agent's arrive (through the app's request queue, answered between frames, so the agent
|
||||||
//! cursor can act it out first); fails if the answer came in fewer than `min_frames` frames
|
//! cursor can act it out first); fails if the answer came in fewer than `min_frames` frames
|
||||||
//! - `{"call": "ui.view", "params": {…}}`: any other control request (`fail: true` expects an
|
//! - `{"call": "ui.view", "params": {…}}`: any other control request (`fail: true` expects an
|
||||||
@@ -375,6 +375,11 @@ impl Harness {
|
|||||||
if (frames as u64) < min {
|
if (frames as u64) < min {
|
||||||
return Err(format!("{m} was answered after {frames} frames: the agent cursor did not act it out ({min})"));
|
return Err(format!("{m} was answered after {frames} frames: the agent cursor did not act it out ({min})"));
|
||||||
}
|
}
|
||||||
|
if let Some(max) = s.get("max_frames").and_then(Value::as_u64)
|
||||||
|
&& frames as u64 > max
|
||||||
|
{
|
||||||
|
return Err(format!("{m} was answered after {frames} frames, more than {max}"));
|
||||||
|
}
|
||||||
v
|
v
|
||||||
} else if let Some(f) = s.get("script").and_then(Value::as_str) {
|
} else if let Some(f) = s.get("script").and_then(Value::as_str) {
|
||||||
// A command script (`{"commands": [{command, params}]}`, path from the repository
|
// A command script (`{"commands": [{command, params}]}`, path from the repository
|
||||||
|
|||||||
@@ -2275,9 +2275,7 @@ fn view_menu(app: &mut SolveApp, ui: &mut egui::Ui, button: Rect) -> bool {
|
|||||||
ui.add_enabled_ui(s.show, |ui| {
|
ui.add_enabled_ui(s.show, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Speed");
|
ui.label("Speed");
|
||||||
for sp in crate::agent_cursor::Speed::ALL {
|
crate::agent_cursor::speed_slider(ui, &mut s.speed);
|
||||||
ui.selectable_value(&mut s.speed, sp, sp.label());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
ui.checkbox(&mut s.follow_camera, "Follow camera (Look At sketches)");
|
ui.checkbox(&mut s.follow_camera, "Follow camera (Look At sketches)");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
[
|
[
|
||||||
{"note": "#35: with the agent cursor on, an agent's commands are acted out (the reply waits for the pointer) and give the same result as with it off"},
|
{"note": "#35, #79: with the agent cursor on, an agent's commands are acted out (the reply waits for the pointer) at 1x and 3x; at Instant they run at once. Each speed builds the same body as the cursor being off."},
|
||||||
{"call": "ui.agent_cursor", "params": {"show": true, "speed": "normal", "follow_camera": true}},
|
{"call": "ui.agent_cursor", "params": {"show": true, "speed": 1, "follow_camera": true}},
|
||||||
{"queued": "engine.execute", "params": {"command": "sketch.create", "params": {"plane": "XY"}}, "min_frames": 30},
|
{"queued": "engine.execute", "params": {"command": "sketch.create", "params": {"plane": "XY"}}, "min_frames": 30},
|
||||||
{"queued": "engine.execute", "params": {"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [40, 25]}}, "min_frames": 45},
|
{"queued": "engine.execute", "params": {"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [40, 25]}}, "min_frames": 45},
|
||||||
{"queued": "engine.execute", "params": {"command": "sketch.finish", "params": {}}, "min_frames": 30},
|
{"queued": "engine.execute", "params": {"command": "sketch.finish", "params": {}}, "min_frames": 30},
|
||||||
{"queued": "engine.execute", "params": {"command": "solid.extrude", "params": {"distance": 10, "operation": "new"}}, "min_frames": 30},
|
{"queued": "engine.execute", "params": {"command": "solid.extrude", "params": {"distance": 10, "operation": "new"}}, "min_frames": 30},
|
||||||
{"expect": {"errors": 0, "bodies": 1, "volume": [10000, 1e-6]}},
|
{"expect": {"errors": 0, "bodies": 1, "volume": [10000, 1e-6]}},
|
||||||
|
{"note": "3x: still acted out, about three times as fast"},
|
||||||
|
{"call": "ui.agent_cursor", "params": {"speed": 3}},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "sketch.create", "params": {"plane": "XY"}}, "min_frames": 8, "max_frames": 45},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [40, 25]}}, "min_frames": 8},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "sketch.finish", "params": {}}, "min_frames": 8},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "solid.extrude", "params": {"distance": 10, "operation": "new"}}, "min_frames": 8},
|
||||||
|
{"expect": {"errors": 0, "bodies": 2, "volume": [20000, 1e-6]}},
|
||||||
|
{"note": "Instant: answered on the next frame, no animation"},
|
||||||
|
{"call": "ui.agent_cursor", "params": {"speed": "instant"}},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "sketch.create", "params": {"plane": "XY"}}, "max_frames": 2},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [40, 25]}}, "max_frames": 2},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "sketch.finish", "params": {}}, "max_frames": 2},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "solid.extrude", "params": {"distance": 10, "operation": "new"}}, "max_frames": 2},
|
||||||
|
{"expect": {"errors": 0, "bodies": 3, "volume": [30000, 1e-6]}},
|
||||||
{"note": "A call with animate: false runs at once"},
|
{"note": "A call with animate: false runs at once"},
|
||||||
{"queued": "engine.execute", "params": {"command": "sketch.create", "params": {"plane": "XY"}, "animate": false}},
|
{"call": "ui.agent_cursor", "params": {"speed": 1}},
|
||||||
|
{"queued": "engine.execute", "params": {"command": "sketch.create", "params": {"plane": "XY"}, "animate": false}, "max_frames": 2},
|
||||||
{"call": "ui.agent_cursor", "params": {"show": false}},
|
{"call": "ui.agent_cursor", "params": {"show": false}},
|
||||||
{"note": "The same steps with the cursor off: a second, identical body"},
|
{"note": "The same steps with the cursor off: a fourth, identical body"},
|
||||||
{"queued": "engine.execute", "params": {"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [40, 25]}}},
|
{"queued": "engine.execute", "params": {"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [40, 25]}}},
|
||||||
{"queued": "engine.execute", "params": {"command": "sketch.finish", "params": {}}},
|
{"queued": "engine.execute", "params": {"command": "sketch.finish", "params": {}}},
|
||||||
{"queued": "engine.execute", "params": {"command": "solid.extrude", "params": {"distance": 10, "operation": "new"}}},
|
{"queued": "engine.execute", "params": {"command": "solid.extrude", "params": {"distance": 10, "operation": "new"}}},
|
||||||
{"expect": {"errors": 0, "bodies": 2, "volume": [20000, 1e-6]}}
|
{"expect": {"errors": 0, "bodies": 4, "volume": [40000, 1e-6]}}
|
||||||
]
|
]
|
||||||
@@ -52,7 +52,7 @@ Commands never open dialogs when run this way. `solvecraft-cli commands` prints
|
|||||||
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`), `zoom_reverse`, `zoom_to_cursor`, `orbit_cursor`, `look_at_anim`, `look_at_speed`, `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `ui_scale`, `up_axis` (`z`, `y`), `offer_stand_up`; `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`), `zoom_reverse`, `zoom_to_cursor`, `orbit_cursor`, `look_at_anim`, `look_at_speed`, `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `ui_scale`, `up_axis` (`z`, `y`), `offer_stand_up`; `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
||||||
| `ui.capture` | `action?`: `capture`, `revert`, `cancel` | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without `action`, what waits |
|
| `ui.capture` | `action?`: `capture`, `revert`, `cancel` | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without `action`, what waits |
|
||||||
| `ui.window` | `action`: minimize, maximize, restore, toggle, close | what the title bar's caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first |
|
| `ui.window` | `action`: minimize, maximize, restore, toggle, close | what the title bar's caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first |
|
||||||
| `ui.agent_cursor` | `show` (bool), `speed`: `instant`, `normal`, `slow`, `follow_camera` (bool); all optional | the settings. While the cursor is shown, `engine.execute` first glides to the command's toolbar button and to each pick, then runs and replies; later requests wait their turn. `"animate": false` on a call skips it |
|
| `ui.agent_cursor` | `show` (bool), `speed`: a number from 1 to 5 (times the normal speed) or `"instant"` (no animation: the command runs at once and the pointer jumps to where it acted), `follow_camera` (bool); all optional | the settings. While the cursor is shown, `engine.execute` first glides to the command's toolbar button and to each pick, then runs and replies; later requests wait their turn. `"animate": false` on a call skips it |
|
||||||
| `ui.resize` | `width`, `height` | |
|
| `ui.resize` | `width`, `height` | |
|
||||||
| `app.quit` | `force?` (quit without asking about unsaved designs) | `quitting` (the window closes), `asking` (a "save changes?" prompt is open) |
|
| `app.quit` | `force?` (quit without asking about unsaved designs) | `quitting` (the window closes), `asking` (a "save changes?" prompt is open) |
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user