use super::*; /// Paint defaults for new art (the fill/stroke proxy) and the rest of the new-art template. #[derive(Clone, Debug, PartialEq)] pub struct PaintDefaults { pub fill: Paint, pub stroke: Paint, pub stroke_width: f64, /// The appearance new art takes (stroke options, more fills and strokes, effects), its top /// fill and stroke repainted with `fill`, `stroke` and `stroke_width`; `None`: one plain fill /// and stroke. Its placed gradients are relative to the unit box. pub appearance: Option, /// New art's opacity and blend mode. pub opacity: f32, pub blend: vectorcraft_color::BlendMode, /// The graphic style the template came from (by name): new art is linked to it. pub style: Option, /// The template is the last selection's appearance (New Art Has Basic Appearance off): with /// the option on again, new art takes only its basic fill and stroke. pub inherited: bool, } impl Default for PaintDefaults { /// White fill, 1 pt black stroke, nothing else. fn default() -> Self { Self { fill: Paint::solid(vectorcraft_color::Color::WHITE), stroke: Paint::solid(vectorcraft_color::Color::BLACK), stroke_width: 1.0, appearance: None, opacity: 1.0, blend: Default::default(), style: None, inherited: false, } } } /// The document window on screen, in document coordinates: widgets that stay put on screen (the /// Plane Switching Widget) are placed with it. #[derive(Clone, Copy, Debug, PartialEq)] pub struct ScreenFrame { /// The window's top-left corner. pub origin: Point, /// One screen pixel to the right and one down. pub right: Vec2, pub down: Vec2, /// The window's size in screen pixels. pub size: (f64, f64), } impl ScreenFrame { /// The document point `x`, `y` screen pixels from the window's top-left corner. pub fn at(&self, x: f64, y: f64) -> Point { self.origin + self.right * x + self.down * y } /// Where the document point `p` is, in screen pixels from the window's top-left corner (the /// inverse of [`Self::at`]); none for a degenerate frame. pub fn to_screen(&self, p: Point) -> Option<(f64, f64)> { let (r, d, v) = (self.right, self.down, p - self.origin); let det = r.x * d.y - d.x * r.y; (det.is_finite() && det.abs() > 1e-12).then(|| ((v.x * d.y - d.x * v.y) / det, (r.x * v.y - v.x * r.y) / det)) } } /// Read-only context a tool sees. pub struct ToolContext<'a> { pub doc: &'a Document, /// The rulers' zero point with the view's active artboard (see /// [`Document::ruler_origin_for`]): where ruler ticks and measured positions count from. pub ruler_origin: Point, /// Which open document `doc` is (its process-unique id) and its revision, which moves with /// every change of the document or the selection: what a tool keeps between pointer events /// for the document as it is (the snap targets of hovering) is keyed on it. pub revision: (u64, u64), pub selection: &'a Selection, /// Screen pixels per document point. pub zoom: f64, pub isolation: Option, pub paint: &'a PaintDefaults, pub outline: bool, pub smart_guides: bool, /// View → Show Guides with Lock Guides off: the selection tools pick and drag ruler guides. pub guides: bool, pub snap_to_grid: bool, /// Bounding box shown (View → Show/Hide Bounding Box). pub show_bbox: bool, /// View → Snap to Pixel. pub snap_to_pixel: bool, /// View → Snap to Point: with Smart Guides off, dragged selections, drawn points and picked /// points (a transform's reference point) land on anchors and ruler guides within /// [`Self::snap_tolerance`]. pub snap_to_point: bool, /// View → Snapping → Snap to Tangent: with Smart Guides on, a line drawn /// from an anchor lands where it touches a nearby curve. pub snap_tangent: bool, /// View → Snapping → Snap to Perpendicular: likewise where it meets a nearby segment square. pub snap_perpendicular: bool, /// View → Snap to Glyph: drawn points snap to live-text baselines and glyph bounds (with Smart Guides). pub snap_to_glyph: bool, /// View → Show Corner Widget: paths show draggable Live Corners widgets in their corners. pub corner_widgets: bool, /// Which paint proxy is in front (true = Fill): the one the Gradient tool edits. pub fill_active: bool, /// The selected gradient stop (`gradient.selectStop`), marked on the gradient annotator. pub gradient_stop: Option, /// The Appearance panel's active fill/stroke (paint-order index into the first selected /// object's stack); paint tools show and edit that item. pub appearance_item: Option, /// General → Constrain Angle (degrees): Shift constrains drags to 45° steps from it. pub constrain_angle: f64, /// General → Keyboard Increment: arrow-key nudges in document units (Shift multiplies by 10). pub keyboard_increment: f64, /// The selected freeform gradient point (`paint.freeform.selectPoint`), marked on the /// freeform annotator. pub freeform_point: Option, /// Eyedropper Options' raster sample size: the pixels square averaged when the Eyedropper /// samples an image (1, 3 or 5). pub raster_sample: u32, /// General → Use Preview Bounds: the bounding box measures visual bounds (strokes included). pub preview_bounds: bool, /// The session's memo of the selection tools' measurements for the document as it is /// ([`select::PreviewMemo`]); without one they measure on every call. pub preview_memo: Option<&'a select::PreviewMemo>, /// Units ▸ General: the unit measurement labels show lengths in. pub unit: Unit, /// Units ▸ Stroke: the unit measurement labels show stroke widths in. pub stroke_unit: Unit, /// Clipboard Handling → When pasting text: Keep Plain Text. Text pasted while typing takes /// the style at the caret, even the text the Type tool copied with its formatting. pub paste_plain_text: bool, /// View → Hide Slices: the Slice Selection tool can't pick hidden slices. pub slices_hidden: bool, /// View → Lock Slices: the Slice Selection tool leaves locked slices alone. pub slices_locked: bool, /// General → Disable Auto Add/Delete is off: the Pen adds an anchor on a selected path's /// segment and deletes one of its anchors. pub auto_add_delete: bool, /// Selection & Anchor Display → Tolerance: how near (screen pixels) a click must be to a path /// or an anchor to pick it. pub selection_tolerance: f64, /// Selection & Anchor Display → Size (1–7, default 3): how big anchors and handles are drawn. pub anchor_size: u32, /// Selection & Anchor Display → Object Selection by Path Only: a click inside a filled path /// doesn't pick it, only one on its path does. pub path_only: bool, /// Type → Type Object Selection by Path Only: type is picked on its type path only (point /// type's baseline, area type's frame, type on a path's path), not anywhere in its bounds. pub type_path_only: bool, /// General → Double Click To Isolate: a double-click on a group with the Selection tool /// isolates it. pub double_click_isolate: bool, /// Selection & Anchor Display → Command Click to Select Objects Behind: Cmd/Ctrl-click with the /// Selection tool selects the object under the selected one, the next click the one under /// that. pub select_behind: bool, /// Selection & Anchor Display → Highlight anchors on mouse over: Direct Selection marks the /// anchor under the pointer. pub highlight_anchors: bool, /// Selection & Anchor Display → Snap to Point (screen pixels): how near an anchor or a ruler /// guide pulls the pointer while View → Snap to Point is on. pub snap_tolerance: f64, /// Selection & Anchor Display → Show handles when multiple anchors are selected: off, Direct /// Selection shows and drags handles only while a single anchor is selected. pub handles_multiple: bool, /// Selection & Anchor Display → Hide Corner Widget for angles greater than (degrees): corners /// wider than this show no Live Corners widget. pub corner_widget_max_angle: f64, /// Selection & Anchor Display → Move Locked and Hidden Artwork with Artboard. pub move_locked_with_artboard: bool, /// Selection & Anchor Display → Enable Rubber Band for Pen Tool: the Pen previews the next /// segment to the pointer. pub pen_rubber_band: bool, /// Selection & Anchor Display → Enable Rubber Band for Curvature Tool. pub curvature_rubber_band: bool, /// Type → Fill New Type Objects With Placeholder Text: type the Type tools place starts with /// placeholder text, selected. pub placeholder_text: bool, /// Smart Guides → Color: the smart guides' lines and labels (RGB). pub smart_guide_color: [u8; 3], /// Smart Guides → Alignment Guides: the lines along the edges and centres the art lines up /// with show. Off, the art still snaps into line. pub alignment_guides: bool, /// Smart Guides → Anchor/Path Labels: the "anchor", "center", "path"… labels show. pub anchor_path_labels: bool, /// Smart Guides → Measurement Labels: the size and offset readouts while drawing and moving. pub measurement_labels: bool, /// Smart Guides → Transform Tools: the readouts while scaling, rotating and shearing. pub transform_tools_guides: bool, /// Smart Guides → Spacing Guides: a moved selection snaps to spacing equal to the gap between /// two other objects in its row or column, or evenly between its two neighbours, and the /// equal gaps show. pub spacing_guides: bool, /// Smart Guides → Invoke Distance Guides: with the Selection tool, Alt/Option held over another /// object shows how far the selection is from it, and over an empty part of an artboard how /// far it is from the artboard's edges ([`guides::distance_guides`]). pub distance_guides: bool, /// Smart Guides → Snapping Tolerance (screen pixels): how near a smart guide target pulls the /// pointer, a dragged edge or a drawn point. pub snapping_tolerance: f64, /// Smart Guides → Construction Guides: the angles (degrees, counter-clockwise from the x axis /// as seen on the page) of the lines through the point a drawn point leaves from, onto which a /// point near one lands ([`guides::construction_angles`]); none with the option off. pub construction_angles: &'static [f64], /// The document window (none headless): screen-fixed widgets sit in it. pub screen: Option, /// Where the Plane Switching Widget sits (Perspective Grid Options); None while it's hidden. pub plane_widget: Option, } impl ToolContext<'_> { /// Tolerance in document units for `px` screen pixels. pub fn tol(&self, px: f64) -> f64 { px / self.zoom.max(1e-9) } /// How far the view is turned (Rotate View), in degrees clockwise on screen; 0 without a /// window. pub fn view_angle(&self) -> f64 { // `right` is one screen pixel to the right, in document units. self.screen.map(|s| (-s.right.y).atan2(s.right.x).to_degrees()).filter(|a| a.is_finite()).unwrap_or(0.0) } /// How near (document units) a smart guide target pulls: Smart Guides → Snapping Tolerance. pub fn snap_tol(&self) -> f64 { self.tol(self.snapping_tolerance) } /// A length as measurement labels show it, in the General unit (`12.50 mm`). pub fn len(&self, v: f64) -> String { self.unit.readout(v) } /// A size label: `W: …` over `H: …`. pub fn size_label(&self, w: f64, h: f64) -> String { format!("W: {}\nH: {}", self.len(w), self.len(h)) } /// Star tool drag: outer (R1) and inner (R2) radii from the centre to the pointer. pub fn star_radii_label(&self, outer: f64, inner: f64) -> String { format!("R1: {}\nR2: {}", self.len(outer), self.len(inner)) } /// A move label: `dX: …` over `dY: …`. pub fn offset_label(&self, dx: f64, dy: f64) -> String { format!("dX: {}\ndY: {}", self.len(dx), self.len(dy)) } /// A segment label while drawing: distance, angle and offsets between two points. pub fn segment_measure_label(&self, a: Point, b: Point) -> String { let (dist, ang, dx, dy) = xform::measure(a, b); format!("D: {}\nAngle: {ang:.1}°\n{}", self.len(dist), self.offset_label(dx, dy)) } /// What Snap to Grid snaps to: the grid's subdivisions (or its lines without any). pub fn grid_step(&self) -> f64 { self.doc.grid.spacing / self.doc.grid.subdivisions.max(1) as f64 } /// The selection tolerance in document units ([`Self::selection_tolerance`]). pub fn pick_tol(&self) -> f64 { self.tol(self.selection_tolerance) } /// How near (document units) a press picks an anchor or a direction handle's end: the /// selection tolerance, but at least 2 px past the drawn point, so a point is easy to catch /// and wins over the segments through it, which are tested after it (#593). pub fn point_tol(&self) -> f64 { // Anchors are drawn 5 px wide at the default Size 3, a pixel more or less per step. let half = (5.0 + f64::from(self.anchor_size.clamp(1, 7)) - 3.0) / 2.0; self.tol(self.selection_tolerance.max(half + 2.0)) } pub fn hit_options(&self) -> vectorcraft_doc::hit::HitOptions { vectorcraft_doc::hit::HitOptions { tol: self.pick_tol(), outline: self.outline, path_only: self.path_only, type_path_only: self.type_path_only, scope: self.isolation, skip_guides: !self.guides, clip_outlines: false, } } /// [`Self::hit_options`] for the selection tools (Selection, Direct Selection, Group /// Selection): a clipping path is also hit on its outline ([`HitOptions::clip_outlines`]). /// Only for what a press or a hover selects: a double-click, loaded text or a guide's release /// acts on what is drawn and takes [`Self::hit_options`]. /// /// [`HitOptions::clip_outlines`]: vectorcraft_doc::hit::HitOptions::clip_outlines pub fn selection_hit_options(&self) -> vectorcraft_doc::hit::HitOptions { vectorcraft_doc::hit::HitOptions { clip_outlines: true, ..self.hit_options() } } }