penpot/frontend/src/app/main/ui/css_cursors.cljs
Alonso Torres 217284b1e1
Improve path operations and edition (#10807)
*  Improve path operations and edition

* 🐛 Fix floating-point equality issues in path editing

Replace exact equality checks with tolerance-based comparisons
in path editing functions to handle floating-point rounding
differences after transforms, rotations, or curve fitting.

Changes:
- distribute-content: Round coordinates to 0.1 precision before
  grouping to ensure coincident nodes move together
- separate-node: Use gpt/close? instead of exact equality to
  find nodes with floating-point imprecision
- collision-step: Use mth/close? for tolerance-based comparison
  to detect paste collisions correctly
- resolve-edit-fills: Add cycle detection to prevent infinite
  loops with corrupted parent chains

Made collision-step, available-offset-step, and resolve-edit-fills
public for better testability.

Added comprehensive tests for all fixes covering both exact and
floating-point coordinate scenarios.

AI-assisted-by: qwen3.7-plus

* 🐛 Fix path editor code review findings

Fix issues identified during code review of path editor enhancements:

- Fix unused binding lint warning in distribute-content that blocked CI
- Fix collision-step floor comparison to use round instead of floor,
  correctly detecting collisions when coordinates drift slightly below
  integer boundaries
- Fix resolve-edit-fills to recurse through empty parent groups when
  searching for inherited fills in nested group hierarchies
- Fix expand-coincident-node-indices to use fuzzy comparison (gpt/close?)
  instead of exact equality, handling floating-point divergence after
  transforms or rotations
- Remove unreachable dead code in path-point* on-pointer-down handler
- Add tests for collision-step boundary cases, nested group fill
  inheritance, and coincident node alignment/flipping

AI-assisted-by: mimo-v2.5-pro

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-08-27 10:51:10 +02:00

82 lines
3.8 KiB
Clojure

(ns app.main.ui.css-cursors
(:require
[app.common.data.macros :as dm]
[app.main.ui.cursors :as cur]
[app.util.css :as css]))
;; angle per rotation
(def angle-step 10)
(defn get-static
"Returns the class name of a static cursor"
[name]
(dm/str "cursor-" name))
(defn get-dynamic
"Returns the class name of a dynamic cursor (with rotation)"
[name rotation]
(dm/str "cursor-" name "-" (mod (* (.floor js/Math (/ rotation angle-step)) angle-step) 360)))
(defn init-static-cursor-style
"Initializes a static cursor style"
[style name value]
(.add style (dm/str ".cursor-" name) (js-obj "cursor" (dm/str value " !important"))))
(defn init-dynamic-cursor-style
"Initializes a dynamic cursor style"
[style name fn]
(let [rotations (seq (range 0 360 angle-step))]
(doseq [rotation rotations]
(.add style (dm/str ".cursor-" name "-" rotation) (js-obj "cursor" (dm/str (fn rotation) " !important"))))))
(defn init-styles
"Initializes all cursor styles"
[]
(let [style (css/create-style "css-cursors")]
;; static
(init-static-cursor-style style "comments" cur/comments)
(init-static-cursor-style style "create-artboard" cur/create-artboard)
(init-static-cursor-style style "create-ellipse" cur/create-ellipse)
(init-static-cursor-style style "create-polygon" cur/create-polygon)
(init-static-cursor-style style "create-rectangle" cur/create-rectangle)
(init-static-cursor-style style "create-shape" cur/create-shape)
(init-static-cursor-style style "draw" cur/draw)
(init-static-cursor-style style "draw-add" cur/draw-add)
(init-static-cursor-style style "draw-node" cur/draw-node)
(init-static-cursor-style style "draw-remove" cur/draw-remove)
(init-static-cursor-style style "duplicate" cur/duplicate)
(init-static-cursor-style style "hand" cur/hand)
(init-static-cursor-style style "move" cur/move)
(init-static-cursor-style style "move-add" cur/move-add)
(init-static-cursor-style style "move-copy" cur/move-copy)
(init-static-cursor-style style "move-curve" cur/move-curve)
(init-static-cursor-style style "move-handles" cur/move-handles)
(init-static-cursor-style style "move-move" cur/move-move)
(init-static-cursor-style style "move-node" cur/move-node)
(init-static-cursor-style style "move-pointer" cur/move-pointer)
(init-static-cursor-style style "move-remove" cur/move-remove)
(init-static-cursor-style style "pen" cur/pen)
(init-static-cursor-style style "pen-node" cur/pen-node)
(init-static-cursor-style style "pencil" cur/pencil)
(init-static-cursor-style style "picker" cur/picker)
(init-static-cursor-style style "pointer-inner" cur/pointer-inner)
(init-static-cursor-style style "pointer-move" cur/pointer-move)
(init-static-cursor-style style "pointer-node" cur/pointer-node)
(init-static-cursor-style style "resize-alt" cur/resize-alt)
(init-static-cursor-style style "zoom" cur/zoom)
(init-static-cursor-style style "zoom-in" cur/zoom-in)
(init-static-cursor-style style "zoom-out" cur/zoom-out)
;; dynamic
(init-dynamic-cursor-style style "resize-ew" cur/resize-ew)
(init-dynamic-cursor-style style "resize-nesw" cur/resize-nesw)
(init-dynamic-cursor-style style "resize-ns" cur/resize-ns)
(init-dynamic-cursor-style style "resize-nwse" cur/resize-nwse)
(init-dynamic-cursor-style style "rotate" cur/rotate)
(init-dynamic-cursor-style style "text" cur/text)
(init-dynamic-cursor-style style "scale-ew" cur/scale-ew)
(init-dynamic-cursor-style style "scale-nesw" cur/scale-nesw)
(init-dynamic-cursor-style style "scale-ns" cur/scale-ns)
(init-dynamic-cursor-style style "scale-nwse" cur/scale-nwse)
(init-dynamic-cursor-style style "resize-ew-2" cur/resize-ew-2)
(init-dynamic-cursor-style style "resize-ns-2" cur/resize-ns-2)))