mirror of
https://github.com/penpot/penpot.git
synced 2025-02-18 13:04:38 -05:00
✨ Measurements in handoff mode
This commit is contained in:
parent
25686eeba1
commit
14d10af9b8
4 changed files with 233 additions and 34 deletions
|
@ -108,6 +108,8 @@
|
||||||
.color-bullet {
|
.color-bullet {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
border-radius: $br-small;
|
||||||
|
border: 1px solid $color-gray-60;
|
||||||
}
|
}
|
||||||
.attributes-copy-button {
|
.attributes-copy-button {
|
||||||
padding: 1rem 0.5rem;
|
padding: 1rem 0.5rem;
|
||||||
|
@ -252,5 +254,12 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attributes-shadow-block {
|
||||||
|
border-top: 1px solid $color-gray-60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes-shadow-blocks :first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
:selected #{}
|
:selected #{}
|
||||||
:collapsed #{}
|
:collapsed #{}
|
||||||
:hover #{}}))
|
:hover nil}))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
|
@ -317,8 +317,7 @@
|
||||||
(ptk/reify ::hover-shape
|
(ptk/reify ::hover-shape
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(update-in state [:viewer-local :hover] (if hover? conj disj) id))))
|
(assoc-in state [:viewer-local :hover] (when hover? id)))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Shortcuts
|
;; --- Shortcuts
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,9 @@
|
||||||
(when handle-copy-shadow
|
(when handle-copy-shadow
|
||||||
[:button.attributes-copy-button {:on-click handle-copy-shadow} i/copy])]
|
[:button.attributes-copy-button {:on-click handle-copy-shadow} i/copy])]
|
||||||
|
|
||||||
(for [shape shapes]
|
[:div.attributes-shadow-blocks
|
||||||
(for [shadow (:shadow shape)]
|
(for [shape shapes]
|
||||||
[:& shadow-block {:shape shape
|
(for [shadow (:shadow shape)]
|
||||||
:locale locale
|
[:& shadow-block {:shape shape
|
||||||
:shadow shadow}]))])))
|
:locale locale
|
||||||
|
:shadow shadow}]))]])))
|
||||||
|
|
|
@ -10,15 +10,49 @@
|
||||||
(ns app.main.ui.viewer.handoff.selection-feedback
|
(ns app.main.ui.viewer.handoff.selection-feedback
|
||||||
(:require
|
(:require
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.math :as mth]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
[app.main.store :as st]))
|
[app.main.store :as st]))
|
||||||
|
|
||||||
(def selection-rect-color-normal "#1FDEA7")
|
;; ------------------------------------------------
|
||||||
(def selection-rect-color-component "#00E0FF")
|
;; CONSTANTS
|
||||||
|
;; ------------------------------------------------
|
||||||
|
|
||||||
|
(def select-color "#1FDEA7")
|
||||||
|
(def hover-color "#DB00FF")
|
||||||
|
(def hover-color-text "#FFF")
|
||||||
|
(def font-size 10)
|
||||||
|
|
||||||
(def selection-rect-width 1)
|
(def selection-rect-width 1)
|
||||||
|
|
||||||
|
(def select-guide-width 1)
|
||||||
|
(def select-guide-dasharray 5)
|
||||||
|
(def hover-guide-width 1)
|
||||||
|
|
||||||
|
(def size-display-color "#FFF")
|
||||||
|
(def size-display-opacity 0.7)
|
||||||
|
(def size-display-text-color "#000")
|
||||||
|
(def size-display-width-min 50)
|
||||||
|
(def size-display-width-max 75)
|
||||||
|
(def size-display-height 16)
|
||||||
|
|
||||||
|
(def distance-color "#DB00FF")
|
||||||
|
(def distance-text-color "#FFF")
|
||||||
|
(def distance-border-radius 2)
|
||||||
|
(def distance-pill-width 40)
|
||||||
|
(def distance-pill-height 16)
|
||||||
|
(def distance-line-stroke 1)
|
||||||
|
|
||||||
|
;; ------------------------------------------------
|
||||||
|
;; LENSES
|
||||||
|
;; ------------------------------------------------
|
||||||
|
|
||||||
(defn make-selected-shapes-iref
|
(defn make-selected-shapes-iref
|
||||||
|
"Creates a lens to the current selected shapes"
|
||||||
[]
|
[]
|
||||||
(let [selected->shapes
|
(let [selected->shapes
|
||||||
(fn [state]
|
(fn [state]
|
||||||
|
@ -29,38 +63,194 @@
|
||||||
#(l/derived selected->shapes st/state)))
|
#(l/derived selected->shapes st/state)))
|
||||||
|
|
||||||
(defn make-hover-shapes-iref
|
(defn make-hover-shapes-iref
|
||||||
|
"Creates a lens to the shapes the user is making hover"
|
||||||
[]
|
[]
|
||||||
(let [hover->shapes
|
(let [hover->shapes
|
||||||
(fn [state]
|
(fn [state]
|
||||||
(let [hover (get-in state [:viewer-local :hover])
|
(let [hover (get-in state [:viewer-local :hover])
|
||||||
objects (get-in state [:viewer-data :page :objects])
|
objects (get-in state [:viewer-data :page :objects])]
|
||||||
resolve-shape #(get objects %)]
|
(get objects hover)))]
|
||||||
(mapv resolve-shape hover)))]
|
|
||||||
#(l/derived hover->shapes st/state)))
|
#(l/derived hover->shapes st/state)))
|
||||||
|
|
||||||
(mf/defc selection-rect [{:keys [shape]}]
|
(def selected-zoom
|
||||||
(let [{:keys [x y width height]} (:selrect shape)]
|
(l/derived (l/in [:viewer-local :zoom]) st/state))
|
||||||
[:rect {:x x
|
|
||||||
:y y
|
;; ------------------------------------------------
|
||||||
:width width
|
;; HELPERS
|
||||||
:height height
|
;; ------------------------------------------------
|
||||||
:fill "transparent"
|
|
||||||
:stroke selection-rect-color-normal
|
(defn frame->selrect [frame]
|
||||||
:stroke-width selection-rect-width
|
{:x1 0
|
||||||
:pointer-events "none"}]))
|
:y1 0
|
||||||
|
:x2 (:width frame)
|
||||||
|
:y2 (:height frame)
|
||||||
|
:width (:width frame)
|
||||||
|
:height (:height frame)})
|
||||||
|
|
||||||
|
(defn calculate-guides
|
||||||
|
"Calculates coordinates for the selection guides"
|
||||||
|
[frame selrect]
|
||||||
|
(let [{frame-width :width frame-height :height} frame
|
||||||
|
{:keys [x y width height]} selrect]
|
||||||
|
[[0 y frame-width y]
|
||||||
|
[0 (+ y height) frame-width (+ y height)]
|
||||||
|
[x 0 x frame-height]
|
||||||
|
[(+ x width) 0 (+ x width) frame-height]]))
|
||||||
|
|
||||||
|
(defn calculate-distance-lines
|
||||||
|
"Given a start/end from two shapes gives the distance lines"
|
||||||
|
[from-s from-e to-s to-e]
|
||||||
|
(let [ss (- to-s from-s)
|
||||||
|
se (- to-e from-s)
|
||||||
|
es (- to-s from-e)
|
||||||
|
ee (- to-e from-e)]
|
||||||
|
(cond-> []
|
||||||
|
(or (and (neg? ss) (pos? se))
|
||||||
|
(and (pos? ss) (neg? ee))
|
||||||
|
(and (neg? ss) (> ss se)))
|
||||||
|
(conj [ from-s (+ from-s ss) ])
|
||||||
|
|
||||||
|
(or (and (neg? se) (<= ss se)))
|
||||||
|
(conj [ from-s (+ from-s se) ])
|
||||||
|
|
||||||
|
(or (and (pos? es) (<= es ee)))
|
||||||
|
(conj [ from-e (+ from-e es) ])
|
||||||
|
|
||||||
|
(or (and (pos? ee) (neg? es))
|
||||||
|
(and (neg? ee) (pos? ss))
|
||||||
|
(and (pos? ee) (< ee es)))
|
||||||
|
(conj [ from-e (+ from-e ee) ]))))
|
||||||
|
|
||||||
|
;; ------------------------------------------------
|
||||||
|
;; COMPONENTS
|
||||||
|
;; ------------------------------------------------
|
||||||
|
|
||||||
|
(mf/defc selection-guides [{:keys [frame selrect zoom]}]
|
||||||
|
[:g.selection-guides
|
||||||
|
(for [[x1 y1 x2 y2] (calculate-guides frame selrect)]
|
||||||
|
[:line {:x1 x1
|
||||||
|
:y1 y1
|
||||||
|
:x2 x2
|
||||||
|
:y2 y2
|
||||||
|
:style {:stroke select-color
|
||||||
|
:stroke-width (/ select-guide-width zoom)
|
||||||
|
:stroke-dasharray (/ select-guide-dasharray zoom)}}])])
|
||||||
|
|
||||||
|
(mf/defc selection-rect [{:keys [type frame selrect zoom]}]
|
||||||
|
(let [{:keys [x y width height]} selrect
|
||||||
|
stroke-color (case type
|
||||||
|
:selection select-color
|
||||||
|
:hover hover-color)]
|
||||||
|
[:g.selection-rect
|
||||||
|
[:rect {:x x
|
||||||
|
:y y
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:style {:fill "transparent"
|
||||||
|
:stroke stroke-color
|
||||||
|
:stroke-width (/ selection-rect-width zoom)}}]]))
|
||||||
|
|
||||||
|
(mf/defc size-display [{:keys [type selrect zoom]}]
|
||||||
|
(let [{:keys [x y width height]} selrect
|
||||||
|
size-label (str/fmt "%s x %s" (mth/round width) (mth/round height))
|
||||||
|
|
||||||
|
rect-height (/ size-display-height zoom)
|
||||||
|
rect-width (/ (if (<= (count size-label) 9)
|
||||||
|
size-display-width-min
|
||||||
|
size-display-width-max)
|
||||||
|
zoom)
|
||||||
|
text-padding (/ 4 zoom)]
|
||||||
|
[:g.size-display
|
||||||
|
[:rect {:x (+ x (/ width 2) (- (/ rect-width 2)))
|
||||||
|
:y (- (+ y height) rect-height)
|
||||||
|
:width rect-width
|
||||||
|
:height rect-height
|
||||||
|
:style {:fill size-display-color
|
||||||
|
:fill-opacity size-display-opacity}}]
|
||||||
|
|
||||||
|
[:text {:x (+ (+ x (/ width 2) (- (/ rect-width 2))) (/ rect-width 2))
|
||||||
|
:y (- (+ y height (+ text-padding (/ rect-height 2))) rect-height)
|
||||||
|
:width rect-width
|
||||||
|
:height rect-height
|
||||||
|
:text-anchor "middle"
|
||||||
|
:style {:fill size-display-text-color
|
||||||
|
:font-size (/ font-size zoom)}}
|
||||||
|
size-label]]))
|
||||||
|
|
||||||
|
|
||||||
|
(mf/defc distance-display [{:keys [type from to zoom]}]
|
||||||
|
(let [h-lines (let [y (+ (:y from) (/ (:height from) 2))]
|
||||||
|
(->> (calculate-distance-lines (:x1 from) (:x2 from) (:x1 to) (:x2 to))
|
||||||
|
(map (fn [[start end]] [start y end y]))))
|
||||||
|
|
||||||
|
v-lines (let [x (+ (:x from) (/ (:width from) 2))]
|
||||||
|
(->> (calculate-distance-lines (:y1 from) (:y2 from) (:y1 to) (:y2 to))
|
||||||
|
(map (fn [[start end]] [x start x end]))))
|
||||||
|
|
||||||
|
lines (d/concat [] v-lines h-lines)
|
||||||
|
|
||||||
|
distance-pill-width (/ distance-pill-width zoom)
|
||||||
|
distance-pill-height (/ distance-pill-height zoom)
|
||||||
|
distance-line-stroke (/ distance-line-stroke zoom)
|
||||||
|
font-size (/ font-size zoom)
|
||||||
|
text-padding (/ 3 zoom)
|
||||||
|
distance-border-radius (/ distance-border-radius zoom)]
|
||||||
|
|
||||||
|
(for [[x1 y1 x2 y2] lines]
|
||||||
|
(let [center-x (+ x1 (/ (- x2 x1) 2))
|
||||||
|
center-y (+ y1 (/ (- y2 y1) 2))]
|
||||||
|
[:g.distance-line {:key (str "line-%s-%s-%s-%s" x1 y1 x2 y2)}
|
||||||
|
[:line {:x1 x1
|
||||||
|
:y1 y1
|
||||||
|
:x2 x2
|
||||||
|
:y2 y2
|
||||||
|
:style {:stroke distance-color
|
||||||
|
:stroke-width distance-line-stroke}}]
|
||||||
|
[:rect {:x (- center-x (/ distance-pill-width 2))
|
||||||
|
:y (- center-y (/ distance-pill-height 2))
|
||||||
|
:rx distance-border-radius
|
||||||
|
:ry distance-border-radius
|
||||||
|
:width distance-pill-width
|
||||||
|
:height distance-pill-height
|
||||||
|
:style {:fill distance-color}}]
|
||||||
|
|
||||||
|
[:text {:x center-x
|
||||||
|
:y (+ center-y text-padding)
|
||||||
|
:rx distance-border-radius
|
||||||
|
:ry distance-border-radius
|
||||||
|
:text-anchor "middle"
|
||||||
|
:width distance-pill-width
|
||||||
|
:height distance-pill-height
|
||||||
|
:style {:fill distance-text-color
|
||||||
|
:font-size font-size}}
|
||||||
|
(str (mth/round
|
||||||
|
(gpt/distance (gpt/point x1 y1) (gpt/point x2 y2))) "px")]]))))
|
||||||
|
|
||||||
(mf/defc selection-feedback [{:keys [frame]}]
|
(mf/defc selection-feedback [{:keys [frame]}]
|
||||||
(let [hover-shapes-ref (mf/use-memo (make-hover-shapes-iref))
|
(let [zoom (mf/deref selected-zoom)
|
||||||
hover-shapes (->> (mf/deref hover-shapes-ref)
|
hover-shapes-ref (mf/use-memo (make-hover-shapes-iref))
|
||||||
(map #(gsh/translate-to-frame % frame)))
|
hover-shape (mf/deref hover-shapes-ref)
|
||||||
|
|
||||||
selected-shapes-ref (mf/use-memo (make-selected-shapes-iref))
|
selected-shapes-ref (mf/use-memo (make-selected-shapes-iref))
|
||||||
selected-shapes (->> (mf/deref selected-shapes-ref)
|
selected-shapes (->> (mf/deref selected-shapes-ref)
|
||||||
(map #(gsh/translate-to-frame % frame)))]
|
(map #(gsh/translate-to-frame % frame)))
|
||||||
|
|
||||||
[:*
|
selrect (gsh/selection-rect selected-shapes)]
|
||||||
(for [shape hover-shapes]
|
|
||||||
[:& selection-rect {:shape shape}])
|
(when (seq selected-shapes)
|
||||||
|
[:g.measurement-feedback {:pointer-events "none"}
|
||||||
|
[:g.selected-shapes
|
||||||
|
[:& selection-guides {:selrect selrect :frame frame :zoom zoom}]
|
||||||
|
[:& selection-rect {:type :selection :selrect selrect :zoom zoom}]
|
||||||
|
[:& size-display {:selrect selrect :zoom zoom}]]
|
||||||
|
|
||||||
|
(if (and (not-empty selected-shapes) (not hover-shape))
|
||||||
|
[:g.hover-shapes
|
||||||
|
[:& distance-display {:from (frame->selrect frame) :to selrect :zoom zoom}]]
|
||||||
|
|
||||||
|
(let [hover-selrect (-> hover-shape (gsh/translate-to-frame frame) :selrect)]
|
||||||
|
[:g.hover-shapes
|
||||||
|
[:& selection-rect {:type :hover :selrect hover-selrect :zoom zoom}]
|
||||||
|
[:& size-display {:selrect hover-selrect :zoom zoom}]
|
||||||
|
[:& distance-display {:from hover-selrect :to selrect :zoom zoom}]]))])))
|
||||||
|
|
||||||
(for [shape selected-shapes]
|
|
||||||
[:& selection-rect {:shape shape}])]))
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue