From 4d90d36225c0853fc324198b6a025f5b8bde4ee5 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 17 Feb 2023 20:36:38 +0100 Subject: [PATCH] :sparkles: Add visualization and mouse control to paddings in frames with layout --- CHANGES.md | 1 + frontend/src/app/main/data/workspace.cljs | 13 +- .../app/main/data/workspace/modifiers.cljs | 1 + frontend/src/app/main/refs.cljs | 3 + frontend/src/app/main/streams.cljs | 11 + frontend/src/app/main/ui/hooks/resize.cljs | 2 +- frontend/src/app/main/ui/measurements.cljs | 236 ++++++++++++++++++ .../options/menus/layout_container.cljs | 15 +- .../src/app/main/ui/workspace/viewport.cljs | 17 +- .../app/main/ui/workspace/viewport/hooks.cljs | 5 +- frontend/src/app/util/keyboard.cljs | 1 + 11 files changed, 298 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dbe77b8ac..b2cbf1fce 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Adds more accessibility improvements in dashboard [Taiga #4577](https://tree.taiga.io/project/penpot/us/4577) - Adds paddings and gaps prediction on layout creation [Taiga #4838](https://tree.taiga.io/project/penpot/task/4838) - Add visual feedback when proportionally scaling text elements with **K** [Taiga #3415](https://tree.taiga.io/project/penpot/us/3415) +- Add visualization and mouse control to paddings in frames with layout [Taiga #4839](https://tree.taiga.io/project/penpot/task/4839) ### :bug: Bugs fixed diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 50308b66d..e8a115702 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1544,7 +1544,6 @@ ;; - Respect the distance of the object to the right and bottom in the original frame (gpt/point paste-x paste-y))] [frame-id frame-id delta])) - (empty? page-selected) (let [frame-id (ctst/top-nested-frame page-objects mouse-pos) delta (gpt/subtract mouse-pos orig-pos)] @@ -1904,6 +1903,18 @@ (rx/empty))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Measurements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn set-paddings-selected + [paddings-selected] + (ptk/reify ::set-paddings-selected + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-global :paddings-selected] paddings-selected)))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Orphan Shapes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 1eaf6a688..87691506f 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -430,6 +430,7 @@ :grow-type :layout-item-h-sizing :layout-item-v-sizing + :layout-padding :position-data ]}) ;; We've applied the text-modifier so we can dissoc the temporary data diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 59c91bbd6..df5637cf2 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -279,6 +279,9 @@ (def workspace-read-only? (l/derived :read-only? workspace-global)) +(def workspace-paddings-selected + (l/derived :paddings-selected workspace-global)) + (defn object-by-id [id] (l/derived #(get % id) workspace-page-objects)) diff --git a/frontend/src/app/main/streams.cljs b/frontend/src/app/main/streams.cljs index 9f63c8572..a4dc59d69 100644 --- a/frontend/src/app/main/streams.cljs +++ b/frontend/src/app/main/streams.cljs @@ -205,3 +205,14 @@ (rx/dedupe))] (rx/subscribe-with ob sub) sub)) + +(defonce keyboard-shift + (let [sub (rx/behavior-subject nil) + ob (->> st/stream + (rx/filter keyboard-event?) + (rx/filter kbd/shift-key?) + (rx/filter (comp not kbd/editing?)) + (rx/map #(= :down (:type %))) + (rx/dedupe))] + (rx/subscribe-with ob sub) + sub)) diff --git a/frontend/src/app/main/ui/hooks/resize.cljs b/frontend/src/app/main/ui/hooks/resize.cljs index a14ce5d8e..2dd4231e9 100644 --- a/frontend/src/app/main/ui/hooks/resize.cljs +++ b/frontend/src/app/main/ui/hooks/resize.cljs @@ -54,7 +54,7 @@ on-mouse-move (mf/use-callback - (mf/deps min-val max-val) + (mf/deps min-val max-val negate?) (fn [event] (when (mf/ref-val dragging-ref) (let [start (mf/ref-val start-ref) diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index 19752f0f9..4e80f4647 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -11,8 +11,15 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] + [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid] + [app.main.data.workspace.modifiers :as dwm] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.cursors :as cur] [app.main.ui.formats :as fmt] + [app.util.dom :as dom] [rumext.v2 :as mf])) ;; ------------------------------------------------ @@ -43,6 +50,8 @@ (def distance-pill-width 50) (def distance-pill-height 16) (def distance-line-stroke 1) +(def padding-pill-width 40) +(def padding-pill-height 20) ;; ------------------------------------------------ ;; HELPERS @@ -261,3 +270,230 @@ [:& distance-display {:from hover-selrect :to selected-selrect :zoom zoom :bounds bounds-selrect}]])]))) + +(mf/defc padding-display-pill [{:keys [x y width height font-size value]}] + [:g.distance-pill + [:rect {:x x + :y y + :width width + :height height + :style {:fill distance-color}}] + + [:text {:x (+ x (/ width 2)) + :y (+ y (/ height 2)) + :text-anchor "middle" + :text-align "center" + :dominant-baseline "central" + :style {:fill distance-text-color + :font-size font-size}} + (fmt/format-number value)]]) + + +(mf/defc padding-display [{:keys [frame-id zoom hover-all? hover-v? hover-h? padding-num padding on-mouse-enter on-mouse-leave + rect-data pill-data hover? selected?]}] + (let [resizing? (mf/use-var false) + start (mf/use-var nil) + original-value (mf/use-var 0) + negate? (:resize-negate? rect-data) + axis (:resize-axis rect-data) + + on-pointer-down + (mf/use-callback + (mf/deps frame-id padding-num padding) + (fn [event] + (dom/capture-pointer event) + (reset! resizing? true) + (reset! start (dom/get-client-position event)) + (reset! original-value (:initial-value rect-data)))) + + on-lost-pointer-capture + (mf/use-callback + (mf/deps frame-id padding-num padding) + (fn [event] + (let [padding-type (if (and (= (:p1 padding) (:p3 padding)) (= (:p2 padding) (:p4 padding))) :simple :multiple)] + (dom/release-pointer event) + (reset! resizing? false) + (reset! start nil) + (reset! original-value 0) + (st/emit! (dwm/apply-modifiers) + (dwsl/update-layout [frame-id] {:layout-padding-type padding-type}))))) + + on-mouse-move + (mf/use-callback + (mf/deps frame-id padding-num padding) + (fn [event] + (when @resizing? + (let [pos (dom/get-client-position event) + delta (-> (gpt/to-vec @start pos) + (cond-> negate? gpt/negate) + (get axis)) + val (int (max (+ @original-value (/ delta zoom)) 0)) + layout-padding (cond + hover-all? (assoc padding :p1 val :p2 val :p3 val :p4 val) + hover-v? (assoc padding :p1 val :p3 val) + hover-h? (assoc padding :p2 val :p4 val) + :else (assoc padding padding-num val)) + modifiers (dwm/create-modif-tree [frame-id] (ctm/change-property (ctm/empty) :layout-padding layout-padding))] + (st/emit! (dwm/set-modifiers modifiers))))))] + + + [:* + [:rect.padding-rect {:x (:x rect-data) + :y (:y rect-data) + :width (:width rect-data) + :height (:height rect-data) + :on-mouse-enter on-mouse-enter + :on-mouse-leave on-mouse-leave + :on-pointer-down on-pointer-down + :on-lost-pointer-capture on-lost-pointer-capture + :on-mouse-move on-mouse-move + :style {:fill (if (or hover? selected?) distance-color "none") + :cursor (when (or hover? selected?) + (if (= (:resize-axis rect-data) :x) (cur/resize-ew 0) (cur/resize-ew 90))) + :opacity (if selected? 0.5 0.25)}}] + (when (or hover? selected?) + [:& padding-display-pill pill-data])])) + +(mf/defc padding-rects [{:keys [frame zoom alt? shift?]}] + (let [frame-id (:id frame) + paddings-selected (mf/deref refs/workspace-paddings-selected) + hover (mf/use-var nil) + hover-all? (and (not (nil? @hover)) alt?) + hover-v? (and (or (= @hover :p1) (= @hover :p3)) shift?) + hover-h? (and (or (= @hover :p2) (= @hover :p4)) shift?) + padding (:layout-padding frame) + [width height x1 x2 y1 y2] ((juxt :width :height :x1 :x2 :y1 :y2) (:selrect frame)) + + pill-width (/ padding-pill-width zoom) + pill-height (/ padding-pill-height zoom) + pill-separation (/ pill-width 2) + pill-data {:height pill-height + :width pill-width + :font-size (/ font-size zoom)} + on-mouse-enter #(reset! hover %) + on-mouse-leave #(reset! hover nil) + negate {:p1 (if (:flip-y frame) true false) + :p2 (if (:flip-x frame) true false) + :p3 (if (:flip-y frame) true false) + :p4 (if (:flip-x frame) true false)} + negate (cond-> negate + (= :fix (:layout-item-h-sizing frame)) (assoc :p2 (not (:p2 negate))) + (= :fix (:layout-item-v-sizing frame)) (assoc :p3 (not (:p3 negate)))) + + padding-display-data [{:frame-id frame-id + :zoom zoom + :hover-all? hover-all? + :hover-v? hover-v? + :hover-h? hover-h? + :padding-num :p1 + :padding padding + :on-mouse-enter (partial on-mouse-enter :p1) + :on-mouse-leave on-mouse-leave + :rect-data {:x x1 + :y (if (:flip-y frame) (- y2 (:p1 padding)) y1) + :width width + :height (:p1 padding) + :initial-value (:p1 padding) + :resize-type (if (:flip-y frame) :bottom :top) + :resize-axis :y + :resize-negate? (:p1 negate)} + :pill-data (assoc pill-data + :x (- x1 (* pill-width 1.5)) + :y (if (:flip-y frame) + (+ (- y2 (:p1 padding)) (/ (- (:p1 padding) pill-height) 2)) + (+ y1 (/ (- (:p1 padding) pill-height) 2))) + :value (:p1 padding)) + :hover? (or hover-all? hover-v? (= @hover :p1)) + :selected? (:p1 paddings-selected)} + + {:frame-id frame-id + :zoom zoom + :hover-all? hover-all? + :hover-v? hover-v? + :hover-h? hover-h? + :padding-num :p2 + :padding padding + :on-mouse-enter (partial on-mouse-enter :p2) + :on-mouse-leave on-mouse-leave + :rect-data {:x (if (:flip-x frame) x1 (- x2 (:p2 padding))) + :y y1 + :width (:p2 padding) + :height height + :initial-value (:p2 padding) + :resize-type :left + :resize-axis :x + :resize-negate? (:p2 negate)} + :pill-data (assoc pill-data + :x (if (:flip-x frame) + (+ x1 (/ (- (:p2 padding) pill-width) 2)) + (+ (- x2 (:p2 padding)) (/ (- (:p2 padding) pill-width) 2))) + :y (- y1 (+ pill-height pill-separation)) + :value (:p2 padding)) + :hover? (or hover-all? hover-h? (= @hover :p2)) + :selected? (:p2 paddings-selected)} + + {:frame-id frame-id + :zoom zoom + :hover-all? hover-all? + :hover-v? hover-v? + :hover-h? hover-h? + :padding-num :p3 + :padding padding + :on-mouse-enter (partial on-mouse-enter :p3) + :on-mouse-leave on-mouse-leave + :rect-data {:x x1 + :y (if (:flip-y frame) y1 (- y2 (:p3 padding))) + :width width + :height (:p3 padding) + :initial-value (:p3 padding) + :resize-type :bottom + :resize-axis :y + :resize-negate? (:p3 negate)} + :pill-data (assoc pill-data + :x (- x1 (+ pill-width pill-separation)) + :y (if (:flip-y frame) + (+ y1 (/ (- (:p3 padding) pill-height) 2)) + (+ (- y2 (:p3 padding)) (/ (- (:p3 padding) pill-height) 2))) + :value (:p3 padding)) + :hover? (or hover-all? hover-v? (= @hover :p3)) + :selected? (:p3 paddings-selected)} + + {:frame-id frame-id + :zoom zoom + :hover-all? hover-all? + :hover-v? hover-v? + :hover-h? hover-h? + :padding-num :p4 + :padding padding + :on-mouse-enter (partial on-mouse-enter :p4) + :on-mouse-leave on-mouse-leave + :rect-data {:x (if (:flip-x frame) (- x2 (:p4 padding)) x1) + :y y1 + :width (:p4 padding) + :height height + :initial-value (:p4 padding) + :resize-type (if (:flip-x frame) :right :left) + :resize-axis :x + :resize-negate? (:p4 negate)} + :pill-data (assoc pill-data + :x (if (:flip-x frame) + (+ (- x2 (:p4 padding)) (/ (- (:p4 padding) pill-width) 2)) + (+ x1 (/ (- (:p4 padding) pill-width) 2))) + :y (- y1 (+ pill-height pill-separation)) + :value (:p4 padding)) + :hover? (or hover-all? hover-h? (= @hover :p4)) + :selected? (:p4 paddings-selected)}]] + + [:g.paddings {:pointer-events "visible"} + (for [data padding-display-data] + [:& padding-display data])])) + + +(mf/defc padding + [{:keys [frame zoom paddings-selected alt? shift?]}] + (when frame + [:g.measurement-gaps {:pointer-events "none"} + [:g.hover-shapes + [:& padding-rects {:frame frame :zoom zoom :alt? alt? :shift? shift? :paddings-selected paddings-selected}]]])) + + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index f9ab4558b..7b130f170 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.menus.layout-container (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.main.data.workspace :as udw] [app.main.data.workspace.shape-layout :as dwsl] [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] @@ -204,7 +205,13 @@ (= (dm/get-in values [:layout-padding :p2]) (dm/get-in values [:layout-padding :p4]))) (dm/get-in values [:layout-padding :p2]) - "--")] + "--") + + select-paddings + (fn [p1? p2? p3? p4?] + (st/emit! (udw/set-paddings-selected {:p1 p1? :p2 p2? :p3 p3? :p4 p4?}))) + + select-padding #(select-paddings (= % :p1) (= % :p2) (= % :p3) (= % :p4))] [:div.padding-row (cond @@ -218,6 +225,8 @@ {:placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-change :simple :p1) + :on-focus #(select-paddings true false true false) + :on-blur #(select-paddings false false false false) :value p1}]] [:div.padding-item.tooltip.tooltip-bottom-left @@ -227,6 +236,8 @@ {:placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-change :simple :p2) + :on-focus #(select-paddings false true false true) + :on-blur #(select-paddings false false false false) :value p2}]]] (= padding-type :multiple) @@ -244,6 +255,8 @@ {:placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-change :multiple num) + :on-focus #(select-padding num) + :on-blur #(select-paddings false false false false) :value (num (:layout-padding values))}]]])]) [:div.padding-icons diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a766ea236..f48c82a0b 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -104,6 +104,7 @@ ;; STATE alt? (mf/use-state false) + shift? (mf/use-state false) mod? (mf/use-state false) space? (mf/use-state false) z? (mf/use-state false) @@ -209,12 +210,16 @@ show-rules? (and (contains? layout :rules) (not (contains? layout :hide-ui))) - disabled-guides? (or drawing-tool transform)] + disabled-guides? (or drawing-tool transform) + + show-padding? (and (= (count selected-shapes) 1) + (= (:type (first selected-shapes)) :frame) + (= (:layout (first selected-shapes)) :flex))] (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport? workspace-read-only?) (hooks/setup-viewport-size viewport-ref) (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? workspace-read-only?) - (hooks/setup-keyboard alt? mod? space? z?) + (hooks/setup-keyboard alt? mod? space? z? shift?) (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?) (hooks/setup-viewport-modifiers modifiers base-objects) (hooks/setup-shortcuts node-editing? drawing-path? text-editing?) @@ -368,6 +373,14 @@ :hover-shape @hover :zoom zoom}]) + (when show-padding? + [:& msr/padding + {:frame (first selected-shapes) + :hover @frame-hover + :zoom zoom + :alt? @alt? + :shift? @shift?}]) + [:& widgets/frame-titles {:objects base-objects :selected selected diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index e7270e7b9..1b5f57ad3 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -107,12 +107,13 @@ (when (not= @cursor new-cursor) (reset! cursor new-cursor)))))) -(defn setup-keyboard [alt? mod? space? z?] +(defn setup-keyboard [alt? mod? space? z? shift?] (hooks/use-stream ms/keyboard-alt #(reset! alt? %)) (hooks/use-stream ms/keyboard-mod #((reset! mod? %) (when-not % (reset! z? false)))) ;; In mac after command+z there is no event for the release of the z key (hooks/use-stream ms/keyboard-space #(reset! space? %)) - (hooks/use-stream ms/keyboard-z #(reset! z? %))) + (hooks/use-stream ms/keyboard-z #(reset! z? %)) + (hooks/use-stream ms/keyboard-shift #(reset! shift? %))) (defn group-empty-space? "Given a group `group-id` check if `hover-ids` contains any of its children. If it doesn't means diff --git a/frontend/src/app/util/keyboard.cljs b/frontend/src/app/util/keyboard.cljs index ccb14ddf6..7d4ecf95a 100644 --- a/frontend/src/app/util/keyboard.cljs +++ b/frontend/src/app/util/keyboard.cljs @@ -50,6 +50,7 @@ (def left-arrow? (is-key? "ArrowLeft")) (def right-arrow? (is-key? "ArrowRight")) (def alt-key? (is-key? "Alt")) +(def shift-key? (is-key? "Shift")) (def ctrl-key? (is-key? "Control")) (def meta-key? (is-key? "Meta")) (def comma? (is-key? ","))