mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 04:06:54 -06:00
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:
co-authored by
Claude Sonnet 5.5
parent
75d5b015d3
commit
4c5ea73d98
@@ -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
@@ -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
@@ -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![],
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user