Smart Guides: Alt over another object or the artboard shows the selection's distances to it

Distance Guides, from the public "Use Distance Guides for accurate
placement" and "Smart Guides options" (Invoke Distance Guides) pages:
with Smart Guides on, something selected and the Selection tool,
holding Alt/Option over another object shows the horizontal and
vertical distances between the selection and it, and over an empty
part of an artboard the selection's distances from the artboard's left,
right, top and bottom edges.

- guides::distance_guides measures the selection's bounds to a box: on
  an axis on which they lie apart, the gap between their facing edges;
  otherwise the offsets of both pairs of matching edges. Each line runs
  across the middle of the overlap on the other axis (else the
  selection's middle), with a dashed line out to the target's edge when
  it passes beside it. An artboard is always measured edge to edge.
  Labels are in the General unit, in the smart guide colour.
- The Selection tool keeps what its Alt hover points at and draws the
  guides while idle; letting Alt go (the canvas re-hovers on a modifier
  change), a press or moving off clears them.
- Overlay::Distance: the line with end ticks and its length in a pill;
  the canvas draws it.
- Preferences › Smart Guides › Invoke Distance Guides (`distanceGuides`,
  on by default), with i18n rows; View › Smart Guides gates it too.
- Hover only, as the pages describe: nothing while moving or nudging.

Tests: select::tests::alt_over_* (gap and edges beside and below,
artboard edges in pt and mm), distance_guides_need_alt_a_selection_and_
smart_guides, tests_prefs::invoke_distance_guides_measures_the_selection_
to_the_object_under_alt, canvas::tests::alt_over_another_object_draws_
the_distance_guide_label (frame test of the drawn label and line).
This commit is contained in:
Taha-Mahmoodi
2026-10-24 03:09:09 +07:00
committed by bflatastic
parent bfc2d6e439
commit a301760aa6
36 changed files with 389 additions and 4 deletions
+1
View File
@@ -234,6 +234,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[
p!("anchorPathLabels", "Smart Guides", "Display Options", "Anchor/Path Labels", bool),
p!("measurementLabels", "Smart Guides", "Display Options", "Measurement Labels", bool),
p!("spacingGuides", "Smart Guides", "Display Options", "Spacing Guides", bool),
p!("distanceGuides", "Smart Guides", "Display Options", "Invoke Distance Guides", bool),
p!("snappingTolerance", "Smart Guides", "", "Snapping Tolerance", num(0.0, 40.0, "pt")),
p!("snapTangent", "Smart Guides", "Geometric Guides", "Snap to Tangent", bool),
p!("snapPerpendicular", "Smart Guides", "Geometric Guides", "Snap to Perpendicular", bool),
+2
View File
@@ -970,6 +970,7 @@ pub struct Prefs {
pub anchor_path_labels: bool,
pub measurement_labels: bool,
pub spacing_guides: bool,
pub distance_guides: bool,
pub snapping_tolerance: f64,
pub snap_tangent: bool,
pub snap_perpendicular: bool,
@@ -1221,6 +1222,7 @@ impl Default for Prefs {
anchor_path_labels: true,
measurement_labels: true,
spacing_guides: true,
distance_guides: true,
snapping_tolerance: 4.0,
snap_tangent: true,
snap_perpendicular: true,
+24
View File
@@ -616,6 +616,30 @@ fn spacing_guides_space_a_dragged_square_like_its_row() {
assert_eq!(drag(&mut s), (161.0, 0), "off: where it was dragged");
}
/// Smart Guides › Invoke Distance Guides: with a square selected, Alt held over another square to
/// its right shows the gap between them in the document's General unit; with the preference or
/// View › Smart Guides off, nothing.
#[test]
fn invoke_distance_guides_measures_the_selection_to_the_object_under_alt() {
use vectorcraft_tools::{Mods, Overlay, PointerKind::Move};
let mut s = new_doc();
let a = square(&mut s, 100.0, 100.0);
square(&mut s, 250.0, 100.0);
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
let v = ViewInfo::default();
s.select_tool("selection", v).unwrap();
let shown = |s: &mut Session, v: ViewInfo| {
s.pointer(&PointerEvent::new(Move, 275.0, 125.0).with_mods(Mods { alt: true, ..Mods::default() }), v).unwrap();
s.overlays(v).into_iter().filter_map(|o| if let Overlay::Distance { text, .. } = o { Some(text) } else { None }).collect::<Vec<_>>()
};
assert_eq!(shown(&mut s, v), ["100.00 pt"], "the 100 pt gap; the tops and bottoms are level");
s.execute("document.setUnits", &json!({"units": "Millimeters"})).unwrap();
assert_eq!(shown(&mut s, v), ["35.28 mm"]);
assert!(shown(&mut s, ViewInfo { smart_guides: false, ..v }).is_empty(), "View › Smart Guides off");
set_pref(&mut s, "distanceGuides", json!(false));
assert!(shown(&mut s, v).is_empty(), "Invoke Distance Guides off");
}
/// Preferences › Smart Guides (#394): Color, Alignment Guides, Anchor/Path Labels, Measurement
/// Labels, Transform Tools and Snapping Tolerance change what the Selection tool shows while a
/// square is dragged into line with another, and how far the pull reaches; a hidden guide still
+1
View File
@@ -282,6 +282,7 @@ impl Session {
measurement_labels: self.prefs.measurement_labels,
transform_tools_guides: self.prefs.transform_tools_guides,
spacing_guides: self.prefs.spacing_guides,
distance_guides: self.prefs.distance_guides,
snapping_tolerance: self.prefs.snapping_tolerance,
construction_angles: if self.prefs.construction_guides {
vectorcraft_tools::guides::construction_angles(&self.prefs.construction_angles)
+43
View File
@@ -1574,6 +1574,49 @@ pub fn snap_pick(cx: &ToolContext, p: Point) -> (Point, Vec<Overlay>) {
Targets::points_near(cx.doc, &roots, near).styled(cx).snap_point(p, tol)
}
/// Distance Guides (Smart Guides → Invoke Distance Guides): how far the selection's bounds `from`
/// lie from `to` along each axis, as measured lines ([`Overlay::Distance`]) labelled with their
/// lengths in the General unit. Along an axis on which the two lie apart, one line spans the gap
/// between their facing edges; along one on which they overlap, or one holds the other, one line
/// runs between their left (top) edges and one between their right (bottom) edges. An artboard
/// (`artboard`) is always measured edge to edge: the selection's distance from its left, right,
/// top and bottom edges. Each line runs across the middle of where the two overlap on the other
/// axis, else across the selection's middle, with a dashed line from `to`'s edge out to it when it
/// passes beside `to`. Lines of no length are left out.
pub fn distance_guides(cx: &ToolContext, from: Rect, to: Rect, artboard: bool) -> Vec<Overlay> {
let finite = |r: &Rect| [r.x0, r.y0, r.x1, r.y1].iter().all(|v| v.is_finite());
if !finite(&from) || !finite(&to) {
return vec![];
}
let color = cx.smart_guide_color;
let mut out = Vec::new();
for x in [true, false] {
let ((f0, f1), (t0, t1)) = (span(&from, x), span(&to, x));
let ((g0, g1), (u0, u1)) = (span(&from, !x), span(&to, !x));
let (lo, hi) = (g0.max(u0), g1.min(u1));
let across = if lo <= hi { (lo + hi) / 2.0 } else { (g0 + g1) / 2.0 };
let pt = |u: f64, c: f64| if x { Point::new(u, c) } else { Point::new(c, u) };
// Each measured line: from the selection's edge to `to`'s.
let lines: Vec<(f64, f64)> = match (artboard, f1 <= t0, t1 <= f0) {
(false, true, _) => vec![(f1, t0)],
(false, _, true) => vec![(f0, t1)],
_ => vec![(f0, t0), (f1, t1)],
};
for (fu, tu) in lines {
let length = (tu - fu).abs();
if length <= 1e-9 {
continue;
}
out.push(Overlay::Distance { a: pt(fu, across), b: pt(tu, across), text: cx.len(length), color });
let on_edge = across.clamp(u0, u1);
if on_edge != across {
out.push(Overlay::Line { a: pt(tu, on_edge), b: pt(tu, across), color, dashed: true });
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
+8
View File
@@ -352,6 +352,10 @@ pub struct ToolContext<'a> {
/// 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,
@@ -455,6 +459,9 @@ pub enum Overlay {
Label { p: Point, text: String, color: [u8; 3] },
/// Measurement pill near the cursor (grey box with white text).
Measure { p: Point, text: String },
/// A Distance Guides measurement: a line from `a` to `b` with a tick across each end, and
/// `text` (its length) in a pill of `color` on its middle.
Distance { a: Point, b: Point, text: String, color: [u8; 3] },
/// Translucent filled quad (text selection highlight), RGBA.
Highlight { quad: [Point; 4], color: [u8; 4] },
/// A colour chip of fixed screen size (a gradient stop), RGBA; ringed when selected.
@@ -1047,6 +1054,7 @@ pub(crate) mod testutil {
measurement_labels: true,
transform_tools_guides: true,
spacing_guides: true,
distance_guides: true,
snapping_tolerance: 4.0,
construction_angles: guides::construction_angles(guides::DEFAULT_CONSTRUCTION_ANGLES),
screen: None,
+184 -1
View File
@@ -22,6 +22,9 @@
//! path, area type or type on a path under the pointer, or into a new frame of the same kind, shape
//! and size at the click on empty canvas,
//! and a drag draws a rectangle for the new frame (`text.thread.add`). Escape drops the loaded text.
//! With Smart Guides and Invoke Distance Guides on, 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 ([`crate::guides::distance_guides`]).
use std::cell::RefCell;
use std::collections::HashMap;
@@ -33,7 +36,7 @@ use vectorcraft_geom::{Affine, Point, Rect, Vec2};
use crate::bbox::{Handle, hit_handle, in_rotate_zone, move_delta, rotate_for_drag, scale_for_drag};
use crate::corners::{self, CornerDrag, over_widget};
use crate::guides::{PointSnap, Targets, hold_on_path};
use crate::guides::{PointSnap, Targets, distance_guides, hold_on_path};
use crate::pathtype::{self, BracketDrag, over_bracket};
use crate::pie::{self, PieDrag};
use crate::rulerguide::GuideEdit;
@@ -108,6 +111,40 @@ pub struct SelectionTool {
guide: GuideEdit,
/// Smart Guides: the centre or anchor under the pointer, and its label ([`Self::hover_point`]).
hover: Option<(Point, &'static str)>,
/// Distance Guides: what Alt held over it measures the selection to ([`distance_target`]).
distance: Option<DistanceTo>,
}
/// What Distance Guides measure the selection to.
#[derive(Clone, Copy, Debug, PartialEq)]
enum DistanceTo {
Object(NodeId),
/// The artboard at this index.
Artboard(usize),
}
/// Distance Guides: with Smart Guides and Smart Guides → Invoke Distance Guides on, something
/// selected and Alt held (`m`), the object under `p` unless it is selected, or over no object the
/// artboard `p` is on.
fn distance_target(cx: &ToolContext, p: Point, m: Mods) -> Option<DistanceTo> {
if !(cx.smart_guides && cx.distance_guides && m.alt) || cx.selection.objects.is_empty() {
return None;
}
match hit_test(cx.doc, p, cx.selection_hit_options()).map(|h| h.top_object(cx.isolation)) {
Some(id) => (!cx.selection.contains(id)).then_some(DistanceTo::Object(id)),
None => cx.doc.artboard_at(p).map(DistanceTo::Artboard),
}
}
/// The Distance Guides from the selection to `to` ([`distance_guides`]); none once either is gone.
fn distance_overlays(cx: &ToolContext, to: DistanceTo) -> Vec<Overlay> {
let Some(from) = selection_bounds(cx) else { return vec![] };
match to {
DistanceTo::Object(id) if !cx.selection.contains(id) => object_bounds(cx, id).map(|r| distance_guides(cx, from, r, false)),
DistanceTo::Object(_) => None,
DistanceTo::Artboard(i) => cx.doc.artboards.get(i).map(|a| distance_guides(cx, from, a.rect, true)),
}
.unwrap_or_default()
}
/// Snapping for the selection moved as a whole, its targets gathered when the move begins: View →
@@ -535,6 +572,7 @@ impl Tool for SelectionTool {
}
fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
self.distance = (ev.kind == PointerKind::Move && matches!(self.state, State::Idle)).then(|| distance_target(cx, ev.pos, ev.mods)).flatten();
if let State::Loaded { from, press } = self.state
&& let Some(out) = self.loaded(cx, ev, from, press)
{
@@ -845,6 +883,9 @@ impl Tool for SelectionTool {
o.push(Overlay::Label { p: q, text: label.into(), color: style.color });
}
}
if let (Some(to), State::Idle, true, true) = (self.distance, &self.state, cx.smart_guides, cx.distance_guides) {
o.extend(distance_overlays(cx, to));
}
if let Some((p, t)) = &self.measure {
o.push(Overlay::Measure { p: *p, text: t.clone() });
}
@@ -1117,6 +1158,148 @@ mod tests {
assert!(!t.overlays(&off).iter().any(|o| matches!(o, Overlay::Label { .. })));
}
/// [`doc_with_rect`] (100, 100)–(200, 200), selected, and a second rectangle `other`.
fn two_rects(other: Rect) -> (vectorcraft_doc::Document, NodeId, NodeId) {
let (mut d, a) = doc_with_rect();
let l = d.layers[0].id;
let b = d.alloc_id();
let path = vectorcraft_geom::shapes::rectangle(other);
d.insert(Some(l), 1, vectorcraft_doc::Node::path(b, path, vectorcraft_doc::Appearance::default_art())).unwrap();
(d, a, b)
}
fn alt(kind: PointerKind, x: f64, y: f64) -> PointerEvent {
ev(kind, x, y).with_mods(Mods { alt: true, ..Mods::default() })
}
/// The Distance Guides overlays: each measured line (its ends and label), and the dashed
/// lines out to it, in order.
fn distances(t: &SelectionTool, cx: &ToolContext) -> (Vec<(Point, Point, String)>, Vec<(Point, Point)>) {
let o = t.overlays(cx);
let lines = o.iter().filter_map(|o| if let Overlay::Distance { a, b, text, .. } = o { Some((*a, *b, text.clone())) } else { None });
let dashed = o.iter().filter_map(|o| if let Overlay::Line { a, b, dashed: true, .. } = o { Some((*a, *b)) } else { None });
(lines.collect(), dashed.collect())
}
fn line(a: (f64, f64), b: (f64, f64), text: &str) -> (Point, Point, String) {
(Point::new(a.0, a.1), Point::new(b.0, b.1), text.to_string())
}
/// Alt held over an object beside the selection shows the horizontal gap between their
/// facing edges across the middle of where they overlap, and the vertical offsets of their
/// top and bottom edges across the selection's middle, with dashed lines out to the other
/// object's edges (helpx: Use Distance Guides for accurate placement).
#[test]
fn alt_over_an_object_beside_the_selection_measures_the_gap_and_the_edges() {
let (d, a, _) = two_rects(Rect::new(300.0, 120.0, 400.0, 180.0));
let s = Selection { objects: vec![a], ..Default::default() };
let p = paint();
let cx = cx(&d, &s, &p);
let mut t = SelectionTool::default();
t.pointer(&cx, &alt(PointerKind::Move, 350.0, 150.0));
let (lines, dashed) = distances(&t, &cx);
assert_eq!(
lines,
vec![
line((200.0, 150.0), (300.0, 150.0), "100.00 pt"),
line((150.0, 100.0), (150.0, 120.0), "20.00 pt"),
line((150.0, 200.0), (150.0, 180.0), "20.00 pt"),
]
);
let p = Point::new;
assert_eq!(dashed, vec![(p(300.0, 120.0), p(150.0, 120.0)), (p(300.0, 180.0), p(150.0, 180.0))]);
assert!(t.overlays(&cx).iter().all(|o| !matches!(o, Overlay::Distance { color, .. } if *color != cx.smart_guide_color)));
}
/// Below the selection: the vertical gap, and the left and right edges' offsets.
#[test]
fn alt_over_an_object_below_the_selection_measures_the_vertical_gap() {
let (d, a, _) = two_rects(Rect::new(120.0, 300.0, 180.0, 400.0));
let s = Selection { objects: vec![a], ..Default::default() };
let p = paint();
let cx = cx(&d, &s, &p);
let mut t = SelectionTool::default();
t.pointer(&cx, &alt(PointerKind::Move, 150.0, 350.0));
let (lines, dashed) = distances(&t, &cx);
assert_eq!(
lines,
vec![
line((100.0, 150.0), (120.0, 150.0), "20.00 pt"),
line((200.0, 150.0), (180.0, 150.0), "20.00 pt"),
line((150.0, 200.0), (150.0, 300.0), "100.00 pt"),
]
);
assert_eq!(dashed.len(), 2, "out to the left and right edges of the object below");
}
/// Alt held over an empty part of the artboard: the selection's distance from the artboard's
/// left, right, top and bottom edges, in the General unit.
#[test]
fn alt_over_the_artboard_measures_to_its_four_edges_in_the_general_unit() {
let (d, a, _) = two_rects(Rect::new(300.0, 120.0, 400.0, 180.0));
let s = Selection { objects: vec![a], ..Default::default() };
let p = paint();
let cx = cx(&d, &s, &p);
let mut t = SelectionTool::default();
t.pointer(&cx, &alt(PointerKind::Move, 50.0, 450.0));
let (lines, dashed) = distances(&t, &cx);
assert_eq!(
lines,
vec![
line((100.0, 150.0), (0.0, 150.0), "100.00 pt"),
line((200.0, 150.0), (500.0, 150.0), "300.00 pt"),
line((150.0, 100.0), (150.0, 0.0), "100.00 pt"),
line((150.0, 200.0), (150.0, 500.0), "300.00 pt"),
]
);
assert!(dashed.is_empty());
let mm = ToolContext { unit: vectorcraft_doc::Unit::Millimeters, ..cx };
t.pointer(&mm, &alt(PointerKind::Move, 50.0, 450.0));
let texts: Vec<String> = distances(&t, &mm).0.into_iter().map(|l| l.2).collect();
assert_eq!(texts, ["35.28 mm", "105.83 mm", "35.28 mm", "105.83 mm"]);
// Off the artboard, over nothing: no distances.
t.pointer(&cx, &alt(PointerKind::Move, 600.0, 600.0));
assert!(distances(&t, &cx).0.is_empty());
}
/// No Distance Guides without Alt, with nothing selected, over the selection itself, with
/// Smart Guides or Invoke Distance Guides off, or once Alt is let go.
#[test]
fn distance_guides_need_alt_a_selection_and_smart_guides() {
let (d, a, _) = two_rects(Rect::new(300.0, 120.0, 400.0, 180.0));
let s = Selection { objects: vec![a], ..Default::default() };
let none = Selection::default();
let p = paint();
let cx = cx(&d, &s, &p);
let shown = |cx: &ToolContext, e: PointerEvent| {
let mut t = SelectionTool::default();
t.pointer(cx, &e);
!distances(&t, cx).0.is_empty()
};
assert!(shown(&cx, alt(PointerKind::Move, 350.0, 150.0)));
assert!(!shown(&cx, ev(PointerKind::Move, 350.0, 150.0)), "without Alt");
assert!(!shown(&cx, ev(PointerKind::Move, 50.0, 450.0)), "without Alt, over the artboard");
assert!(!shown(&crate::testutil::cx(&d, &none, &p), alt(PointerKind::Move, 350.0, 150.0)), "nothing selected");
assert!(!shown(&cx, alt(PointerKind::Move, 150.0, 150.0)), "over the selection");
assert!(
!shown(&ToolContext { smart_guides: false, ..crate::testutil::cx(&d, &s, &p) }, alt(PointerKind::Move, 350.0, 150.0)),
"View › Smart Guides off"
);
assert!(
!shown(&ToolContext { distance_guides: false, ..crate::testutil::cx(&d, &s, &p) }, alt(PointerKind::Move, 350.0, 150.0)),
"the preference off"
);
// Letting Alt go (the canvas sends a hover with the new modifiers) takes them away.
let mut t = SelectionTool::default();
t.pointer(&cx, &alt(PointerKind::Move, 350.0, 150.0));
t.pointer(&cx, &ev(PointerKind::Move, 350.0, 150.0));
assert!(distances(&t, &cx).0.is_empty());
// A press ends them too.
t.pointer(&cx, &alt(PointerKind::Move, 350.0, 150.0));
t.pointer(&cx, &alt(PointerKind::Down, 350.0, 150.0));
assert!(distances(&t, &cx).0.is_empty());
}
#[test]
fn click_selects_object() {
let (d, id) = doc_with_rect();
+87
View File
@@ -3183,6 +3183,7 @@ fn draw_overlays(p: &egui::Painter, xf: &Xf, overlays: &[Overlay], t: &Tokens, l
p.rect_filled(r, CornerRadius::same(3), t.measure_bg);
p.galley(sp + vec2(6.0, 4.0), galley, Color32::WHITE);
}
Overlay::Distance { a, b, text, color } => distance_guide(p, xf.to_screen(*a), xf.to_screen(*b), text, c32(*color)),
Overlay::GridLine { a, b, color } => {
let c = Color32::from_rgba_unmultiplied(color[0], color[1], color[2], color[3]);
p.line_segment([xf.to_screen(*a), xf.to_screen(*b)], Stroke::new(1.0, c));
@@ -3202,6 +3203,28 @@ fn draw_overlays(p: &egui::Painter, xf: &Xf, overlays: &[Overlay], t: &Tokens, l
}
}
/// A Distance Guides measurement on screen ([`Overlay::Distance`]): the line from `a` to `b` in the
/// smart guide colour `color` with a 4 px tick across each end, and `text` in a pill of that colour
/// on its middle, in white or black, whichever reads on it.
fn distance_guide(p: &egui::Painter, a: Pos2, b: Pos2, text: &str, color: Color32) {
let s = Stroke::new(1.0, color);
p.line_segment([a, b], s);
let along = b - a;
let len = along.length();
if len > 0.0 && len.is_finite() {
let across = vec2(-along.y, along.x) / len * 4.0;
for end in [a, b] {
p.line_segment([end - across, end + across], s);
}
}
let luma = 0.299 * f32::from(color.r()) + 0.587 * f32::from(color.g()) + 0.114 * f32::from(color.b());
let ink = if luma > 150.0 { Color32::BLACK } else { Color32::WHITE };
let galley = p.layout_no_wrap(text.to_owned(), egui::FontId::proportional(11.0), ink);
let r = egui::Rect::from_center_size(a + along / 2.0, galley.size() + vec2(8.0, 4.0));
p.rect_filled(r, CornerRadius::same(3), color);
p.galley(r.min + vec2(4.0, 2.0), galley, ink);
}
/// The Home screen (`app.home`; with no document open, unless its preference is off).
fn home(app: &mut VectorcraftApp, ui: &mut Ui, rect: egui::Rect) {
let t = Tokens::get(ui.ctx());
@@ -9473,6 +9496,70 @@ mod tests {
}
}
/// Distance Guides on screen: with a square selected and Alt held over another to its right,
/// the frame draws the measured line between their facing edges with the gap's length in a
/// pill of the smart guide colour on its middle; without Alt, or with Alt let go, nothing.
#[test]
fn alt_over_another_object_draws_the_distance_guide_label() {
use egui::{Event, Modifiers};
use vectorcraft_tools::guides::MAGENTA;
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
let a = app.run("shape.rectangle", json!({"x": 50, "y": 50, "width": 50, "height": 50})).unwrap()["id"].clone();
app.run("shape.rectangle", json!({"x": 200, "y": 50, "width": 50, "height": 50})).unwrap();
app.run("select.set", json!({"ids": [a]})).unwrap();
app.ui.view.smart_guides = true;
app.select_tool("selection");
let ctx = egui::Context::default();
frame(&mut app, &ctx, vec![]);
let view = app.view_mut().unwrap();
view.zoom = 1.0;
view.rotation = 0.0;
view.center = Point::new(200.0, 150.0);
frame(&mut app, &ctx, vec![]);
let xf = Xf::new(app.canvas_rect.unwrap(), app.view().unwrap());
let over = xf.to_screen(Point::new(225.0, 75.0));
// One frame with `events` → the shapes drawn, flattened. The modifiers held stay from one
// frame to the next until an `Event::ModifiersChanged`.
let run = |app: &mut VectorcraftApp, events: Vec<Event>| {
let raw = egui::RawInput { screen_rect: Some(WINDOW), events, ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| show(app, ui));
out.textures_delta.clear();
fn flat(s: Shape, out: &mut Vec<Shape>) {
match s {
Shape::Vec(v) => v.into_iter().for_each(|s| flat(s, out)),
s => out.push(s),
}
}
let mut v = vec![];
out.shapes.into_iter().for_each(|c| flat(c.shape, &mut v));
v
};
// The label "100.00 pt" drawn and the pill it sits in, if any.
let label = |s: &[Shape]| {
let text = s
.iter()
.find_map(|s| if let Shape::Text(t) = s { (t.galley.text() == "100.00 pt").then(|| t.visual_bounding_rect()) } else { None })?;
let pill = s.iter().find_map(|s| {
if let Shape::Rect(r) = s { (r.fill == c32(MAGENTA) && r.rect.contains(text.center())).then_some(r.rect) } else { None }
});
Some((text, pill))
};
run(&mut app, vec![Event::PointerMoved(over)]);
assert_eq!(label(&run(&mut app, vec![])), None, "without Alt");
run(&mut app, vec![Event::ModifiersChanged(Modifiers::ALT)]);
let s = run(&mut app, vec![]);
let (text, pill) = label(&s).expect("the gap's length is drawn");
let pill = pill.expect("in a pill of the smart guide colour");
let mid = xf.to_screen(Point::new(150.0, 75.0));
assert!(pill.center().distance(mid) < 1.0 && pill.contains_rect(text), "{pill:?} on {mid:?}");
let (from, to) = (xf.to_screen(Point::new(100.0, 75.0)), xf.to_screen(Point::new(200.0, 75.0)));
let measured = s.iter().any(|s| matches!(s, Shape::LineSegment { points, stroke } if *points == [from, to] && stroke.color == c32(MAGENTA)));
assert!(measured, "the measured line between the facing edges");
run(&mut app, vec![Event::ModifiersChanged(Modifiers::NONE)]);
assert_eq!(label(&run(&mut app, vec![])), None, "Alt let go");
}
/// The 800 × 600 window [`frame`] draws the canvas in.
const WINDOW: egui::Rect = egui::Rect { min: Pos2::ZERO, max: Pos2 { x: 800.0, y: 600.0 } };
+1
View File
@@ -3028,6 +3028,7 @@ font-technology Variable Променливи
Space Before Paragraph Разстояние преди абзац
Spaces Интервали
Spacing Guides Водачи за разстояние
Invoke Distance Guides Извикване на водачи за отстояния
Spacing: Разстояние:
Spanish Испански
Specified Distance Упоменато разстояние
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3521,6 +3521,7 @@ theme Appearance Motiv
Size/Leading Velikost/proklad
Snapping Tolerance Tolerance přichytávání
Spacing Guides Vodítka rozestupů
Invoke Distance Guides Vyvolat vodítka vzdálenosti
Spanish Španělština
Subdivisions Dělení
Templates Folder Složka šablon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3270,6 +3270,7 @@ theme Appearance Udseende
Size/Leading Størrelse/linjeafstand
Snapping Tolerance Snap-tolerance
Spacing Guides Afstandshjælpelinjer
Invoke Distance Guides Aktivér hjælpelinjer for afstandsmåling
Spanish Spansk
Subdivisions Underinddelinger
Templates Folder Skabelonmappe
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3066,6 +3066,7 @@ effect Points Punkte
Space Before Paragraph Abstand vor Absatz
Spaces Leerzeichen
Spacing Guides Abstandshilfslinien
Invoke Distance Guides Distanzhilfslinien aufrufen
Spacing: Abstand:
Spanish Spanisch
Specified Distance Festgelegter Abstand
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3476,6 +3476,7 @@ theme Appearance Εμφάνιση
Space Before Paragraph Διάστημα πριν από την παράγραφο
Spaces Διαστήματα
Spacing Guides Οδηγοί αποστάσεων
Invoke Distance Guides Εμφάνιση οδηγών μέτρησης απόστασης
Spanish Ισπανικά
Specified Distance Καθορισμένη απόσταση
Specified Steps Καθορισμένα βήματα
Can't render this file because it contains an unexpected character in line 2059 and column 19.
+1
View File
@@ -3096,6 +3096,7 @@ font-technology Variable Variables
Space Before Paragraph Espacio antes del párrafo
Spaces Espacios
Spacing Guides Guías de espaciado
Invoke Distance Guides Invocar guías de distancia
Spacing: Espaciado:
Spanish Español
Specified Distance Distancia especificada
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3447,6 +3447,7 @@ theme Appearance Ulkoasu
Size/Leading Koko/riviväli
Snapping Tolerance Tartuntatoleranssi
Spacing Guides Välistysapuviivat
Invoke Distance Guides Käytä etäisyysapuviivoja
Spanish Espanja
Subdivisions Jaot
Templates Folder Mallipohjakansio
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3093,6 +3093,7 @@ font-technology Variable Variables
Space Before Paragraph Espace avant le paragraphe
Spaces Espaces
Spacing Guides Repères d’espacement
Invoke Distance Guides Appeler les repères de distance
Spacing: Espacement :
Spanish Espagnol
Specified Distance Distance définie
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3735,6 +3735,7 @@ mark-style Japanese Japán
Spaces Szóközök
Spacing Térköz
Spacing Guides Térköz-segédvonalak
Invoke Distance Guides Távolság-segédvonalak előhívása
Spacing: Térköz:
Spanish Spanyol
Spatter Fröcskölés
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3270,6 +3270,7 @@ theme Appearance Tampilan
Size/Leading Ukuran/Leading
Snapping Tolerance Toleransi kancing
Spacing Guides Panduan jarak
Invoke Distance Guides Panggil panduan pengukuran jarak
Spanish Spanyol
Subdivisions Subdivisi
Templates Folder Folder templat
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
3270
3271
3272
3273
3274
3275
3276
+1
View File
@@ -3130,6 +3130,7 @@ mark-style Japanese Giapponese
Space Before Paragraph Spazio prima del paragrafo
Spaces Spazi
Spacing Guides Guide di spaziatura
Invoke Distance Guides Richiama guide di distanza
Spacing: Spaziatura:
Spanish Spagnolo
Specified Distance Distanza specificata
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2896,6 +2896,7 @@ mark-style Japanese 日本式
Space Before Paragraph 段落前のアキ
Spaces スペース
Spacing Guides 間隔ガイド
Invoke Distance Guides 距離ガイドを呼び出す
Spacing: 間隔:
Spanish スペイン語
Specify: 指定:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3080,6 +3080,7 @@ theme Appearance იერსახე
Size/Leading ზომა/სტრიქონთა დაშორება
Snapping Tolerance მიბმის დაშვება
Spacing Guides შუალედის დამხმარე ხაზები
Invoke Distance Guides მანძილის დამხმარე ხაზების გამოძახება
Spanish ესპანური
Subdivisions ქვედანაყოფები
Templates Folder შაბლონების საქაღალდე
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2890,6 +2890,7 @@ mark-style Japanese 일본식
Space Before Paragraph 단락 앞 간격
Spaces 공백
Spacing Guides 간격 안내선
Invoke Distance Guides 거리 안내선 호출
Spacing: 간격:
Spanish Español
Specify: 지정:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3284,6 +3284,7 @@ theme Appearance Vormgeving
Size/Leading Tekengrootte/Regelafstand
Snapping Tolerance Snapping-tolerantie
Spacing Guides Hulplijnen voor tussenruimte
Invoke Distance Guides Afstandshulplijnen oproepen
Spanish Spaans
Subdivisions Onderverdelingen
Templates Folder Sjablonenmap
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3383,6 +3383,7 @@ theme Appearance Wygląd
Size/Leading Rozmiar/interlinia
Snapping Tolerance Tolerancja przyciągania
Spacing Guides Linie odstępów
Invoke Distance Guides Wywołuj linie odległości
Spanish Hiszpański
Subdivisions Podpodziały
Templates Folder Folder szablonów
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2564,6 +2564,7 @@
Space Before Paragraph Espaço Antes do Parágrafo
Spaces Espaços
Spacing Guides Guias de Espaçamento
Invoke Distance Guides Invocar Guias de Distância
Spacing: Espaçamento:
Spanish Espanhol
Specify: Especificar:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3126,6 +3126,7 @@ mark-style Japanese Японский
Space Before Paragraph Интервал перед абзацем
Spaces Пробелы
Spacing Guides Направляющие интервалов
Invoke Distance Guides Вызывать направляющие расстояний
Spacing: Интервал:
Spanish Испанский
Specified Distance Заданное расстояние
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3028,6 +3028,7 @@ font-technology Variable Variabla
Space Before Paragraph Avstånd före stycke
Spaces Blanksteg
Spacing Guides Avståndsstödlinjer
Invoke Distance Guides Anropa mätstödlinjer för avstånd
Spacing: Avstånd:
Spanish Spanska
Specified Distance Angivet avstånd
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -2934,6 +2934,7 @@ font-technology Variable Değişken
Space Before Paragraph Paragraftan Önce Boşluk
Spaces Alanlar
Spacing Guides Aralık Kılavuzları
Invoke Distance Guides Mesafe Kılavuzlarını Çağır
Spacing: Aralık:
Spanish İspanyolca
Specified Distance Belirtilen Mesafe
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -3123,6 +3123,7 @@ effect Points Точки
Space Before Paragraph Інтервал перед абзацом
Spaces Пробіли
Spacing Guides Напрямні інтервалів
Invoke Distance Guides Викликати напрямні відстаней
Spacing: Інтервал:
Spanish Іспанська
Specified Distance Задана відстань
Can't render this file because it contains an unexpected character in line 740 and column 40.
+1
View File
@@ -3462,6 +3462,7 @@ mark-style Japanese Kiểu Nhật
Spaces Khoảng trắng
Spacing Khoảng cách
Spacing Guides Đường căn khoảng cách
Invoke Distance Guides Gọi đường căn đo khoảng cách
Spacing: Khoảng cách:
Spanish Tiếng Tây Ban Nha
Spatter Bắn tung tóe
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
3462
3463
3464
3465
3466
3467
3468
+1
View File
@@ -2962,6 +2962,7 @@ font-script Japanese 汉字
Space Before Paragraph 段前间距
Spaces 空格
Spacing Guides 间距参考线
Invoke Distance Guides 调用距离参考线
Spacing: 间距:
Spanish 西班牙语
Specified Distance 指定距离
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3887,6 +3887,7 @@ font-script Japanese 漢字
Space Before Paragraph 段前間距
Spaces 空格
Spacing Guides 間距參考線
Invoke Distance Guides 叫用距離參考線
Spacing: 間距:
Spanish 西班牙文
Specified Distance 指定距離
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4 -1
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -2721,6 +2721,11 @@ Guides change where a snapped point lands:
- `spacingGuides` (on by default): a moved selection (Selection tool / `pointer_gesture` with `selection`) snaps so
the gap to a neighbour matches the equal spacing among other objects nearby; off, only alignment and construction
guides pull.
- `distanceGuides` (Invoke Distance Guides, on by default): with Smart Guides on, something selected and the Selection
tool hovering with `mods.alt` (a `pointer` event of kind `move`), the canvas measures the selection's bounds to the
object under the pointer (the gap between their facing edges on an axis on which they lie apart, else the offsets
of their two edges on it) or, over no object, to the left, right, top and bottom edges of the artboard under the
pointer: measured lines labelled in the General unit. Off, Alt-hovering shows none.
With Smart Guides on, a bounding-box handle dragged with the Selection or Free Transform tool (`mods.shift`
proportional, `mods.alt` from the centre) lands on another object's anchor or centre (labelled "anchor" or
File diff suppressed because one or more lines are too long
+3 -1
View File
File diff suppressed because one or more lines are too long