mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-29 04:46:50 -06:00
579 lines
20 KiB
Rust
579 lines
20 KiB
Rust
//! Pointer tools. The UI (or an agent through the control channel) feeds [`PointerEvent`]s in page
|
|
//! coordinates; tools turn gestures into document edits through interactions (one undo step per
|
|
//! gesture) and publish overlay hints (marquee, guides, size label, cursor) for the canvas.
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use figcraft_color::{ImageScaleMode, PaintKind};
|
|
use figcraft_doc::geometry::select_target;
|
|
use figcraft_doc::{Document, LayoutMode, Node, NodeId, NodeKind, Sizing, StrokeAlign, StrokeCap, TextAutoResize, TextData, TextPath, VectorNetwork};
|
|
use figcraft_geom::{Affine, Point, Rect, Vec2};
|
|
use serde::Serialize;
|
|
use serde_json::json;
|
|
|
|
use crate::arc_handles::{ArcHandle, ArcHint};
|
|
use crate::cmd::arrange::move_world;
|
|
use crate::cmd::create::frame_at;
|
|
use crate::cmd::edit::roots;
|
|
use crate::shape_handles::{ShapeHandle, ShapeHint};
|
|
use crate::snap::{Guide, Targets};
|
|
use crate::{Result, Session, textedit};
|
|
|
|
mod click;
|
|
mod drag;
|
|
mod draw;
|
|
mod handles;
|
|
mod keys;
|
|
mod pen;
|
|
mod pointer;
|
|
|
|
use handles::DRAG_SLOP_PX;
|
|
#[cfg(test)]
|
|
pub(crate) use handles::scale_factor;
|
|
pub use handles::{
|
|
PENCIL_STROKE, SMALL_BOX_PX, anchor_name, anchor_of, box_sides_px, handle_at, handle_at_corners, label_at, locked_in, scale_anchor_point,
|
|
selection_box, selection_frame,
|
|
};
|
|
pub(crate) use handles::{SNAP_PX, handle_cursor, readable_text_fills, resized_box};
|
|
#[cfg(test)]
|
|
pub(crate) use pen::pressure_profile;
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Default)]
|
|
pub enum ToolId {
|
|
#[default]
|
|
Move,
|
|
Scale,
|
|
Hand,
|
|
Frame,
|
|
Section,
|
|
Slice,
|
|
Rectangle,
|
|
Line,
|
|
Arrow,
|
|
Ellipse,
|
|
Polygon,
|
|
Star,
|
|
Pen,
|
|
Pencil,
|
|
Text,
|
|
TextOnPath,
|
|
Comment,
|
|
Eyedropper,
|
|
ShapeBuilder,
|
|
// Board tools.
|
|
Sticky,
|
|
BoardShape,
|
|
Connector,
|
|
Marker,
|
|
Highlighter,
|
|
Eraser,
|
|
Stamp,
|
|
}
|
|
|
|
impl ToolId {
|
|
pub const ALL: [ToolId; 26] = [
|
|
ToolId::Move,
|
|
ToolId::Scale,
|
|
ToolId::Hand,
|
|
ToolId::Frame,
|
|
ToolId::Section,
|
|
ToolId::Slice,
|
|
ToolId::Rectangle,
|
|
ToolId::Line,
|
|
ToolId::Arrow,
|
|
ToolId::Ellipse,
|
|
ToolId::Polygon,
|
|
ToolId::Star,
|
|
ToolId::Pen,
|
|
ToolId::Pencil,
|
|
ToolId::Text,
|
|
ToolId::TextOnPath,
|
|
ToolId::Comment,
|
|
ToolId::Eyedropper,
|
|
ToolId::ShapeBuilder,
|
|
ToolId::Sticky,
|
|
ToolId::BoardShape,
|
|
ToolId::Connector,
|
|
ToolId::Marker,
|
|
ToolId::Highlighter,
|
|
ToolId::Eraser,
|
|
ToolId::Stamp,
|
|
];
|
|
pub fn id(self) -> &'static str {
|
|
match self {
|
|
ToolId::Move => "move",
|
|
ToolId::Scale => "scale",
|
|
ToolId::Hand => "hand",
|
|
ToolId::Frame => "frame",
|
|
ToolId::Section => "section",
|
|
ToolId::Slice => "slice",
|
|
ToolId::Rectangle => "rectangle",
|
|
ToolId::Line => "line",
|
|
ToolId::Arrow => "arrow",
|
|
ToolId::Ellipse => "ellipse",
|
|
ToolId::Polygon => "polygon",
|
|
ToolId::Star => "star",
|
|
ToolId::Pen => "pen",
|
|
ToolId::Pencil => "pencil",
|
|
ToolId::Text => "text",
|
|
ToolId::TextOnPath => "textOnPath",
|
|
ToolId::Comment => "comment",
|
|
ToolId::Eyedropper => "eyedropper",
|
|
ToolId::ShapeBuilder => "shapeBuilder",
|
|
ToolId::Sticky => "sticky",
|
|
ToolId::BoardShape => "shape",
|
|
ToolId::Connector => "connector",
|
|
ToolId::Marker => "marker",
|
|
ToolId::Highlighter => "highlighter",
|
|
ToolId::Eraser => "eraser",
|
|
ToolId::Stamp => "stamp",
|
|
}
|
|
}
|
|
pub fn from_id(s: &str) -> Option<ToolId> {
|
|
ToolId::ALL.into_iter().find(|t| t.id().eq_ignore_ascii_case(s))
|
|
}
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
ToolId::Move => "Move",
|
|
ToolId::Scale => "Scale",
|
|
ToolId::Hand => "Hand tool",
|
|
ToolId::Frame => "Frame",
|
|
ToolId::Section => "Section",
|
|
ToolId::Slice => "Slice",
|
|
ToolId::Rectangle => "Rectangle",
|
|
ToolId::Line => "Line",
|
|
ToolId::Arrow => "Arrow",
|
|
ToolId::Ellipse => "Ellipse",
|
|
ToolId::Polygon => "Polygon",
|
|
ToolId::Star => "Star",
|
|
ToolId::Pen => "Pen",
|
|
ToolId::Pencil => "Pencil",
|
|
ToolId::Text => "Text",
|
|
ToolId::TextOnPath => "Text on a path",
|
|
ToolId::Comment => "Comment",
|
|
ToolId::Eyedropper => "Pick color",
|
|
ToolId::ShapeBuilder => "Shape builder",
|
|
ToolId::Sticky => "Sticky note",
|
|
ToolId::BoardShape => "Shapes",
|
|
ToolId::Connector => "Connector",
|
|
ToolId::Marker => "Marker",
|
|
ToolId::Highlighter => "Highlighter",
|
|
ToolId::Eraser => "Eraser",
|
|
ToolId::Stamp => "Stamp",
|
|
}
|
|
}
|
|
|
|
/// The tools of a board's toolbar (a design file's toolbar has the others).
|
|
pub fn on_boards(self) -> bool {
|
|
matches!(
|
|
self,
|
|
ToolId::Sticky | ToolId::BoardShape | ToolId::Connector | ToolId::Marker | ToolId::Highlighter | ToolId::Eraser | ToolId::Stamp
|
|
)
|
|
}
|
|
/// The tool's key (from [`crate::keymap`]; empty when it has none).
|
|
pub fn shortcut(self) -> &'static str {
|
|
let id = self.id();
|
|
crate::keymap::KEYS
|
|
.iter()
|
|
.find(|b| if self == ToolId::Eyedropper { b.id == "edit.pickColor" } else { b.id.strip_prefix("tool.") == Some(id) })
|
|
.and_then(|b| b.keys(crate::keymap::Platform::current()).first().copied())
|
|
.unwrap_or("")
|
|
}
|
|
|
|
/// The size a click (or Enter, `tool.place`) makes with a shape or frame tool.
|
|
pub fn click_size(self) -> (f64, f64) {
|
|
match self {
|
|
ToolId::Line | ToolId::Arrow => (100.0, 0.0),
|
|
ToolId::Section => (400.0, 300.0),
|
|
_ => (100.0, 100.0),
|
|
}
|
|
}
|
|
|
|
pub fn draws_shape(self) -> bool {
|
|
matches!(
|
|
self,
|
|
ToolId::Frame
|
|
| ToolId::Section
|
|
| ToolId::Slice
|
|
| ToolId::Rectangle
|
|
| ToolId::Ellipse
|
|
| ToolId::Polygon
|
|
| ToolId::Star
|
|
| ToolId::Line
|
|
| ToolId::Arrow
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
|
pub enum PointerKind {
|
|
Down,
|
|
Move,
|
|
Up,
|
|
/// Pointer moved without a button.
|
|
Hover,
|
|
DoubleClick,
|
|
}
|
|
|
|
/// A pointer event in page coordinates (and screen coordinates for panning).
|
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
|
pub struct PointerEvent {
|
|
pub kind: PointerKind,
|
|
pub x: f64,
|
|
pub y: f64,
|
|
/// Screen position (logical pixels).
|
|
pub sx: f64,
|
|
pub sy: f64,
|
|
pub shift: bool,
|
|
pub alt: bool,
|
|
/// Cmd on macOS, Ctrl elsewhere.
|
|
pub cmd: bool,
|
|
pub ctrl: bool,
|
|
/// Screen pixels per page unit.
|
|
pub zoom: f64,
|
|
/// Pen pressure, 0..1, where the device reports it (`None`: a mouse, or a platform that doesn't).
|
|
pub pressure: Option<f32>,
|
|
}
|
|
|
|
/// The pointer is still within [`DRAG_SLOP_PX`] of where it was pressed.
|
|
fn within_slop(ev: &PointerEvent, start: Point) -> bool {
|
|
(ev.p() - start).hypot() * ev.zoom.max(1e-6) < DRAG_SLOP_PX
|
|
}
|
|
|
|
impl PointerEvent {
|
|
pub fn at(kind: PointerKind, x: f64, y: f64, zoom: f64) -> Self {
|
|
Self { kind, x, y, sx: x * zoom, sy: y * zoom, shift: false, alt: false, cmd: false, ctrl: false, zoom, pressure: None }
|
|
}
|
|
pub(crate) fn p(&self) -> Point {
|
|
Point::new(self.x, self.y)
|
|
}
|
|
fn tol(&self) -> f64 {
|
|
1.0 / self.zoom.max(1e-6)
|
|
}
|
|
}
|
|
|
|
/// Cursor hint for the UI.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Default)]
|
|
pub enum Cursor {
|
|
#[default]
|
|
Default,
|
|
Crosshair,
|
|
Move,
|
|
/// Resize along an angle (degrees, 0 = horizontal).
|
|
Resize(f64),
|
|
/// Rotate, turned to the corner it is near: the direction from the box's centre to the corner
|
|
/// (while rotating: to the pointer), in degrees, 0 = right and 90 = down.
|
|
Rotate(f64),
|
|
Grab,
|
|
Grabbing,
|
|
Text,
|
|
Pen(PenCursor),
|
|
/// A drag here makes a copy (Alt).
|
|
Copy,
|
|
/// Over a clickable part of a widget.
|
|
Pointer,
|
|
Eyedropper,
|
|
Comment,
|
|
/// What the gesture is doing will be undone when it ends (an edit only a main component can
|
|
/// make).
|
|
NotAllowed,
|
|
}
|
|
|
|
/// What a click with the pen would do.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Default)]
|
|
pub enum PenCursor {
|
|
/// A new point.
|
|
#[default]
|
|
New,
|
|
/// On a point: closes the path, connects to the point or goes on from it.
|
|
Close,
|
|
/// On a segment: adds a point there.
|
|
Add,
|
|
/// Bends the segment or the point's handles (the Bend tool; Cmd/Ctrl in vector edit mode).
|
|
Bend,
|
|
}
|
|
|
|
impl ToolId {
|
|
/// The tool's cursor over empty canvas.
|
|
pub fn cursor(self) -> Cursor {
|
|
match self {
|
|
ToolId::Hand => Cursor::Grab,
|
|
ToolId::Pen => Cursor::Pen(PenCursor::New),
|
|
ToolId::Comment => Cursor::Comment,
|
|
ToolId::Eyedropper => Cursor::Eyedropper,
|
|
// Text on a path acts only over a path, where it shows the I-beam.
|
|
ToolId::Move | ToolId::Scale | ToolId::TextOnPath => Cursor::Default,
|
|
// The other tools make something where they are pressed: shapes, frames, slices, pencil
|
|
// strokes, a new text (over a text the Text tool shows the I-beam instead).
|
|
_ => Cursor::Crosshair,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Selection handle kinds: corners (0 tl, 1 tr, 2 br, 3 bl), edges (0 t, 1 r, 2 b, 3 l) and the
|
|
/// rotation zones outside the corners.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
|
pub enum Handle {
|
|
Corner(u8),
|
|
Edge(u8),
|
|
Rotate(u8),
|
|
}
|
|
|
|
/// What the canvas should draw on top of the scene.
|
|
#[derive(Clone, Debug, Default, Serialize)]
|
|
pub struct Overlay {
|
|
pub marquee: Option<[f64; 4]>,
|
|
pub guides: Vec<Guide>,
|
|
pub cursor: Cursor,
|
|
/// Pen tool: the in-progress path's anchor points and the rubber band to the pointer.
|
|
pub pen: Vec<[f64; 2]>,
|
|
pub rubber: Option<[f64; 4]>,
|
|
/// Pen tool: the handles shaping the segments at the pen (anchor x, y, handle x, y): the last
|
|
/// anchor's in and out handles and the one before's out handle.
|
|
pub pen_handles: Vec<[f64; 4]>,
|
|
/// Pen tool: where the last anchor's out handle ends, when it has one: the rubber band is the
|
|
/// curve a click would make, leaving along it.
|
|
pub rubber_handle: Option<[f64; 2]>,
|
|
/// Frame/section the pointer is drawing or dropping into (highlighted).
|
|
pub drop_target: Option<NodeId>,
|
|
/// Auto layout insertion indicator (x0, y0, x1, y1).
|
|
pub insert_line: Option<[f64; 4]>,
|
|
/// The instance an Alt-drop of a component from the Assets panel would swap (highlighted).
|
|
pub swap_target: Option<NodeId>,
|
|
/// Auto layout grid cell a drag would drop into (x0, y0, x1, y1).
|
|
pub drop_cell: Option<[f64; 4]>,
|
|
/// Vector edit mode: what the pointer is over, the lasso being drawn and the closed area the
|
|
/// paint bucket would fill (page-space polygons).
|
|
pub vector_hover: Option<crate::vectoredit::VHit>,
|
|
/// The edited layer `vector_hover` is on (several layers can be in edit mode).
|
|
pub vector_hover_on: Option<NodeId>,
|
|
/// Vector edit mode: the point a press on the outline would add (the pen's ghost anchor where
|
|
/// it is over a segment, or the midpoint of the segment under the Move tool).
|
|
pub vector_add: Option<VectorAdd>,
|
|
/// Vector edit mode, Variable width tool: the width point, handle or path under the pointer.
|
|
pub width_hover: Option<crate::vectoredit::WHit>,
|
|
pub lasso: Vec<[f64; 2]>,
|
|
/// The eraser brush under the pointer (x, y, radius in page units).
|
|
pub brush: Option<[f64; 3]>,
|
|
pub face: Vec<Vec<[f64; 2]>>,
|
|
/// Shape builder: the regions under the pointer or collected by the drag (indexes into
|
|
/// [`crate::shapebuilder::current`]) and whether releasing removes them.
|
|
pub builder_regions: Vec<usize>,
|
|
pub builder_remove: bool,
|
|
/// Measuring (Alt): the box of the layer or frame measured to (x0, y0, x1, y1), outlined.
|
|
pub measure_box: Option<[f64; 4]>,
|
|
/// Scale tool drag: the factor so far and the point that stays put.
|
|
pub scale: Option<ScaleHint>,
|
|
/// The selected ellipse's arc handle under the pointer, and the readout while one is dragged
|
|
/// (see [`crate::arc_handles`]).
|
|
pub arc_hover: Option<ArcHandle>,
|
|
pub arc: Option<ArcHint>,
|
|
/// The selected shape's corner radius or star ratio handle under the pointer, and the readout
|
|
/// while one is dragged (see [`crate::shape_handles`]).
|
|
pub shape_hover: Option<ShapeHandle>,
|
|
pub shape: Option<ShapeHint>,
|
|
}
|
|
|
|
/// A point a press would add to the edited vector's outline: page position, whether it is a
|
|
/// segment's midpoint (the Move tool) rather than the pen's place on its segment, and whether the
|
|
/// pointer is on it (a press adds it; otherwise it just shows where one would go).
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
|
pub struct VectorAdd {
|
|
pub at: [f64; 2],
|
|
pub midpoint: bool,
|
|
pub on: bool,
|
|
}
|
|
|
|
/// The Scale tool's live readout while dragging a handle.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
|
pub struct ScaleHint {
|
|
pub factor: f64,
|
|
pub pivot: [f64; 2],
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct ResizeStart {
|
|
/// Node id → (world transform, w, h) at the start.
|
|
nodes: Vec<(NodeId, Affine, f64, f64)>,
|
|
/// The handle box: page transform of its top-left corner (axes along the box) and its size.
|
|
frame: (Affine, f64, f64),
|
|
/// One layer (resized in place) rather than several (mapped from the old box to the new).
|
|
single: bool,
|
|
handle: Handle,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub(crate) enum Gesture {
|
|
#[default]
|
|
None,
|
|
Marquee {
|
|
start: Point,
|
|
/// Shift: toggle what it touches in the selection it started from (`base`).
|
|
additive: bool,
|
|
base: Vec<NodeId>,
|
|
within: Option<NodeId>,
|
|
},
|
|
Move {
|
|
start: Point,
|
|
ids: Vec<NodeId>,
|
|
targets: Targets,
|
|
duplicate: bool,
|
|
moved: bool,
|
|
/// A Shift-press on this selected layer: it leaves the selection on release, unless the
|
|
/// press became a drag.
|
|
deselect: Option<NodeId>,
|
|
},
|
|
Resize {
|
|
start: Point,
|
|
r: Box<ResizeStart>,
|
|
scale_tool: bool,
|
|
moved: bool,
|
|
},
|
|
Rotate {
|
|
center: Point,
|
|
start_angle: f64,
|
|
ids: Vec<NodeId>,
|
|
},
|
|
Draw {
|
|
start: Point,
|
|
id: Option<NodeId>,
|
|
parent: NodeId,
|
|
targets: Targets,
|
|
},
|
|
Pan {
|
|
sx: f64,
|
|
sy: f64,
|
|
cx: f64,
|
|
cy: f64,
|
|
},
|
|
Pencil {
|
|
points: Vec<Point>,
|
|
/// The pen pressure at each point, where the device reports it.
|
|
pressure: Vec<Option<f32>>,
|
|
},
|
|
TextSelect {
|
|
id: NodeId,
|
|
},
|
|
/// A new text from the Text tool, still pressed: a drag makes it a box the size of the drag
|
|
/// (at least `line`, the height of its first line, tall).
|
|
TextBox {
|
|
id: NodeId,
|
|
start: Point,
|
|
line: f64,
|
|
},
|
|
PenDrag {
|
|
anchor: Point,
|
|
},
|
|
/// Vector edit mode gestures (handled in `vectoredit`).
|
|
Vector(crate::vectoredit::VGesture),
|
|
/// Dragging an ellipse's arc handle: the arc so far (see [`crate::arc_handles`]).
|
|
Arc {
|
|
id: NodeId,
|
|
handle: ArcHandle,
|
|
arc: crate::arc_handles::Arc,
|
|
},
|
|
/// Dragging a shape's corner radius or star ratio handle (see [`crate::shape_handles`]).
|
|
Shape {
|
|
id: NodeId,
|
|
handle: ShapeHandle,
|
|
},
|
|
/// Dragging the rotation origin handle (see [`crate::rotation_origin`]).
|
|
Origin,
|
|
/// The connector tool: dragging from `from` (pressed at `start`).
|
|
Connect {
|
|
from: figcraft_doc::End,
|
|
start: Point,
|
|
},
|
|
/// Dragging the selected connector `id`'s end (`from`: its start, pressed at `start`) to
|
|
/// attach it elsewhere.
|
|
Reattach {
|
|
id: NodeId,
|
|
from: bool,
|
|
start: Point,
|
|
},
|
|
/// The eraser: the strokes it took so far.
|
|
Erase {
|
|
gone: Vec<NodeId>,
|
|
},
|
|
/// Shape builder: regions collected so far (handled in `shapebuilder`).
|
|
ShapeBuild {
|
|
picked: Vec<usize>,
|
|
remove: bool,
|
|
start: Point,
|
|
last: Point,
|
|
},
|
|
}
|
|
|
|
/// Where Shift puts the Pen's next point: as far from `last` as `p` is, at the nearest 45° step.
|
|
fn at_45_degrees(last: Point, p: Point) -> Point {
|
|
let v = p - last;
|
|
let ang = (v.atan2() / (std::f64::consts::PI / 4.0)).round() * std::f64::consts::PI / 4.0;
|
|
last + Vec2::from_angle(ang) * v.hypot()
|
|
}
|
|
|
|
/// The handles a pen path shows while it is drawn ([`Overlay::pen_handles`]): the last anchor's in
|
|
/// and out handles and the one before's out handle, those that aren't zero.
|
|
fn pen_handles(points: &[(Point, Vec2, Vec2)]) -> Vec<[f64; 4]> {
|
|
let last = points.last().into_iter().flat_map(|(p, i, o)| [(p, i), (p, o)]);
|
|
let before = points.len().checked_sub(2).and_then(|k| points.get(k)).into_iter().map(|(p, _, o)| (p, o));
|
|
before.chain(last).filter(|(_, h)| h.hypot() > 0.0).map(|(p, h)| [p.x, p.y, p.x + h.x, p.y + h.y]).collect()
|
|
}
|
|
|
|
/// The pen tool's in-progress path (page coordinates).
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct PenPath {
|
|
pub id: Option<NodeId>,
|
|
pub parent: Option<NodeId>,
|
|
/// Anchors with (in, out) handle offsets.
|
|
pub points: Vec<(Point, Vec2, Vec2)>,
|
|
pub closed: bool,
|
|
/// The path ends connected to this anchor of its own (a vector network).
|
|
pub join: Option<usize>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct ToolState {
|
|
pub tool: ToolId,
|
|
pub(crate) gesture: Gesture,
|
|
pub overlay: Overlay,
|
|
pub pen: Option<PenPath>,
|
|
/// Tool to return to after a temporary tool (space → hand).
|
|
pub temp_from: Option<ToolId>,
|
|
/// Last click (for double-click detection by agents that only send downs).
|
|
pub last_hover: Option<NodeId>,
|
|
/// Shape builder: the cached arrangement of the selection, and Remove mode (a plain click
|
|
/// removes; Alt merges).
|
|
pub shape_builder: Option<std::sync::Arc<crate::shapebuilder::Arrangement>>,
|
|
pub builder_remove: bool,
|
|
/// Dev Mode: the canvas inspects. Hovering shows distances from the selection without Alt, and
|
|
/// presses select, pan and comment but never edit the design (no moving, resizing, drawing,
|
|
/// or editing text or vectors).
|
|
pub dev_mode: bool,
|
|
/// Pen snap targets, kept while hovering (see `vectoredit::point_snap`).
|
|
pub(crate) pen_targets: Option<crate::vectoredit::point_snap::PenTargets>,
|
|
/// The Scale tool's anchor: column and row (0 start, 1 middle, 2 end) of the point of the
|
|
/// selection box that stays put (`tool.scaleAnchor`).
|
|
pub scale_anchor: (u8, u8),
|
|
/// The last drag move handled (a held button repeats it every frame).
|
|
pub(crate) last_move: Option<PointerEvent>,
|
|
/// The name labels of top-level frames and sections as the canvas last drew them (each label's
|
|
/// corners on the page, turned with its frame): the Move and Scale tools treat a press or
|
|
/// hover on one as on its frame.
|
|
pub labels: Vec<(NodeId, [Point; 4])>,
|
|
/// The brush the Pencil draws with (`tool.pencilBrush`; `None`: a plain stroke).
|
|
pub pencil_brush: Option<figcraft_doc::Brush>,
|
|
/// The board tools' colours and choices.
|
|
pub board: crate::board::BoardTool,
|
|
/// The eyedropper samples for a colour picker (`edit.pickColor` with `apply: false`): the
|
|
/// colour goes to `picked`, for the picker to write to the paint it edits, not to the fill.
|
|
pub pick_only: bool,
|
|
/// The colour the eyedropper sampled for a colour picker, until the picker takes it.
|
|
pub picked: Option<figcraft_color::Color>,
|
|
}
|
|
|
|
impl ToolState {
|
|
pub fn busy(&self) -> bool {
|
|
!matches!(self.gesture, Gesture::None)
|
|
}
|
|
}
|