mirror of
https://github.com/penpot/penpot.git
synced 2026-09-30 15:56:17 +00:00
✨ Improvements on the dimension badge (#11884)
* 🎉 Add flip option to measures badge * 🎉 Show dimension badge while resizing * 🎉 Show dimension badge while moving * 🎉 Hide badge when is smaller than shape * ♻️ Clean format * ♻️ Reduce comments * ♻️ Add memoization to selected-shapes
This commit is contained in:
parent
aeeca59b1a
commit
e4a8aa5c62
@ -310,6 +310,17 @@
|
||||
(update-fn value))
|
||||
state))
|
||||
|
||||
(defn use-throttle
|
||||
"Like `use-debounce`, but updates at most once per `ms` instead of only
|
||||
after the value stops changing: the first and the last value of each burst
|
||||
are both applied, so it stays live during continuous updates (e.g. a drag)."
|
||||
[ms value]
|
||||
(let [[state update-state-fn] (mf/useState value)
|
||||
update-fn (mf/use-memo (mf/deps ms) #(f/throttle update-state-fn ms))]
|
||||
(mf/with-effect [value]
|
||||
(update-fn value))
|
||||
state))
|
||||
|
||||
(defn use-shared-state
|
||||
"A specialized hook that adds persistence and inter-context reactivity
|
||||
to the default mf/use-state hook.
|
||||
|
||||
@ -206,8 +206,41 @@
|
||||
:top [p0 p1]
|
||||
:left [p3 p0])))
|
||||
|
||||
(defn- opposite-edge
|
||||
[edge]
|
||||
(case edge
|
||||
:bottom :top
|
||||
:top :bottom
|
||||
:left :right
|
||||
:right :left))
|
||||
|
||||
(defn- edge-rot-offset
|
||||
[edge]
|
||||
(case edge
|
||||
:bottom 0
|
||||
:right 270
|
||||
:top 180
|
||||
:left 90))
|
||||
|
||||
(defn- edge-badge-position
|
||||
"Badge center for a badge anchored to `edge` of the rotated shape."
|
||||
[points edge offset]
|
||||
(let [[ep1 ep2] (get-edge-points points edge)
|
||||
mid-point (gpt/lerp ep1 ep2 0.5)
|
||||
normal (gpt/normal-right (gpt/subtract ep2 ep1))]
|
||||
{:cx (+ (:x mid-point) (* (:x normal) offset))
|
||||
:cy (+ (:y mid-point) (* (:y normal) offset))}))
|
||||
|
||||
(defn- fits-above-vbox-bottom?
|
||||
"True when a badge centered at `cy` (with `badge-height`) still lands above
|
||||
the visible bottom edge of `vbox`."
|
||||
[cy badge-height vbox]
|
||||
(or (nil? vbox)
|
||||
(<= (+ cy (/ badge-height 2))
|
||||
(+ (:y vbox) (:height vbox)))))
|
||||
|
||||
(mf/defc selection-size-badge*
|
||||
[{:keys [zoom shapes]}]
|
||||
[{:keys [zoom shapes vbox]}]
|
||||
(let [badge-height (/ selection-badge-height zoom)
|
||||
badge-padding-x (/ selection-badge-padding-x zoom)
|
||||
badge-gap (/ selection-badge-vertical-gap zoom)
|
||||
@ -250,30 +283,32 @@
|
||||
text (dm/str (fmt/format-number shape-width) " x " (fmt/format-number shape-height))
|
||||
|
||||
text-width (* (count text) badge-char-width)
|
||||
badge-width (+ text-width (* 2 badge-padding-x))]
|
||||
badge-width (+ text-width (* 2 badge-padding-x))
|
||||
|
||||
(when-not ^boolean single-line?
|
||||
hidden? (or single-line?
|
||||
(< shape-width badge-width)
|
||||
(< shape-height badge-height))]
|
||||
|
||||
(when-not ^boolean hidden?
|
||||
(if has-rotation?
|
||||
(let [edge (get-edge-for-badge rotation)
|
||||
points (dm/get-prop single-shape :points)
|
||||
|
||||
[ep1 ep2] (get-edge-points points edge)
|
||||
|
||||
mid-point (gpt/lerp ep1 ep2 0.5)
|
||||
normal (gpt/normal-right (gpt/subtract ep2 ep1))
|
||||
|
||||
rot-offset (case edge
|
||||
:bottom 0
|
||||
:right 270
|
||||
:top 180
|
||||
:left 90)
|
||||
badge-rot (+ rotation rot-offset)
|
||||
(let [edge (get-edge-for-badge rotation)
|
||||
points (dm/get-prop single-shape :points)
|
||||
offset (+ badge-gap (/ badge-height 2))
|
||||
|
||||
;; Rotation follows the original edge even if the position
|
||||
;; flips to the opposite one for lack of space, so the text
|
||||
;; stays upright.
|
||||
badge-rot (+ rotation (edge-rot-offset edge))
|
||||
|
||||
position (edge-badge-position points edge offset)
|
||||
position (if (fits-above-vbox-bottom? (:cy position) badge-height vbox)
|
||||
position
|
||||
(edge-badge-position points (opposite-edge edge) offset))
|
||||
|
||||
badge-x (- (/ badge-width 2))
|
||||
badge-y (- (/ badge-height 2))
|
||||
badge-cx (+ (:x mid-point) (* (:x normal) offset))
|
||||
badge-cy (+ (:y mid-point) (* (:y normal) offset))]
|
||||
badge-cx (:cx position)
|
||||
badge-cy (:cy position)]
|
||||
|
||||
[:g.selection-size-badge {:pointer-events "none"
|
||||
:transform (dm/str "translate(" badge-cx "," badge-cy ") rotate(" badge-rot ")")}
|
||||
@ -291,10 +326,13 @@
|
||||
:dominant-baseline "middle"}
|
||||
text]])
|
||||
|
||||
(let [badge-x (- (/ badge-width 2))
|
||||
badge-y (- (/ badge-height 2))
|
||||
badge-cx (+ (:x selrect) (/ (:width selrect) 2))
|
||||
badge-cy (+ (:y selrect) (:height selrect) badge-gap (/ badge-height 2))]
|
||||
(let [badge-x (- (/ badge-width 2))
|
||||
badge-y (- (/ badge-height 2))
|
||||
badge-cx (+ (:x selrect) (/ (:width selrect) 2))
|
||||
below-cy (+ (:y selrect) (:height selrect) badge-gap (/ badge-height 2))
|
||||
badge-cy (if (fits-above-vbox-bottom? below-cy badge-height vbox)
|
||||
below-cy
|
||||
(- (:y selrect) badge-gap (/ badge-height 2)))]
|
||||
|
||||
[:g.selection-size-badge {:pointer-events "none"
|
||||
:transform (dm/str "translate(" badge-cx "," badge-cy ")")}
|
||||
|
||||
@ -125,9 +125,10 @@
|
||||
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
|
||||
(apply-modifiers-to-selected selected base-objects text-modifiers modifiers))
|
||||
|
||||
selected-shapes (->> selected
|
||||
(into [] (keep (d/getf objects-modified)))
|
||||
(not-empty))
|
||||
selected-shapes (mf/with-memo [selected objects-modified]
|
||||
(->> selected
|
||||
(into [] (keep (d/getf objects-modified)))
|
||||
(not-empty)))
|
||||
|
||||
;; True when at least one selected shape can be alt-duplicated.
|
||||
;; Inner shapes of component copies are excluded by ctk/allow-duplicate?
|
||||
@ -135,6 +136,11 @@
|
||||
(some #(ctk/allow-duplicate? base-objects %)
|
||||
(map (d/getf base-objects) selected)))
|
||||
|
||||
selected-shapes' (ui-hooks/use-throttle 100 selected-shapes)
|
||||
badge-shapes (if (= transform :resize)
|
||||
selected-shapes'
|
||||
selected-shapes)
|
||||
|
||||
;; STATE
|
||||
alt? (mf/use-state false)
|
||||
shift? (mf/use-state false)
|
||||
@ -517,14 +523,15 @@
|
||||
:modifiers modifiers}])
|
||||
|
||||
(when (and (seq selected-shapes)
|
||||
(not transform)
|
||||
(or (not transform) (contains? #{:resize :move} transform))
|
||||
(not text-editing?)
|
||||
(not edition)
|
||||
(not read-only?)
|
||||
(not mode-inspect?))
|
||||
[:> msr/selection-size-badge*
|
||||
{:shapes selected-shapes
|
||||
:zoom zoom}])
|
||||
{:shapes badge-shapes
|
||||
:zoom zoom
|
||||
:vbox vbox}])
|
||||
|
||||
(when show-measures?
|
||||
[:> msr/measurement*
|
||||
|
||||
@ -201,9 +201,10 @@
|
||||
[base-objects wasm-modifiers]
|
||||
(apply-modifiers-to-selected selected base-objects wasm-modifiers))
|
||||
|
||||
selected-shapes (->> selected
|
||||
(into [] (keep (d/getf objects-modified)))
|
||||
(not-empty))
|
||||
selected-shapes (mf/with-memo [selected objects-modified]
|
||||
(->> selected
|
||||
(into [] (keep (d/getf objects-modified)))
|
||||
(not-empty)))
|
||||
|
||||
;; True when at least one selected shape can be alt-duplicated.
|
||||
;; Inner shapes of component copies are excluded by ctk/allow-duplicate?
|
||||
@ -211,6 +212,10 @@
|
||||
(some #(ctk/allow-duplicate? base-objects %)
|
||||
(map (d/getf base-objects) selected)))
|
||||
|
||||
selected-shapes' (ui-hooks/use-throttle 100 selected-shapes)
|
||||
badge-shapes (if (= transform :resize)
|
||||
selected-shapes'
|
||||
selected-shapes)
|
||||
;; STATE
|
||||
alt? (mf/use-state false)
|
||||
shift? (mf/use-state false)
|
||||
@ -813,15 +818,16 @@
|
||||
:zoom zoom}])
|
||||
|
||||
(when (and (seq selected-shapes)
|
||||
(not transform)
|
||||
(or (not transform) (contains? #{:resize :move} transform))
|
||||
(not text-editing?)
|
||||
(not edition)
|
||||
(not read-only?)
|
||||
(not mode-inspect?)
|
||||
(not page-transition?))
|
||||
[:> msr/selection-size-badge*
|
||||
{:shapes selected-shapes
|
||||
:zoom zoom}])
|
||||
{:shapes badge-shapes
|
||||
:zoom zoom
|
||||
:vbox vbox}])
|
||||
|
||||
(when show-measures?
|
||||
[:> msr/measurement*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user