refactor(tools): fewer parameters and simpler pointer handling (Sonar S107, S3776) (#1725)

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Dylan M. Taylor
2026-10-24 04:51:35 -04:00
committed by GitHub
co-authored by Claude Sonnet 5.5
parent 75d5b015d3
commit 4c5ea73d98
3 changed files with 154 additions and 143 deletions
+2 -3
View File
@@ -570,8 +570,7 @@ fn finish_snapped_resize(
proposed: Rect,
mods: Mods,
driver_x: bool,
x_edges: [bool; 3],
y_edges: [bool; 3],
(x_edges, y_edges): ([bool; 3], [bool; 3]),
hit: &crate::Snap,
) -> Rect {
if !mods.alt {
@@ -1021,7 +1020,7 @@ impl Tool for SelectionTool {
pointer,
},
);
to = finish_snapped_resize(from, handle, proposed, ev.mods, driver_x, x_edges, y_edges, &hit);
to = finish_snapped_resize(from, handle, proposed, ev.mods, driver_x, (x_edges, y_edges), &hit);
crate::snap::lay_dimension_guides(&mut hit.guides, cx.layout.xf(spread), proposed, to, x_edges, y_edges, hit.length_delta);
self.guides = hit.guides;
}
+32 -25
View File
@@ -204,7 +204,7 @@ fn match_rotation(cx: &ToolContext, req: &SnapRequest<'_>, zone_length: f64) ->
let mut seen = HashSet::new();
if let Some(spread) = cx.doc.spread(req.spread) {
for item in &spread.items {
push_rotation(&mut best, cx.selection, req, item, Affine::IDENTITY, false, 0, &mut seen, proposed, tol);
push_rotation(&mut best, cx.selection, req, item, Walk::ROOT, &mut seen, (proposed, tol));
}
}
if shows_parent_items(cx, req) {
@@ -223,7 +223,24 @@ fn push_parent_rotations(
tol: f64,
) {
for (item, _) in shown_parent_items(cx.doc, req.spread) {
push_rotation(best, cx.selection, req, item, Affine::IDENTITY, false, 0, seen_items, proposed, tol);
push_rotation(best, cx.selection, req, item, Walk::ROOT, seen_items, (proposed, tol));
}
}
/// Where a recursive walk over an item tree stands: the parent's absolute transform, whether an
/// ancestor was skipped, and how deep it is.
#[derive(Clone, Copy)]
struct Walk {
ancestor: Affine,
skipped: bool,
depth: usize,
}
impl Walk {
const ROOT: Walk = Walk { ancestor: Affine::IDENTITY, skipped: false, depth: 0 };
fn child(self, ancestor: Affine, skipped: bool) -> Walk {
Walk { ancestor, skipped, depth: self.depth + 1 }
}
}
@@ -232,13 +249,11 @@ fn push_rotation(
selection: &Selection,
req: &SnapRequest<'_>,
item: &Item,
ancestor: Affine,
skipped: bool,
depth: usize,
walk: Walk,
seen: &mut HashSet<ItemId>,
proposed: f64,
tol: f64,
(proposed, tol): (f64, f64),
) {
let Walk { ancestor, skipped, depth } = walk;
if depth >= ALIGN_GROUP_DEPTH || !seen.insert(item.id) || item.hidden {
return;
}
@@ -254,7 +269,7 @@ fn push_rotation(
return;
}
for child in item.children() {
push_rotation(best, selection, req, child, abs, skip, depth + 1, seen, proposed, tol);
push_rotation(best, selection, req, child, walk.child(abs, skip), seen, (proposed, tol));
}
}
@@ -907,7 +922,7 @@ fn dimension_lengths(cx: &ToolContext, req: &SnapRequest<'_>) -> Vec<f64> {
let mut seen = HashSet::new();
if let Some(sp) = cx.doc.spread(req.spread) {
for item in &sp.items {
push_own_lengths(&mut out, cx.selection, req, item, Affine::IDENTITY, false, 0, &mut seen);
push_own_lengths(&mut out, cx.selection, req, item, Walk::ROOT, &mut seen);
}
}
if shows_parent_items(cx, req) {
@@ -916,16 +931,8 @@ fn dimension_lengths(cx: &ToolContext, req: &SnapRequest<'_>) -> Vec<f64> {
out
}
fn push_own_lengths(
out: &mut Vec<f64>,
selection: &Selection,
req: &SnapRequest<'_>,
item: &Item,
ancestor: Affine,
skipped: bool,
depth: usize,
seen: &mut HashSet<ItemId>,
) {
fn push_own_lengths(out: &mut Vec<f64>, selection: &Selection, req: &SnapRequest<'_>, item: &Item, walk: Walk, seen: &mut HashSet<ItemId>) {
let Walk { ancestor, skipped, depth } = walk;
if depth >= ALIGN_GROUP_DEPTH || !seen.insert(item.id) || item.hidden {
return;
}
@@ -936,7 +943,7 @@ fn push_own_lengths(
let skip = skipped || align_skips_item(selection, req, item.id);
if item.is_group() && !item.children().is_empty() {
for child in item.children() {
push_own_lengths(out, selection, req, child, abs, skip, depth + 1, seen);
push_own_lengths(out, selection, req, child, walk.child(abs, skip), seen);
}
return;
}
@@ -994,7 +1001,7 @@ fn stroke_outset(item: &Item) -> f64 {
fn push_parent_lengths(out: &mut Vec<f64>, cx: &ToolContext, req: &SnapRequest<'_>, seen_items: &mut HashSet<ItemId>) {
for (item, _) in shown_parent_items(cx.doc, req.spread) {
push_own_lengths(out, cx.selection, req, item, Affine::IDENTITY, false, 0, seen_items);
push_own_lengths(out, cx.selection, req, item, Walk::ROOT, seen_items);
}
}
@@ -1084,11 +1091,11 @@ fn pass_align(cx: &ToolContext, req: &SnapRequest<'_>, axis: Axis, tol: f64) ->
let mut best: Option<Best> = None;
if let Some(sp) = cx.doc.spread(req.spread) {
for page in &sp.pages {
offer_box(&mut best, req.rect, flags, axis, page.bounds(), tol, cx.snap.align_edges, cx.snap.align_centers);
offer_box(&mut best, req.rect, flags, axis, page.bounds(), tol, (cx.snap.align_edges, cx.snap.align_centers));
}
for item in &sp.items {
let Some(rect) = item_target_rect(cx.selection, req, item) else { continue };
offer_box(&mut best, req.rect, flags, axis, rect, tol, cx.snap.align_edges, cx.snap.align_centers);
offer_box(&mut best, req.rect, flags, axis, rect, tol, (cx.snap.align_edges, cx.snap.align_centers));
}
}
if shows_parent_items(cx, req) {
@@ -1186,7 +1193,7 @@ fn group_box(item: &Item, seen: &mut HashSet<ItemId>, depth: usize, filter: Opti
fn offer_parent_items(best: &mut Option<Best>, cx: &ToolContext, req: &SnapRequest<'_>, flags: [bool; 3], axis: Axis, tol: f64) {
for (item, dx) in shown_parent_items(cx.doc, req.spread) {
let Some(rect) = item_target_rect(cx.selection, req, item) else { continue };
offer_box(best, req.rect, flags, axis, shift_x(rect, dx), tol, cx.snap.align_edges, cx.snap.align_centers);
offer_box(best, req.rect, flags, axis, shift_x(rect, dx), tol, (cx.snap.align_edges, cx.snap.align_centers));
}
}
@@ -1280,7 +1287,7 @@ fn offer_line(best: &mut Option<Best>, moving: Rect, flags: [bool; 3], axis: Axi
}
}
fn offer_box(best: &mut Option<Best>, moving: Rect, flags: [bool; 3], axis: Axis, target: Rect, tol: f64, edges: bool, centers: bool) {
fn offer_box(best: &mut Option<Best>, moving: Rect, flags: [bool; 3], axis: Axis, target: Rect, tol: f64, (edges, centers): (bool, bool)) {
let (m0, m_mid, m1) = axis_triple(moving, axis);
let (t0, t_mid, t1) = axis_triple(target, axis);
let [use_left, use_mid, use_right] = flags;
+120 -115
View File
@@ -212,6 +212,123 @@ fn underlines(cx: &ToolContext, ts: TextSel, r: Range<usize>) -> Vec<(Point, Poi
out
}
impl TypeTool {
fn pointer_down(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
self.start = Some(ev.pos);
self.drawing = false;
self.selecting = None;
self.cell_drag = None;
self.unit = None;
self.border = None;
if ev.kind == PointerKind::Down
&& let Some(b) = border_at(cx, ev.pos)
{
self.border = Some(b);
return vec![];
}
// Text frames take the caret (locked ones too, as in InDesign); an empty frame
// becomes one, unless it's locked: then the press draws a new text frame over it.
if let Some((_, id)) = cx.hit(ev.pos)
&& let Some(it) = cx.doc.item(id)
&& !matches!(it.content, designcraft_doc::Content::Graphic(_) | designcraft_doc::Content::Group { .. })
&& (it.is_text_frame() || !it.locked)
{
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let clicks = ev.click_count();
// Whole rows/columns selected: wait to see whether this is a drag.
if clicks == 1 && !ev.mods.shift && whole_rows_or_cols(cx) {
self.cell_drag = Some((id.0, sp));
return vec![];
}
self.selecting = Some(id.0);
// Two presses select a word (or a run of spaces), three a line, four a
// paragraph, five the story.
if clicks >= 2 {
let unit = match clicks {
2 => "word",
3 => "line",
4 => "paragraph",
_ => "story",
};
self.unit = Some(unit);
return vec![Action::Exec("text.selectAt".into(), json!({"frame": id.0, "point": [sp.x, sp.y], "unit": unit}))];
}
let cmd = if ev.mods.shift { "text.extendTo" } else { "text.placeCaret" };
return vec![Action::Exec(cmd.into(), json!({"frame": id.0, "point": [sp.x, sp.y]}))];
}
vec![]
}
fn pointer_drag(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
let Some(a) = self.start else { return vec![] };
if let Some(b) = self.border.as_mut() {
// A click with a little wobble must not resize.
if !b.begun && (ev.pos - a).hypot() < cx.tol(3.0) {
return vec![];
}
return b.drag(ev.pos, ev.mods.shift);
}
if self.cell_drag.is_some() {
return vec![];
}
if let Some(fid) = self.selecting {
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let mut p = json!({"frame": fid, "point": [sp.x, sp.y]});
// After several clicks the drag grows the selection by whole units.
if let Some(unit) = self.unit {
p["unit"] = json!(unit);
}
return vec![Action::Exec("text.extendTo".into(), p)];
}
if (ev.pos - a).hypot() < cx.tol(3.0) && !self.drawing {
return vec![];
}
let Some((sr, sa)) = cx.layout.spread_at(a) else { return vec![] };
// Drawn like the frame tools: the press point and the moving edges snap.
let sa = if cx.snap.any() { crate::snap::snap_point(cx, sr, sa).0 } else { sa };
let (r, guides) = snapped_drag_rect(cx, sr, sa, cx.layout.to_spread(sr, ev.pos), ev.mods);
self.guides = guides;
let mut out = vec![];
if !self.drawing {
self.drawing = true;
out.push(Action::Begin("Create Text Frame".into()));
}
out.push(Action::Preview(
"frame.create".into(),
json!({"spread": spread_json(sr), "shape": "rectangle", "content": "text", "rect": rect_json(r), "caret": true, "vertical": self.vertical}),
));
out
}
fn pointer_up(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
self.guides.clear();
let start = self.start.take();
if let Some(b) = self.border.take() {
return if b.begun { vec![Action::Commit] } else { vec![] };
}
if let Some((fid, from)) = self.cell_drag.take() {
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let moved = start.is_some_and(|a| (ev.pos - a).hypot() >= cx.tol(3.0));
return if moved {
vec![Action::Exec("table.dropCells".into(), json!({"frame": fid, "from": [from.x, from.y], "to": [sp.x, sp.y]}))]
} else {
vec![Action::Exec("text.placeCaret".into(), json!({"frame": fid, "point": [from.x, from.y]}))]
};
}
if std::mem::take(&mut self.drawing) {
self.selecting = None;
return vec![Action::Commit];
}
// Ends a press in text: drops dragged text, or is ignored by the engine.
if let Some(fid) = self.selecting.take() {
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let moved = start.is_some_and(|a| (ev.pos - a).hypot() >= cx.tol(3.0));
return vec![Action::Exec("text.release".into(), json!({"frame": fid, "point": [sp.x, sp.y], "moved": moved, "copy": ev.mods.alt}))];
}
vec![]
}
}
impl Tool for TypeTool {
fn id(&self) -> &'static str {
if self.vertical { "verticalType" } else { "type" }
@@ -219,121 +336,9 @@ impl Tool for TypeTool {
fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
match ev.kind {
PointerKind::Down | PointerKind::DoubleClick => {
self.start = Some(ev.pos);
self.drawing = false;
self.selecting = None;
self.cell_drag = None;
self.unit = None;
self.border = None;
if ev.kind == PointerKind::Down
&& let Some(b) = border_at(cx, ev.pos)
{
self.border = Some(b);
return vec![];
}
// Text frames take the caret (locked ones too, as in InDesign); an empty frame
// becomes one, unless it's locked: then the press draws a new text frame over it.
if let Some((_, id)) = cx.hit(ev.pos)
&& let Some(it) = cx.doc.item(id)
&& !matches!(it.content, designcraft_doc::Content::Graphic(_) | designcraft_doc::Content::Group { .. })
&& (it.is_text_frame() || !it.locked)
{
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let clicks = ev.click_count();
// Whole rows/columns selected: wait to see whether this is a drag.
if clicks == 1 && !ev.mods.shift && whole_rows_or_cols(cx) {
self.cell_drag = Some((id.0, sp));
return vec![];
}
self.selecting = Some(id.0);
// Two presses select a word (or a run of spaces), three a line, four a
// paragraph, five the story.
if clicks >= 2 {
let unit = match clicks {
2 => "word",
3 => "line",
4 => "paragraph",
_ => "story",
};
self.unit = Some(unit);
return vec![Action::Exec("text.selectAt".into(), json!({"frame": id.0, "point": [sp.x, sp.y], "unit": unit}))];
}
let cmd = if ev.mods.shift { "text.extendTo" } else { "text.placeCaret" };
return vec![Action::Exec(cmd.into(), json!({"frame": id.0, "point": [sp.x, sp.y]}))];
}
vec![]
}
PointerKind::Drag => {
let Some(a) = self.start else { return vec![] };
if let Some(b) = self.border.as_mut() {
// A click with a little wobble must not resize.
if !b.begun && (ev.pos - a).hypot() < cx.tol(3.0) {
return vec![];
}
return b.drag(ev.pos, ev.mods.shift);
}
if self.cell_drag.is_some() {
return vec![];
}
if let Some(fid) = self.selecting {
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let mut p = json!({"frame": fid, "point": [sp.x, sp.y]});
// After several clicks the drag grows the selection by whole units.
if let Some(unit) = self.unit {
p["unit"] = json!(unit);
}
return vec![Action::Exec("text.extendTo".into(), p)];
}
if (ev.pos - a).hypot() < cx.tol(3.0) && !self.drawing {
return vec![];
}
let Some((sr, sa)) = cx.layout.spread_at(a) else { return vec![] };
// Drawn like the frame tools: the press point and the moving edges snap.
let sa = if cx.snap.any() { crate::snap::snap_point(cx, sr, sa).0 } else { sa };
let (r, guides) = snapped_drag_rect(cx, sr, sa, cx.layout.to_spread(sr, ev.pos), ev.mods);
self.guides = guides;
let mut out = vec![];
if !self.drawing {
self.drawing = true;
out.push(Action::Begin("Create Text Frame".into()));
}
out.push(Action::Preview(
"frame.create".into(),
json!({"spread": spread_json(sr), "shape": "rectangle", "content": "text", "rect": rect_json(r), "caret": true, "vertical": self.vertical}),
));
out
}
PointerKind::Up => {
self.guides.clear();
let start = self.start.take();
if let Some(b) = self.border.take() {
return if b.begun { vec![Action::Commit] } else { vec![] };
}
if let Some((fid, from)) = self.cell_drag.take() {
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let moved = start.is_some_and(|a| (ev.pos - a).hypot() >= cx.tol(3.0));
return if moved {
vec![Action::Exec("table.dropCells".into(), json!({"frame": fid, "from": [from.x, from.y], "to": [sp.x, sp.y]}))]
} else {
vec![Action::Exec("text.placeCaret".into(), json!({"frame": fid, "point": [from.x, from.y]}))]
};
}
if std::mem::take(&mut self.drawing) {
self.selecting = None;
return vec![Action::Commit];
}
// Ends a press in text: drops dragged text, or is ignored by the engine.
if let Some(fid) = self.selecting.take() {
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);
let moved = start.is_some_and(|a| (ev.pos - a).hypot() >= cx.tol(3.0));
return vec![Action::Exec(
"text.release".into(),
json!({"frame": fid, "point": [sp.x, sp.y], "moved": moved, "copy": ev.mods.alt}),
)];
}
vec![]
}
PointerKind::Down | PointerKind::DoubleClick => self.pointer_down(cx, ev),
PointerKind::Drag => self.pointer_drag(cx, ev),
PointerKind::Up => self.pointer_up(cx, ev),
PointerKind::Move => vec![],
}
}