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))
|
(update-fn value))
|
||||||
state))
|
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
|
(defn use-shared-state
|
||||||
"A specialized hook that adds persistence and inter-context reactivity
|
"A specialized hook that adds persistence and inter-context reactivity
|
||||||
to the default mf/use-state hook.
|
to the default mf/use-state hook.
|
||||||
|
|||||||
@ -206,8 +206,41 @@
|
|||||||
:top [p0 p1]
|
:top [p0 p1]
|
||||||
:left [p3 p0])))
|
: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*
|
(mf/defc selection-size-badge*
|
||||||
[{:keys [zoom shapes]}]
|
[{:keys [zoom shapes vbox]}]
|
||||||
(let [badge-height (/ selection-badge-height zoom)
|
(let [badge-height (/ selection-badge-height zoom)
|
||||||
badge-padding-x (/ selection-badge-padding-x zoom)
|
badge-padding-x (/ selection-badge-padding-x zoom)
|
||||||
badge-gap (/ selection-badge-vertical-gap 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 (dm/str (fmt/format-number shape-width) " x " (fmt/format-number shape-height))
|
||||||
|
|
||||||
text-width (* (count text) badge-char-width)
|
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?
|
(if has-rotation?
|
||||||
(let [edge (get-edge-for-badge rotation)
|
(let [edge (get-edge-for-badge rotation)
|
||||||
points (dm/get-prop single-shape :points)
|
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)
|
|
||||||
offset (+ badge-gap (/ badge-height 2))
|
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-x (- (/ badge-width 2))
|
||||||
badge-y (- (/ badge-height 2))
|
badge-y (- (/ badge-height 2))
|
||||||
badge-cx (+ (:x mid-point) (* (:x normal) offset))
|
badge-cx (:cx position)
|
||||||
badge-cy (+ (:y mid-point) (* (:y normal) offset))]
|
badge-cy (:cy position)]
|
||||||
|
|
||||||
[:g.selection-size-badge {:pointer-events "none"
|
[:g.selection-size-badge {:pointer-events "none"
|
||||||
:transform (dm/str "translate(" badge-cx "," badge-cy ") rotate(" badge-rot ")")}
|
:transform (dm/str "translate(" badge-cx "," badge-cy ") rotate(" badge-rot ")")}
|
||||||
@ -291,10 +326,13 @@
|
|||||||
:dominant-baseline "middle"}
|
:dominant-baseline "middle"}
|
||||||
text]])
|
text]])
|
||||||
|
|
||||||
(let [badge-x (- (/ badge-width 2))
|
(let [badge-x (- (/ badge-width 2))
|
||||||
badge-y (- (/ badge-height 2))
|
badge-y (- (/ badge-height 2))
|
||||||
badge-cx (+ (:x selrect) (/ (:width selrect) 2))
|
badge-cx (+ (:x selrect) (/ (:width selrect) 2))
|
||||||
badge-cy (+ (:y selrect) (:height selrect) badge-gap (/ badge-height 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"
|
[:g.selection-size-badge {:pointer-events "none"
|
||||||
:transform (dm/str "translate(" badge-cx "," badge-cy ")")}
|
:transform (dm/str "translate(" badge-cx "," badge-cy ")")}
|
||||||
|
|||||||
@ -125,9 +125,10 @@
|
|||||||
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
|
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
|
||||||
(apply-modifiers-to-selected selected base-objects text-modifiers modifiers))
|
(apply-modifiers-to-selected selected base-objects text-modifiers modifiers))
|
||||||
|
|
||||||
selected-shapes (->> selected
|
selected-shapes (mf/with-memo [selected objects-modified]
|
||||||
(into [] (keep (d/getf objects-modified)))
|
(->> selected
|
||||||
(not-empty))
|
(into [] (keep (d/getf objects-modified)))
|
||||||
|
(not-empty)))
|
||||||
|
|
||||||
;; True when at least one selected shape can be alt-duplicated.
|
;; True when at least one selected shape can be alt-duplicated.
|
||||||
;; Inner shapes of component copies are excluded by ctk/allow-duplicate?
|
;; Inner shapes of component copies are excluded by ctk/allow-duplicate?
|
||||||
@ -135,6 +136,11 @@
|
|||||||
(some #(ctk/allow-duplicate? base-objects %)
|
(some #(ctk/allow-duplicate? base-objects %)
|
||||||
(map (d/getf base-objects) selected)))
|
(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
|
;; STATE
|
||||||
alt? (mf/use-state false)
|
alt? (mf/use-state false)
|
||||||
shift? (mf/use-state false)
|
shift? (mf/use-state false)
|
||||||
@ -517,14 +523,15 @@
|
|||||||
:modifiers modifiers}])
|
:modifiers modifiers}])
|
||||||
|
|
||||||
(when (and (seq selected-shapes)
|
(when (and (seq selected-shapes)
|
||||||
(not transform)
|
(or (not transform) (contains? #{:resize :move} transform))
|
||||||
(not text-editing?)
|
(not text-editing?)
|
||||||
(not edition)
|
(not edition)
|
||||||
(not read-only?)
|
(not read-only?)
|
||||||
(not mode-inspect?))
|
(not mode-inspect?))
|
||||||
[:> msr/selection-size-badge*
|
[:> msr/selection-size-badge*
|
||||||
{:shapes selected-shapes
|
{:shapes badge-shapes
|
||||||
:zoom zoom}])
|
:zoom zoom
|
||||||
|
:vbox vbox}])
|
||||||
|
|
||||||
(when show-measures?
|
(when show-measures?
|
||||||
[:> msr/measurement*
|
[:> msr/measurement*
|
||||||
|
|||||||
@ -201,9 +201,10 @@
|
|||||||
[base-objects wasm-modifiers]
|
[base-objects wasm-modifiers]
|
||||||
(apply-modifiers-to-selected selected base-objects wasm-modifiers))
|
(apply-modifiers-to-selected selected base-objects wasm-modifiers))
|
||||||
|
|
||||||
selected-shapes (->> selected
|
selected-shapes (mf/with-memo [selected objects-modified]
|
||||||
(into [] (keep (d/getf objects-modified)))
|
(->> selected
|
||||||
(not-empty))
|
(into [] (keep (d/getf objects-modified)))
|
||||||
|
(not-empty)))
|
||||||
|
|
||||||
;; True when at least one selected shape can be alt-duplicated.
|
;; True when at least one selected shape can be alt-duplicated.
|
||||||
;; Inner shapes of component copies are excluded by ctk/allow-duplicate?
|
;; Inner shapes of component copies are excluded by ctk/allow-duplicate?
|
||||||
@ -211,6 +212,10 @@
|
|||||||
(some #(ctk/allow-duplicate? base-objects %)
|
(some #(ctk/allow-duplicate? base-objects %)
|
||||||
(map (d/getf base-objects) selected)))
|
(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
|
;; STATE
|
||||||
alt? (mf/use-state false)
|
alt? (mf/use-state false)
|
||||||
shift? (mf/use-state false)
|
shift? (mf/use-state false)
|
||||||
@ -813,15 +818,16 @@
|
|||||||
:zoom zoom}])
|
:zoom zoom}])
|
||||||
|
|
||||||
(when (and (seq selected-shapes)
|
(when (and (seq selected-shapes)
|
||||||
(not transform)
|
(or (not transform) (contains? #{:resize :move} transform))
|
||||||
(not text-editing?)
|
(not text-editing?)
|
||||||
(not edition)
|
(not edition)
|
||||||
(not read-only?)
|
(not read-only?)
|
||||||
(not mode-inspect?)
|
(not mode-inspect?)
|
||||||
(not page-transition?))
|
(not page-transition?))
|
||||||
[:> msr/selection-size-badge*
|
[:> msr/selection-size-badge*
|
||||||
{:shapes selected-shapes
|
{:shapes badge-shapes
|
||||||
:zoom zoom}])
|
:zoom zoom
|
||||||
|
:vbox vbox}])
|
||||||
|
|
||||||
(when show-measures?
|
(when show-measures?
|
||||||
[:> msr/measurement*
|
[:> msr/measurement*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user