mirror of
https://github.com/penpot/penpot.git
synced 2026-06-20 06:12:04 +00:00
✨ Show guide pill on hover
This commit is contained in:
parent
76e9e5957b
commit
92972234d7
@ -40,6 +40,12 @@
|
||||
(def ^:const guide-pill-corner-radius 4)
|
||||
(def ^:const guide-active-area 16)
|
||||
|
||||
;; Manhattan distance (in screen pixels) the pointer must travel after
|
||||
;; pointerdown before we consider the interaction a drag. Below this we keep
|
||||
;; the hover state so a click — including the clicks that make up a
|
||||
;; double-click — doesn't flicker the overlay pill.
|
||||
(def ^:const guide-drag-threshold 3)
|
||||
|
||||
(def ^:const guide-creation-margin-left 8)
|
||||
(def ^:const guide-creation-margin-top 28)
|
||||
(def ^:const guide-creation-width 16)
|
||||
@ -608,8 +614,9 @@
|
||||
(nth (vec (vals guides)) index nil)))
|
||||
|
||||
(mf/defc guide-overlay*
|
||||
"Temporary SVG rendering of a guide that's being interacted with (drag or
|
||||
inline edit). In :edit mode it also shows the pill with an editable number."
|
||||
"Temporary SVG rendering of a guide that's being interacted with (drag, hover
|
||||
or inline edit). In :hover mode the line is omitted (WASM still draws it) and
|
||||
only the position pill is shown. In :edit mode the pill is an editable input."
|
||||
[{:keys [guide position vbox zoom mode frame-offset
|
||||
on-input-commit on-input-cancel]}]
|
||||
(let [axis (:axis guide)
|
||||
@ -617,6 +624,12 @@
|
||||
stroke-width (/ guide-width zoom)
|
||||
{:keys [x1 y1 x2 y2]} (guide-line-axis position vbox axis)
|
||||
input-ref (mf/use-ref nil)
|
||||
;; In :hover mode the WASM engine still renders the guide line, so we
|
||||
;; only overlay the pill. Drag and edit hide the WASM line and require
|
||||
;; the SVG line.
|
||||
show-line? (not= mode :hover)
|
||||
show-pill? (or (= mode :edit) (= mode :hover))
|
||||
editing? (= mode :edit)
|
||||
|
||||
on-key-down
|
||||
(mf/use-fn
|
||||
@ -639,19 +652,21 @@
|
||||
(fn []
|
||||
(on-input-commit (-> (mf/ref-val input-ref) dom/get-value))))]
|
||||
|
||||
(mf/with-effect []
|
||||
(some-> (mf/ref-val input-ref) dom/select-text!))
|
||||
(mf/with-effect [mode]
|
||||
(when editing?
|
||||
(some-> (mf/ref-val input-ref) dom/select-text!)))
|
||||
|
||||
[:g.guide-overlay
|
||||
[:line {:x1 x1
|
||||
:y1 y1
|
||||
:x2 x2
|
||||
:y2 y2
|
||||
:style {:stroke guide-color
|
||||
:stroke-width stroke-width
|
||||
:stroke-opacity guide-opacity-hover}}]
|
||||
(when show-line?
|
||||
[:line {:x1 x1
|
||||
:y1 y1
|
||||
:x2 x2
|
||||
:y2 y2
|
||||
:style {:stroke guide-color
|
||||
:stroke-width stroke-width
|
||||
:stroke-opacity guide-opacity-hover}}])
|
||||
|
||||
(when (= mode :edit)
|
||||
(when show-pill?
|
||||
(let [{:keys [rect-x rect-y rect-width rect-height text-x text-y]}
|
||||
(guide-pill-axis position vbox zoom axis)
|
||||
corner-radius (/ guide-pill-corner-radius zoom)
|
||||
@ -666,32 +681,45 @@
|
||||
:rx corner-radius
|
||||
:ry corner-radius
|
||||
:style {:fill guide-color}}]
|
||||
[:foreignObject {:x (- text-x (/ input-w 2))
|
||||
:y (- text-y (/ input-h 2))
|
||||
:width input-w
|
||||
:height input-h
|
||||
:transform (when (= axis :y)
|
||||
(str "rotate(-90 " text-x "," text-y ")"))}
|
||||
[:input {:ref input-ref
|
||||
:type "number"
|
||||
:step "any"
|
||||
:default-value display-value
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:on-pointer-down dom/stop-propagation
|
||||
:style {:width "100%"
|
||||
:height "100%"
|
||||
:border "none"
|
||||
:outline "none"
|
||||
:padding 0
|
||||
:margin 0
|
||||
:background "transparent"
|
||||
:color colors/white
|
||||
|
||||
(if editing?
|
||||
[:foreignObject {:x (- text-x (/ input-w 2))
|
||||
:y (- text-y (/ input-h 2))
|
||||
:width input-w
|
||||
:height input-h
|
||||
:transform (when (= axis :y)
|
||||
(str "rotate(-90 " text-x "," text-y ")"))}
|
||||
[:input {:ref input-ref
|
||||
:type "number"
|
||||
:step "any"
|
||||
:default-value display-value
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:on-pointer-down dom/stop-propagation
|
||||
:style {:width "100%"
|
||||
:height "100%"
|
||||
:border "none"
|
||||
:outline "none"
|
||||
:padding 0
|
||||
:margin 0
|
||||
:background "transparent"
|
||||
:color colors/white
|
||||
:font-family rulers/font-family
|
||||
:font-size (str (/ rulers/font-size zoom) "px")
|
||||
:text-align "center"
|
||||
:-moz-appearance "textfield"}}]]
|
||||
[:text {:x text-x
|
||||
:y text-y
|
||||
:text-anchor "middle"
|
||||
:dominant-baseline "middle"
|
||||
:transform (when (= axis :y)
|
||||
(str "rotate(-90 " text-x "," text-y ")"))
|
||||
:style {:font-size (/ rulers/font-size zoom)
|
||||
:font-family rulers/font-family
|
||||
:font-size (str (/ rulers/font-size zoom) "px")
|
||||
:text-align "center"
|
||||
:-moz-appearance "textfield"}}]]]))]))
|
||||
:fill colors/white
|
||||
:pointer-events "none"}}
|
||||
display-value])]))]))
|
||||
|
||||
(defn use-wasm-guide-interaction
|
||||
"Owns both drag and inline-edit lifecycles for WASM-rendered guides.
|
||||
@ -737,59 +765,76 @@
|
||||
(when (some? on-guide-hover)
|
||||
(on-guide-hover axis))))
|
||||
|
||||
reset-state
|
||||
clear-drag-refs
|
||||
(fn []
|
||||
(remove-drag-listeners)
|
||||
(when (some? on-guide-drag)
|
||||
(on-guide-drag nil))
|
||||
(mf/set-ref-val! dragging-ref false)
|
||||
(mf/set-ref-val! moved-ref false)
|
||||
(mf/set-ref-val! start-ref nil)
|
||||
(mf/set-ref-val! guide-ref nil)
|
||||
(mf/set-ref-val! pending-ref nil)
|
||||
(mf/set-ref-val! pending-ref nil))
|
||||
|
||||
reset-state
|
||||
(fn []
|
||||
(clear-drag-refs)
|
||||
(when (some? on-guide-drag)
|
||||
(on-guide-drag nil))
|
||||
(emit-hover-axis nil)
|
||||
(reset! state nil))
|
||||
|
||||
finish-drag
|
||||
(fn [event]
|
||||
(when (mf/ref-val dragging-ref)
|
||||
(when (and (mf/ref-val moved-ref) (some? on-guide-change))
|
||||
(when-let [{:keys [guide new-position new-frame-id]}
|
||||
(mf/ref-val pending-ref)]
|
||||
(when (and (some? guide) (some? new-position))
|
||||
(on-guide-change (assoc guide
|
||||
:position new-position
|
||||
:frame-id new-frame-id)))))
|
||||
(when-let [viewport @uwvv/viewport-ref]
|
||||
(when (.-pointerId event)
|
||||
(.releasePointerCapture viewport (.-pointerId event))))
|
||||
(reset-state)))
|
||||
(let [moved? (mf/ref-val moved-ref)]
|
||||
(when (and moved? (some? on-guide-change))
|
||||
(when-let [{:keys [guide new-position new-frame-id]}
|
||||
(mf/ref-val pending-ref)]
|
||||
(when (and (some? guide) (some? new-position))
|
||||
(on-guide-change (assoc guide
|
||||
:position new-position
|
||||
:frame-id new-frame-id)))))
|
||||
(when-let [viewport @uwvv/viewport-ref]
|
||||
(when (.-pointerId event)
|
||||
(.releasePointerCapture viewport (.-pointerId event))))
|
||||
;; A click without movement (no drag): leave the hover state
|
||||
;; alone so a follow-up double-click can transition straight
|
||||
;; from :hover to :edit without flickering through nil.
|
||||
(if moved?
|
||||
(reset-state)
|
||||
(clear-drag-refs)))))
|
||||
|
||||
drag-move
|
||||
(fn [move-event]
|
||||
(when (mf/ref-val dragging-ref)
|
||||
(when-let [guide (mf/ref-val guide-ref)]
|
||||
(let [axis (:axis guide)
|
||||
start-pt (mf/ref-val start-ref)
|
||||
current-pt (dom/get-client-position move-event)
|
||||
new-position (compute-guide-drag-position
|
||||
{:axis axis
|
||||
:position (:position guide)
|
||||
:start-pt start-pt
|
||||
:current-pt current-pt
|
||||
:zoom zoom
|
||||
:snap-pixel? snap-pixel?})
|
||||
new-frame-id (-> (get-hover-frame) (get :id))
|
||||
pending {:guide guide
|
||||
:new-position new-position
|
||||
:new-frame-id new-frame-id
|
||||
:mode :drag}]
|
||||
(when-not (mf/ref-val moved-ref)
|
||||
(mf/set-ref-val! moved-ref true)
|
||||
(when (some? on-guide-drag)
|
||||
(on-guide-drag (:id guide))))
|
||||
(mf/set-ref-val! pending-ref pending)
|
||||
(reset! state pending)))))
|
||||
(let [start-pt (mf/ref-val start-ref)
|
||||
current-pt (dom/get-client-position move-event)
|
||||
already-moved? (mf/ref-val moved-ref)
|
||||
past-threshold?
|
||||
(or already-moved?
|
||||
(> (+ (mth/abs (- (:x current-pt) (:x start-pt)))
|
||||
(mth/abs (- (:y current-pt) (:y start-pt))))
|
||||
guide-drag-threshold))]
|
||||
(when past-threshold?
|
||||
(let [axis (:axis guide)
|
||||
new-position (compute-guide-drag-position
|
||||
{:axis axis
|
||||
:position (:position guide)
|
||||
:start-pt start-pt
|
||||
:current-pt current-pt
|
||||
:zoom zoom
|
||||
:snap-pixel? snap-pixel?})
|
||||
new-frame-id (-> (get-hover-frame) (get :id))
|
||||
pending {:guide guide
|
||||
:new-position new-position
|
||||
:new-frame-id new-frame-id
|
||||
:mode :drag}]
|
||||
(when-not already-moved?
|
||||
(mf/set-ref-val! moved-ref true)
|
||||
(when (some? on-guide-drag)
|
||||
(on-guide-drag (:id guide))))
|
||||
(mf/set-ref-val! pending-ref pending)
|
||||
(reset! state pending)))))))
|
||||
|
||||
editing?
|
||||
(fn [] (= :edit (:mode @state)))
|
||||
@ -799,18 +844,37 @@
|
||||
(when-let [pt (uwvv/point->viewport (dom/get-client-position event))]
|
||||
(guide-by-serialized-index guides (wasm.api/find-guide-at pt zoom))))
|
||||
|
||||
guide-frame-offset
|
||||
(fn [guide]
|
||||
(let [frame (some-> (:frame-id guide) refs/object-by-id deref)]
|
||||
(if frame
|
||||
(if (= :x (:axis guide)) (:x frame) (:y frame))
|
||||
0)))
|
||||
|
||||
pointer-move-hover
|
||||
(fn [event]
|
||||
;; Only update hover cursor when we are not in the middle of a
|
||||
;; drag or edit. During drag the cursor is already set to the
|
||||
;; Only update hover cursor / pill when we are not in the middle of
|
||||
;; a drag or edit. During drag the cursor is already set to the
|
||||
;; dragged guide's axis; during edit the input owns the cursor.
|
||||
(when (and (not read-only?)
|
||||
(not (editing?))
|
||||
(not (mf/ref-val dragging-ref)))
|
||||
(let [guide (guide-at-event event)
|
||||
axis (when (and guide (guide-draggable-in-focus? focus guide))
|
||||
(:axis guide))]
|
||||
(emit-hover-axis axis))))
|
||||
(let [guide (when-let [g (guide-at-event event)]
|
||||
(when (guide-draggable-in-focus? focus g) g))
|
||||
current-state @state
|
||||
current-hover-id (when (= :hover (:mode current-state))
|
||||
(-> current-state :guide :id))]
|
||||
(emit-hover-axis (:axis guide))
|
||||
(cond
|
||||
(and (some? guide)
|
||||
(not= (:id guide) current-hover-id))
|
||||
(reset! state {:guide guide
|
||||
:new-position (:position guide)
|
||||
:frame-offset (guide-frame-offset guide)
|
||||
:mode :hover})
|
||||
|
||||
(and (nil? guide) (some? current-hover-id))
|
||||
(reset! state nil)))))
|
||||
|
||||
pointer-down
|
||||
(fn [event]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user