mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
Holding backquote while dragging with the Selection tool (move, bounding-box scale or rotate) or the Rotate, Reflect, Scale or Shear tool applies the drag to the selected objects' pattern fills and strokes and leaves their geometry in place. The key state at release decides the result: the key may be pressed after the mouse button goes down, and releasing it before the button comes up commits an ordinary transform. Objects without pattern paint do not move. PointerEvent gains a `backquote` flag (false when absent). The desktop canvas reads it from the held key on every pointer event, and `ui.pointer` and the `pointer_gesture` tool accept it per event or as a default for the gesture.
131 lines
4.1 KiB
Rust
131 lines
4.1 KiB
Rust
use super::{Deserialize, Point, Serialize, Value};
|
||
|
||
/// Modifier keys.
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub struct Mods {
|
||
#[serde(default)]
|
||
pub shift: bool,
|
||
/// Option on macOS.
|
||
#[serde(default)]
|
||
pub alt: bool,
|
||
/// Command on macOS, Ctrl elsewhere.
|
||
#[serde(default)]
|
||
pub cmd: bool,
|
||
#[serde(default)]
|
||
pub ctrl: bool,
|
||
#[serde(default)]
|
||
pub space: bool,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "lowercase")]
|
||
pub enum PointerKind {
|
||
/// Button pressed.
|
||
Down,
|
||
/// Moved with the button held.
|
||
Drag,
|
||
/// Button released.
|
||
Up,
|
||
/// Moved with no button (hover).
|
||
Move,
|
||
DoubleClick,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct PointerEvent {
|
||
pub kind: PointerKind,
|
||
/// Document coordinates.
|
||
pub pos: Point,
|
||
#[serde(default)]
|
||
pub mods: Mods,
|
||
/// Whether backquote is held for this event.
|
||
#[serde(default)]
|
||
pub backquote: bool,
|
||
#[serde(default = "one")]
|
||
pub pressure: f32,
|
||
}
|
||
|
||
fn one() -> f32 {
|
||
1.0
|
||
}
|
||
|
||
impl PointerEvent {
|
||
pub fn new(kind: PointerKind, x: f64, y: f64) -> Self {
|
||
Self { kind, pos: Point::new(x, y), mods: Mods::default(), backquote: false, pressure: 1.0 }
|
||
}
|
||
pub fn with_mods(mut self, m: Mods) -> Self {
|
||
self.mods = m;
|
||
self
|
||
}
|
||
/// The pen `pressure` of a pointer event given as JSON (control channel, MCP): 0..1, default 1.
|
||
pub fn json_pressure(e: &Value) -> f32 {
|
||
e.get("pressure").and_then(Value::as_f64).filter(|f| f.is_finite()).map_or(1.0, |f| f.clamp(0.0, 1.0) as f32)
|
||
}
|
||
/// How long the pointer then holds still, in seconds, from a JSON pointer event's `holdMs`
|
||
/// (0..60000; default 0): the time [`Tool::tick`] gets.
|
||
pub fn json_hold(e: &Value) -> f64 {
|
||
e.get("holdMs").and_then(Value::as_f64).filter(|f| f.is_finite()).map_or(0.0, |ms| ms.clamp(0.0, 60_000.0) / 1000.0)
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn backquote_json_defaults_false_and_decodes_true() {
|
||
let event = PointerEvent::new(PointerKind::Down, 10.0, 20.0);
|
||
let mut value = serde_json::to_value(event).unwrap();
|
||
value.as_object_mut().unwrap().remove("backquote");
|
||
assert!(!serde_json::from_value::<PointerEvent>(value.clone()).unwrap().backquote);
|
||
value["backquote"] = serde_json::json!(true);
|
||
assert!(serde_json::from_value::<PointerEvent>(value).unwrap().backquote);
|
||
}
|
||
}
|
||
|
||
/// Keys tools care about.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum ToolKey {
|
||
Enter,
|
||
Escape,
|
||
Backspace,
|
||
Delete,
|
||
Up,
|
||
Down,
|
||
Left,
|
||
Right,
|
||
/// Illustrator: ↑/↓ while drawing a polygon/star change sides/points; `[`/`]` change sizes.
|
||
BracketLeft,
|
||
BracketRight,
|
||
Tab,
|
||
Home,
|
||
End,
|
||
/// A digit key 0–9 (5 while dragging with the Perspective Selection tool moves perpendicular
|
||
/// to the plane).
|
||
Digit(u8),
|
||
/// A letter key a–z, lowercase (E while dragging a Selection or Group Selection marquee makes it
|
||
/// select only the objects entirely inside it, and E again switches it back).
|
||
Letter(char),
|
||
}
|
||
|
||
/// What a tool asks the engine to do.
|
||
#[derive(Clone, Debug, PartialEq)]
|
||
pub enum Action {
|
||
/// Start an interaction (snapshot the document). The label becomes the undo name.
|
||
Begin(String),
|
||
/// Replace the current preview with this command, applied to the snapshot.
|
||
Preview(String, Value),
|
||
/// Finish the interaction: keep the last preview as one undo step.
|
||
Commit,
|
||
/// Abort the interaction: restore the snapshot.
|
||
Cancel,
|
||
/// Execute a command immediately (its own undo step, if it edits).
|
||
Exec(String, Value),
|
||
/// Ask the UI to open a dialog (e.g. click with the Rectangle tool → size dialog).
|
||
Dialog(String, Value),
|
||
/// Switch to another tool (e.g. after placing a text frame).
|
||
SwitchTool(String),
|
||
/// Call the tool's `notify` after the preceding actions ran (e.g. to pick up a created object).
|
||
Notify(String),
|
||
}
|