mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 22:20:50 -06:00
Part of #394: Preferences that act on selection, the wheel, tool tips and the grid
Fifteen Preferences options were stored but read by nothing. They now act, through the existing plumbing (ToolContext for the tools, the prefs read by the canvas for the view), and reach agents through prefs.set: - General: Show Tool Tips (off: no widget's tool tip comes due), Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways), Use Precise Cursors (Pen, Eyedropper, Slice and Blend pointers become a crosshair). - Selection & Anchor Display: Tolerance (objects, anchors and handles, in place of the fixed 3 and 4 px), Object Selection by Path Only (compound paths too), Command Click to Select Objects Behind (cycles down the objects under the pointer; not when Cmd only borrows the selection tool), Zoom to Selection (Zoom In/Out centre the selection), anchor Size, Highlight anchors on mouse over (Direct Selection). - Type: Number of Recent Fonts (up to 15 Recent Fonts slots). - Guides & Grid: guide and grid Color and Style (lines or dots), Grids In Back. The canvas's context menu, panel drops and hover highlight pick objects like the selection tools. The control channel gets ui.wheel (with modifiers held like ui.click) so agents can turn the wheel. The rest of #394 stays open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f7a03160d6
commit
47bed05ff4
@@ -95,11 +95,13 @@ pub struct DirectSelectionTool {
|
||||
mesh: MeshEdit,
|
||||
/// Smart guides of the handle being dragged.
|
||||
guides: Vec<Overlay>,
|
||||
/// The anchor under the pointer (Highlight anchors on mouse over): its path, where it is.
|
||||
hover: Option<(NodeId, Point)>,
|
||||
}
|
||||
|
||||
impl DirectSelectionTool {
|
||||
pub fn new(group: bool) -> Self {
|
||||
Self { group, state: State::Idle, spine: None, mesh: MeshEdit::default(), guides: vec![] }
|
||||
Self { group, state: State::Idle, spine: None, mesh: MeshEdit::default(), guides: vec![], hover: None }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +238,7 @@ impl Tool for DirectSelectionTool {
|
||||
}
|
||||
fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
|
||||
let p = ev.pos;
|
||||
let tol = cx.tol(4.0);
|
||||
let tol = cx.pick_tol();
|
||||
match (ev.kind, self.state.clone()) {
|
||||
(PointerKind::Down, _) if self.group => {
|
||||
let Some(h) = hit_test(cx.doc, p, cx.hit_options()) else {
|
||||
@@ -260,6 +262,10 @@ impl Tool for DirectSelectionTool {
|
||||
}
|
||||
}
|
||||
(PointerKind::DoubleClick, _) if !self.group => corners::double_click(cx, p).into_iter().collect(),
|
||||
(PointerKind::Move, State::Idle) => {
|
||||
self.hover = if self.group || !cx.highlight_anchors { None } else { hovered_anchor(cx, p) };
|
||||
vec![]
|
||||
}
|
||||
(PointerKind::Down, _) => {
|
||||
if let Some(c) = CornerDrag::hit(cx, ev) {
|
||||
self.state = State::Corner(c);
|
||||
@@ -492,6 +498,9 @@ impl Tool for DirectSelectionTool {
|
||||
if matches!(self.state, State::Handle { .. }) {
|
||||
out.extend(self.guides.iter().cloned());
|
||||
}
|
||||
if let Some((id, p)) = self.hover.filter(|_| matches!(self.state, State::Idle)) {
|
||||
out.push(Overlay::Anchor { p, color: cx.doc.layer_color(id), filled: false, size: 8.0 });
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
@@ -504,6 +513,17 @@ impl Tool for DirectSelectionTool {
|
||||
}
|
||||
}
|
||||
|
||||
/// The anchor within the selection tolerance of `p`, of a selected path or the path under `p`:
|
||||
/// what Highlight anchors on mouse over marks (a press there picks it).
|
||||
fn hovered_anchor(cx: &ToolContext, p: Point) -> Option<(NodeId, Point)> {
|
||||
let under = hit_test(cx.doc, p, cx.hit_options()).map(|h| h.leaf);
|
||||
let tol = cx.pick_tol();
|
||||
cx.selection.objects.iter().copied().chain(under).find_map(|id| {
|
||||
let path = cx.doc.node(id).and_then(editable_path)?;
|
||||
path.anchors().find(|(_, _, a)| a.p.distance(p) <= tol).map(|(_, _, a)| (id, a.p))
|
||||
})
|
||||
}
|
||||
|
||||
/// The frame anchors of the selected area type, which Direct Selection drags.
|
||||
fn frame_overlays(cx: &ToolContext) -> Vec<Overlay> {
|
||||
let mut out = vec![];
|
||||
@@ -564,6 +584,30 @@ mod tests {
|
||||
(d, g, k1)
|
||||
}
|
||||
|
||||
/// Highlight anchors on mouse over (#394): the anchor under the pointer, within the selection
|
||||
/// tolerance, shows enlarged; none with the preference off or away from anchors.
|
||||
#[test]
|
||||
fn the_anchor_under_the_pointer_is_highlighted() {
|
||||
let (d, id) = doc_with_rect();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let big = |t: &DirectSelectionTool, cx: &ToolContext| {
|
||||
t.overlays(cx).into_iter().find_map(|o| match o {
|
||||
Overlay::Anchor { p, size, .. } if size > 5.0 => Some(p),
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
let mut t = DirectSelectionTool::new(false);
|
||||
let on = cx(&d, &s, &p);
|
||||
t.pointer(&on, &PointerEvent::new(PointerKind::Move, 102.0, 101.0));
|
||||
assert_eq!(big(&t, &on), Some(Point::new(100.0, 100.0)), "the corner of {id:?}");
|
||||
t.pointer(&on, &PointerEvent::new(PointerKind::Move, 150.0, 100.0));
|
||||
assert_eq!(big(&t, &on), None, "mid-segment: no anchor");
|
||||
let off = ToolContext { highlight_anchors: false, ..cx(&d, &s, &p) };
|
||||
t.pointer(&off, &PointerEvent::new(PointerKind::Move, 102.0, 101.0));
|
||||
assert_eq!(big(&t, &off), None, "off");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dragging_a_spine_point_moves_it() {
|
||||
let (d, g, _) = blend_doc();
|
||||
|
||||
Reference in New Issue
Block a user