0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-10 14:01:29 -05:00

New focus mode in workspace

This commit is contained in:
alonso.torres 2022-02-23 16:30:01 +01:00 committed by Andrey Antukh
parent dc18a6c3bc
commit 78d7fe3e10
26 changed files with 484 additions and 179 deletions

View file

@ -12,6 +12,7 @@
- Add new invitations section [Taiga #2797](https://tree.taiga.io/project/penpot/us/2797)
- Ability to add multiple fills to a shape [Taiga #1394](https://tree.taiga.io/project/penpot/us/1394)
- Team members redesign [Taiga #2283](https://tree.taiga.io/project/penpot/us/2283)
- New focus mode in workspace [Taiga #2748](https://tree.taiga.io/project/penpot/us/2748)
- Changed text shapes to be displayed as natives SVG text elements [Taiga #2759](https://tree.taiga.io/project/penpot/us/2759)
- Texts now can have strokes, multiple fills and can be used as masks

View file

@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.pages.changes :as changes]
[app.common.pages.common :as common]
[app.common.pages.focus :as focus]
[app.common.pages.indices :as indices]
[app.common.pages.init :as init]))
@ -19,6 +20,11 @@
(dm/export common/default-color)
(dm/export common/component-sync-attrs)
;; Focus
(dm/export focus/focus-objects)
(dm/export focus/filter-not-focus)
(dm/export focus/is-in-focus?)
;; Indices
(dm/export indices/calculate-z-index)
(dm/export indices/update-z-index)
@ -36,3 +42,4 @@
(dm/export init/make-minimal-shape)
(dm/export init/make-minimal-group)
(dm/export init/empty-file-data)

View file

@ -0,0 +1,51 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.common.pages.focus
(:require
[app.common.data :as d]
[app.common.pages.helpers :as cph]
[app.common.pages.indices :as cpi]
[app.common.uuid :as uuid]))
(defn focus-objects
[objects focus]
(let [[ids-with-children z-index]
(when (d/not-empty? focus)
[(into (conj focus uuid/zero)
(mapcat (partial cph/get-children-ids objects))
focus)
(cpi/calculate-z-index objects)])
sort-by-z-index
(fn [coll]
(->> coll (sort-by (fn [a b] (- (get z-index a) (get z-index b))))))]
(cond-> objects
(some? ids-with-children)
(-> (select-keys ids-with-children)
(assoc-in [uuid/zero :shapes] (sort-by-z-index focus))))))
(defn filter-not-focus
[objects focus ids]
(let [focused-ids
(when (d/not-empty? focus)
(into focus
(mapcat (partial cph/get-children-ids objects))
focus))]
(if (some? focused-ids)
(into (d/ordered-set)
(filter #(contains? focused-ids %))
ids)
ids)))
(defn is-in-focus?
[objects focus id]
(d/seek
#(contains? focus %)
(cph/get-parents-seq objects id)))

View file

@ -74,6 +74,16 @@
[objects id]
(-> objects (get id) :parent-id))
(defn get-parents-seq
[objects shape-id]
(cond
(nil? shape-id)
nil
:else
(lazy-seq (cons shape-id (get-parents-seq objects (get-in objects [shape-id :parent-id]))))))
(defn get-parent-ids
"Returns a vector of parents of the specified shape."
[objects shape-id]
@ -463,3 +473,4 @@
[path name]
(let [path-split (split-path path)]
(merge-path-item (first path-split) name)))

View file

@ -113,6 +113,43 @@
}
}
}
& .focus-title {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: auto 1fr auto;
grid-column-gap: 8px;
& .back-button {
cursor: pointer;
background: none;
border: none;
transform: rotate(180deg);
padding: 0;
&:hover {
svg {
fill: $color-primary;
}
}
& svg {
fill: $color-white;
}
}
& .focus-mode {
color: $color-primary;
border: 1px solid $color-primary;
border-radius: 3px;
font-size: 10px;
text-transform: uppercase;
padding: 0px 4px;
display: flex;
align-items: center;
}
}
}
.assets-bar .tool-window {

View file

@ -10,7 +10,6 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.align :as gal]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
@ -44,6 +43,7 @@
[app.main.data.workspace.svg-upload :as svg]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.zoom :as dwz]
[app.main.repo :as rp]
[app.main.streams :as ms]
[app.main.worker :as uw]
@ -294,7 +294,7 @@
exit-workspace? (not= :workspace (get-in state [:route :data :name]))]
(cond-> (assoc-in state [:workspace-cache page-id] local)
:always
(dissoc :current-page-id :workspace-local :trimmed-page)
(dissoc :current-page-id :workspace-local :trimmed-page :workspace-focus-selected)
exit-workspace?
(dissoc :workspace-drawing))))))
@ -478,11 +478,6 @@
;; --- Viewport Sizing
(declare increase-zoom)
(declare decrease-zoom)
(declare set-zoom)
(declare zoom-to-fit-all)
(defn initialize-viewport
[{:keys [width height] :as size}]
(letfn [(update* [{:keys [vport] :as local}]
@ -612,114 +607,8 @@
(-> state
(update :workspace-local dissoc :panning)))))
(defn start-zooming [pt]
(ptk/reify ::start-zooming
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (->> stream (rx/filter (ptk/type? ::finish-zooming)))]
(when-not (get-in state [:workspace-local :zooming])
(rx/concat
(rx/of #(-> % (assoc-in [:workspace-local :zooming] true)))
(->> stream
(rx/filter ms/pointer-event?)
(rx/filter #(= :delta (:source %)))
(rx/map :pt)
(rx/take-until stopper)
(rx/map (fn [delta]
(let [scale (+ 1 (/ (:y delta) 100))] ;; this number may be adjusted after user testing
(set-zoom pt scale)))))))))))
(defn finish-zooming []
(ptk/reify ::finish-zooming
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-local dissoc :zooming)))))
;; --- Zoom Management
(defn- impl-update-zoom
[{:keys [vbox] :as local} center zoom]
(let [new-zoom (if (fn? zoom) (zoom (:zoom local)) zoom)
old-zoom (:zoom local)
center (if center center (gsh/center-rect vbox))
scale (/ old-zoom new-zoom)
mtx (gmt/scale-matrix (gpt/point scale) center)
vbox' (gsh/transform-rect vbox mtx)]
(-> local
(assoc :zoom new-zoom)
(update :vbox merge (select-keys vbox' [:x :y :width :height])))))
(defn increase-zoom
[center]
(ptk/reify ::increase-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % center (fn [z] (min (* z 1.3) 200)))))))
(defn decrease-zoom
[center]
(ptk/reify ::decrease-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01)))))))
(defn set-zoom
[center scale]
(ptk/reify ::set-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % center (fn [z] (-> (* z scale)
(max 0.01)
(min 200))))))))
(def reset-zoom
(ptk/reify ::reset-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % nil 1)))))
(def zoom-to-fit-all
(ptk/reify ::zoom-to-fit-all
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
shapes (cph/get-immediate-children objects)
srect (gsh/selection-rect shapes)]
(if (empty? shapes)
state
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
zoom (/ (:width vport) (:width srect))]
(-> local
(assoc :zoom zoom)
(update :vbox merge srect))))))))))
(def zoom-to-selected-shape
(ptk/reify ::zoom-to-selected-shape
ptk/UpdateEvent
(update [_ state]
(let [selected (wsh/lookup-selected state)]
(if (empty? selected)
state
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
srect (->> selected
(map #(get objects %))
(gsh/selection-rect))]
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
zoom (/ (:width vport) (:width srect))]
(-> local
(assoc :zoom zoom)
(update :vbox merge srect)))))))))))
;; --- Update Shape Attrs
@ -1887,21 +1776,25 @@
(dm/export dwp/clone-media-object)
(dm/export dwc/image-uploaded)
;; Selection
(dm/export dws/select-shape)
(dm/export dws/deselect-shape)
(dm/export dws/select-all)
(dm/export dws/deselect-all)
;; Common
(dm/export dwc/add-shape)
(dm/export dwc/clear-edition-mode)
(dm/export dwc/select-shapes)
(dm/export dws/shift-select-shapes)
(dm/export dwc/start-edition-mode)
;; Drawing
(dm/export dwd/select-for-drawing)
;; Selection
(dm/export dws/toggle-focus-mode)
(dm/export dws/deselect-all)
(dm/export dws/deselect-shape)
(dm/export dws/duplicate-selected)
(dm/export dws/handle-area-selection)
(dm/export dws/select-all)
(dm/export dws/select-inside-group)
(dm/export dwd/select-for-drawing)
(dm/export dwc/clear-edition-mode)
(dm/export dwc/add-shape)
(dm/export dwc/start-edition-mode)
(dm/export dws/select-shape)
(dm/export dws/shift-select-shapes)
;; Groups
@ -1924,3 +1817,11 @@
(dm/export dwgu/remove-guide)
(dm/export dwgu/set-hover-guide)
;; Zoom
(dm/export dwz/reset-zoom)
(dm/export dwz/zoom-to-selected-shape)
(dm/export dwz/start-zooming)
(dm/export dwz/finish-zooming)
(dm/export dwz/zoom-to-fit-all)
(dm/export dwz/decrease-zoom)
(dm/export dwz/increase-zoom)

View file

@ -60,6 +60,7 @@
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
layout (get state :workspace-layout)
focus (:workspace-focus-selected state)
zoom (get-in state [:workspace-local :zoom] 1)
frames (cph/get-frames objects)
@ -80,7 +81,7 @@
(rx/of #(assoc-in state [:workspace-drawing :object] shape))
;; Initial SNAP
(->> (snap/closest-snap-point page-id [shape] layout zoom initial)
(->> (snap/closest-snap-point page-id [shape] objects layout zoom focus initial)
(rx/map move-drawing))
(->> ms/mouse-position
@ -88,7 +89,7 @@
(rx/with-latest vector ms/mouse-position-shift)
(rx/switch-map
(fn [[point :as current]]
(->> (snap/closest-snap-point page-id [shape] layout zoom point)
(->> (snap/closest-snap-point page-id [shape] objects layout zoom focus point)
(rx/map #(conj current %)))))
(rx/map
(fn [[_ shift? point]]

View file

@ -10,6 +10,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@ -20,10 +21,13 @@
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.zoom :as dwz]
[app.main.refs :as refs]
[app.main.streams :as ms]
[app.main.worker :as uw]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[linked.set :as lks]
[potok.core :as ptk]))
@ -161,7 +165,12 @@
(ptk/reify ::select-shapes
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selected] ids))
(let [objects (wsh/lookup-page-objects state)
focus (:workspace-focus-selected state)
ids (if (d/not-empty? focus)
(cp/filter-not-focus objects focus ids)
ids)]
(assoc-in state [:workspace-local :selected] ids)))
ptk/WatchEvent
(watch [_ state _]
@ -173,8 +182,9 @@
(ptk/reify ::select-all
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
(let [focus (:workspace-focus-selected state)
objects (-> (wsh/lookup-page-objects state)
(cp/focus-objects focus))
selected (let [frame-ids (into #{} (comp
(map (d/getf objects))
@ -484,8 +494,8 @@
id-duplicated (when (= (count selected) 1) (first selected))]
(rx/of (select-shapes selected)
(dch/commit-changes changes)
(rx/of (dch/commit-changes changes)
(select-shapes selected)
(memorize-duplicated id-original id-duplicated)))))))
(defn change-hover-state
@ -495,3 +505,54 @@
(update [_ state]
(let [hover-value (if value #{id} #{})]
(assoc-in state [:workspace-local :hover] hover-value)))))
(defn update-focus-shapes
[added removed]
(ptk/reify ::update-focus-shapes
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
focus (-> (:workspace-focus-selected state)
(set/union added)
(set/difference removed))
focus (cph/clean-loops objects focus)]
(-> state
(assoc :workspace-focus-selected focus))))))
(defn toggle-focus-mode
[]
(ptk/reify ::toggle-focus-mode
ptk/UpdateEvent
(update [_ state]
(let [selected (wsh/lookup-selected state)]
(cond-> state
(and (empty? (:workspace-focus-selected state))
(d/not-empty? selected))
(assoc :workspace-focus-selected selected)
(d/not-empty? (:workspace-focus-selected state))
(dissoc :workspace-focus-selected))))
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (rx/filter #(or (= ::toggle-focus-mode (ptk/type %))
(= :app.main.data.workspace/finalize-page (ptk/type %))) stream)]
(when (d/not-empty? (:workspace-focus-selected state))
(rx/merge
(rx/of dwz/zoom-to-selected-shape
(deselect-all))
(->> (rx/from-atom refs/workspace-page-objects {:emit-current-value? true})
(rx/take-until stopper)
(rx/map (comp set keys))
(rx/buffer 2 1)
(rx/merge-map
(fn [[old-keys new-keys]]
(let [removed (set/difference old-keys new-keys)
added (set/difference new-keys old-keys)]
(if (or (d/not-empty? added) (d/not-empty? removed))
(rx/of (update-focus-shapes added removed))
(rx/empty))))))))))))

View file

@ -352,11 +352,13 @@
:command (ds/c-mod "alt+g")
:fn #(st/emit! (dw/create-artboard-from-selection))}
:hide-ui {:tooltip "\\"
:command "\\"
:fn #(st/emit! (dw/toggle-layout-flags :hide-ui))}
:hide-ui {:tooltip "\\"
:command "\\"
:fn #(st/emit! (dw/toggle-layout-flags :hide-ui))}
})
:toggle-focus-mode {:command "f"
:tooltip "F"
:fn #(st/emit! (dw/toggle-focus-mode))}})
(def opacity-shortcuts
(into {} (->>

View file

@ -375,6 +375,7 @@
stoper (rx/filter ms/mouse-up? stream)
layout (:workspace-layout state)
page-id (:current-page-id state)
focus (:workspace-focus-selected state)
zoom (get-in state [:workspace-local :zoom] 1)
objects (wsh/lookup-page-objects state page-id)
resizing-shapes (map #(get objects %) ids)
@ -387,7 +388,7 @@
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
(rx/map normalize-proportion-lock)
(rx/switch-map (fn [[point _ _ :as current]]
(->> (snap/closest-snap-point page-id resizing-shapes layout zoom point)
(->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point)
(rx/map #(conj current %)))))
(rx/mapcat (partial resize shape initial-position layout))
(rx/take-until stoper))
@ -509,7 +510,6 @@
(rx/of (start-move initial selected)))))
(rx/take-until stopper)))))))
(defn- start-move-duplicate
[from-position]
(ptk/reify ::start-move-duplicate
@ -544,6 +544,7 @@
stopper (rx/filter ms/mouse-up? stream)
layout (get state :workspace-layout)
zoom (get-in state [:workspace-local :zoom] 1)
focus (:workspace-focus-selected state)
fix-axis (fn [[position shift?]]
(let [delta (gpt/to-vec from-position position)]
@ -564,10 +565,10 @@
(rx/throttle 20)
(rx/switch-map
(fn [pos]
(->> (snap/closest-snap-move page-id shapes objects layout zoom pos)
(->> (snap/closest-snap-move page-id shapes objects layout zoom focus pos)
(rx/map #(vector pos %)))))))]
(if (empty? shapes)
(rx/empty)
(rx/of (finish-transform))
(rx/concat
(->> position
(rx/with-latest vector snap-delta)

View file

@ -0,0 +1,123 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.data.workspace.zoom
(:require
[app.common.geom.align :as gal]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.streams :as ms]
[beicon.core :as rx]
[potok.core :as ptk]))
(defn- impl-update-zoom
[{:keys [vbox] :as local} center zoom]
(let [new-zoom (if (fn? zoom) (zoom (:zoom local)) zoom)
old-zoom (:zoom local)
center (if center center (gsh/center-rect vbox))
scale (/ old-zoom new-zoom)
mtx (gmt/scale-matrix (gpt/point scale) center)
vbox' (gsh/transform-rect vbox mtx)]
(-> local
(assoc :zoom new-zoom)
(update :vbox merge (select-keys vbox' [:x :y :width :height])))))
(defn increase-zoom
[center]
(ptk/reify ::increase-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % center (fn [z] (min (* z 1.3) 200)))))))
(defn decrease-zoom
[center]
(ptk/reify ::decrease-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01)))))))
(defn set-zoom
[center scale]
(ptk/reify ::set-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % center (fn [z] (-> (* z scale)
(max 0.01)
(min 200))))))))
(def reset-zoom
(ptk/reify ::reset-zoom
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
#(impl-update-zoom % nil 1)))))
(def zoom-to-fit-all
(ptk/reify ::zoom-to-fit-all
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
shapes (cph/get-immediate-children objects)
srect (gsh/selection-rect shapes)]
(if (empty? shapes)
state
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
zoom (/ (:width vport) (:width srect))]
(-> local
(assoc :zoom zoom)
(update :vbox merge srect))))))))))
(def zoom-to-selected-shape
(ptk/reify ::zoom-to-selected-shape
ptk/UpdateEvent
(update [_ state]
(let [selected (wsh/lookup-selected state)]
(if (empty? selected)
state
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
srect (->> selected
(map #(get objects %))
(gsh/selection-rect))]
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
zoom (/ (:width vport) (:width srect))]
(-> local
(assoc :zoom zoom)
(update :vbox merge srect)))))))))))
(defn start-zooming [pt]
(ptk/reify ::start-zooming
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (->> stream (rx/filter (ptk/type? ::finish-zooming)))]
(when-not (get-in state [:workspace-local :zooming])
(rx/concat
(rx/of #(-> % (assoc-in [:workspace-local :zooming] true)))
(->> stream
(rx/filter ms/pointer-event?)
(rx/filter #(= :delta (:source %)))
(rx/map :pt)
(rx/take-until stopper)
(rx/map (fn [delta]
(let [scale (+ 1 (/ (:y delta) 100))] ;; this number may be adjusted after user testing
(set-zoom pt scale)))))))))))
(defn finish-zooming []
(ptk/reify ::finish-zooming
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-local dissoc :zooming)))))

View file

@ -284,6 +284,9 @@
(mapv (d/getf objects) shapes)))]
(l/derived selector selected-data =)))
(def workspace-focus-selected
(l/derived :workspace-focus-selected st/state))
;; ---- Viewer refs
(def viewer-file

View file

@ -10,6 +10,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.uuid :refer [zero]]
[app.main.refs :as refs]
@ -32,21 +33,27 @@
(defn make-remove-snap
"Creates a filter for the snap data. Used to disable certain layouts"
[layout filter-shapes]
[layout filter-shapes objects focus]
(fn [{:keys [type id]}]
(fn [{:keys [type id frame-id]}]
(cond
(= type :layout)
(or (not (contains? layout :display-grid))
(not (contains? layout :snap-grid)))
(not (contains? layout :snap-grid))
(and (d/not-empty? focus)
(not (contains? focus id))))
(= type :guide)
(or (not (contains? layout :rules))
(not (contains? layout :snap-guides)))
(not (contains? layout :snap-guides))
(and (d/not-empty? focus)
(not (contains? focus frame-id))))
:else
(or (contains? filter-shapes id)
(not (contains? layout :dynamic-alignment))))))
(not (contains? layout :dynamic-alignment))
(and (d/not-empty? focus)
(not (cp/is-in-focus? objects focus id)))))))
(defn- flatten-to-points
[query-result]
@ -223,19 +230,19 @@
(rx/map snap->vector))))
(defn closest-snap-point
[page-id shapes layout zoom point]
[page-id shapes objects layout zoom focus point]
(let [frame-id (snap-frame-id shapes)
filter-shapes (into #{} (map :id shapes))
remove-snap? (make-remove-snap layout filter-shapes)]
remove-snap? (make-remove-snap layout filter-shapes objects focus)]
(->> (closest-snap page-id frame-id [point] remove-snap? zoom)
(rx/map #(or % (gpt/point 0 0)))
(rx/map #(gpt/add point %)))))
(defn closest-snap-move
[page-id shapes objects layout zoom movev]
[page-id shapes objects layout zoom focus movev]
(let [frame-id (snap-frame-id shapes)
filter-shapes (into #{} (map :id shapes))
remove-snap? (make-remove-snap layout filter-shapes)
remove-snap? (make-remove-snap layout filter-shapes objects focus)
shape (if (> (count shapes) 1)
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))

View file

@ -7,7 +7,9 @@
(ns app.main.ui.hooks
"A collection of general purpose react hooks."
(:require
[app.common.pages :as cp]
[app.main.data.shortcuts :as dsc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.util.dom :as dom]
[app.util.dom.dnd :as dnd]
@ -235,3 +237,14 @@
(let [ret (effect-fn)]
(when (fn? ret) (ret)))
(mf/use-effect deps effect-fn)))
(defn with-focus-objects
([objects]
(let [focus (mf/deref refs/workspace-focus-selected)]
(with-focus-objects objects focus)))
([objects focus]
(let [objects (mf/use-memo
(mf/deps focus objects)
#(cp/focus-objects objects focus))]
objects)))

View file

@ -212,6 +212,17 @@
:on-click do-create-artboard-from-selection}]
[:& menu-separator]])]))
(mf/defc context-focus-mode-menu
[{:keys []}]
(let [focus (mf/deref refs/workspace-focus-selected)
do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))]
[:& menu-entry {:title (if (empty? focus)
(tr "workspace.focus.focus-on")
(tr "workspace.focus.focus-off"))
:shortcut (sc/get-tooltip :toggle-focus-mode)
:on-click do-toggle-focus-mode}]))
(mf/defc context-menu-path
[{:keys [shapes disable-flatten? disable-booleans?]}]
(let [multiple? (> (count shapes) 1)
@ -426,6 +437,7 @@
[:> context-menu-layer-position props]
[:> context-menu-flip props]
[:> context-menu-group props]
[:> context-focus-mode-menu props]
[:> context-menu-path props]
[:> context-menu-layer-options props]
[:> context-menu-prototype props]
@ -434,15 +446,23 @@
(mf/defc viewport-context-menu
[]
(let [do-paste (st/emitf dw/paste)
do-hide-ui (st/emitf (dw/toggle-layout-flags :hide-ui))]
(let [focus (mf/deref refs/workspace-focus-selected)
do-paste (st/emitf dw/paste)
do-hide-ui (st/emitf (dw/toggle-layout-flags :hide-ui))
do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))]
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.paste")
:shortcut (sc/get-tooltip :paste)
:on-click do-paste}]
[:& menu-entry {:title (tr "workspace.shape.menu.hide-ui")
:shortcut (sc/get-tooltip :hide-ui)
:on-click do-hide-ui}]]))
:on-click do-hide-ui}]
(when (d/not-empty? focus)
[:& menu-entry {:title (tr "workspace.focus.focus-off")
:shortcut (sc/get-tooltip :toggle-focus-mode)
:on-click do-toggle-focus-mode}])]))
(mf/defc context-menu
[]

View file

@ -6,6 +6,7 @@
(ns app.main.ui.workspace.shapes.frame
(:require
[app.common.colors :as cc]
[app.common.data :as d]
[app.common.pages.helpers :as cph]
[app.main.ui.hooks :as hooks]
@ -41,7 +42,7 @@
(let [{:keys [x y width height fill-color] :as shape} (obj/get props "shape")]
(if (some? (:thumbnail shape))
[:& frame/frame-thumbnail {:shape shape}]
[:rect {:x x :y y :width width :height height :style {:fill (or fill-color "var(--color-white)")}}])))
[:rect.frame-thumbnail {:x x :y y :width width :height height :style {:fill (or fill-color cc/white)}}])))
(defn custom-deferred
[component]

View file

@ -17,6 +17,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.timers :as ts]
@ -314,11 +315,23 @@
(mf/defc layers-toolbox
{:wrap [mf/memo]}
[]
(let [page (mf/deref refs/workspace-page)]
(let [page (mf/deref refs/workspace-page)
focus (mf/deref refs/workspace-focus-selected)
objects (hooks/with-focus-objects (:objects page) focus)
title (when (= 1 (count focus)) (get-in objects [(first focus) :name]))]
[:div#layers.tool-window
[:div.tool-window-bar
[:div.tool-window-icon i/layers]
[:span (:name page)]]
(if (d/not-empty? focus)
[:div.tool-window-bar
[:div.focus-title
[:button.back-button
{:on-click #(st/emit! (dw/toggle-focus-mode))}
i/arrow-slide ]
[:span (or title (tr "workspace.focus.selection"))]
[:div.focus-mode (tr "workspace.focus.focus-mode")]]]
[:div.tool-window-bar
[:span (:name page)]])
[:div.tool-window-content
[:& layers-tree-wrapper {:key (:id page)
:objects (:objects page)}]]]))
:objects objects}]]]))

View file

@ -11,6 +11,7 @@
[app.common.geom.shapes :as gsh]
[app.main.refs :as refs]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :as ui-hooks]
[app.main.ui.measurements :as msr]
[app.main.ui.shapes.embed :as embed]
[app.main.ui.shapes.export :as use]
@ -65,7 +66,9 @@
;; DEREFS
drawing (mf/deref refs/workspace-drawing)
options (mf/deref refs/workspace-page-options)
base-objects (mf/deref refs/workspace-page-objects)
focus (mf/deref refs/workspace-focus-selected)
base-objects (-> (mf/deref refs/workspace-page-objects)
(ui-hooks/with-focus-objects focus))
modifiers (mf/deref refs/workspace-modifiers)
objects-modified (mf/with-memo [base-objects modifiers]
(gsh/merge-modifiers base-objects modifiers))
@ -169,7 +172,7 @@
(hooks/setup-viewport-size viewport-ref)
(hooks/setup-cursor cursor alt? ctrl? space? panning drawing-tool drawing-path? node-editing?)
(hooks/setup-keyboard alt? ctrl? space?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected ctrl? hover hover-ids @hover-disabled? focus zoom)
(hooks/setup-viewport-modifiers modifiers base-objects)
(hooks/setup-shortcuts node-editing? drawing-path?)
(hooks/setup-active-frames base-objects vbox hover active-frames)
@ -253,8 +256,12 @@
[:& outline/shape-outlines
{:objects base-objects
:selected selected
:hover (when (or @ctrl? (not= :frame (:type @hover)))
#{(or @frame-hover (:id @hover))})
:hover (cond
(and @hover (or @ctrl? (not= :frame (:type @hover))))
#{(:id @hover)}
@frame-hover
#{@frame-hover})
:edition edition
:zoom zoom}])
@ -313,7 +320,8 @@
[:& frame-grid/frame-grid
{:zoom zoom
:selected selected
:transform transform}])
:transform transform
:focus focus}])
(when show-pixel-grid?
[:& widgets/pixel-grid
@ -329,6 +337,7 @@
:page-id page-id
:selected selected
:objects base-objects
:focus focus
:modifiers modifiers}])
(when show-snap-distance?

View file

@ -80,13 +80,14 @@
(mf/defc frame-grid
{::mf/wrap [mf/memo]}
[{:keys [zoom transform selected]}]
[{:keys [zoom transform selected focus]}]
(let [frames (mf/deref refs/workspace-frames)
moving (when (= :move transform) selected)
is-moving? #(contains? moving (:id %))]
[:g.grid-display {:style {:pointer-events "none"}}
(for [frame (remove is-moving? frames)]
[:& grid-display-frame {:key (str "grid-" (:id frame))
:zoom zoom
:frame (gsh/transform-shape frame)}])]))
(when (or (empty? focus) (contains? focus (:id frame)))
[:& grid-display-frame {:key (str "grid-" (:id frame))
:zoom zoom
:frame (gsh/transform-shape frame)}]))]))

View file

@ -428,6 +428,8 @@
(vals)
(filter (guide-inside-vbox? vbox))))
focus (mf/deref refs/workspace-focus-selected)
hover-frame-ref (mf/use-ref nil)
;; We use the ref to not redraw every guide everytime the hovering frame change
@ -464,12 +466,15 @@
:disabled-guides? disabled-guides?}]
(for [current guides]
[:& guide {:key (str "guide-" (:id current))
:guide current
:vbox vbox
:zoom zoom
:frame-modifier (get modifiers (:frame-id current))
:get-hover-frame get-hover-frame
:on-guide-change on-guide-change
:disabled-guides? disabled-guides?}])]))
(when (or (nil? (:frame-id current))
(empty? focus)
(contains? focus (:frame-id current)))
[:& guide {:key (str "guide-" (:id current))
:guide current
:vbox vbox
:zoom zoom
:frame-modifier (get modifiers (:frame-id current))
:get-hover-frame get-hover-frame
:on-guide-change on-guide-change
:disabled-guides? disabled-guides?}]))]))

View file

@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.main.data.shortcuts :as dsc]
[app.main.data.workspace :as dw]
@ -97,13 +98,14 @@
(some #(cph/is-parent? objects % group-id))
(not))))
(defn setup-hover-shapes [page-id move-stream objects transform selected ctrl? hover hover-ids hover-disabled? zoom]
(defn setup-hover-shapes [page-id move-stream objects transform selected ctrl? hover hover-ids hover-disabled? focus zoom]
(let [;; We use ref so we don't recreate the stream on a change
zoom-ref (mf/use-ref zoom)
ctrl-ref (mf/use-ref @ctrl?)
transform-ref (mf/use-ref nil)
selected-ref (mf/use-ref selected)
hover-disabled-ref (mf/use-ref hover-disabled?)
focus-ref (mf/use-ref focus)
query-point
(mf/use-callback
@ -157,6 +159,10 @@
(mf/deps hover-disabled?)
#(mf/set-ref-val! hover-disabled-ref hover-disabled?))
(mf/use-effect
(mf/deps focus)
#(mf/set-ref-val! focus-ref focus))
(hooks/use-stream
over-shapes-stream
(mf/deps page-id objects @ctrl?)
@ -166,6 +172,7 @@
(contains? #{:group :bool} (get-in objects [id :type])))
selected (mf/ref-val selected-ref)
focus (mf/ref-val focus-ref)
remove-xfm (mapcat #(cph/get-parent-ids objects %))
remove-id? (cond-> (into #{} remove-xfm selected)
@ -177,6 +184,8 @@
hover-shape (->> ids
(filter (comp not remove-id?))
(filter #(or (empty? focus)
(cp/is-in-focus? objects focus %)))
(first)
(get objects))]
(reset! hover hover-shape)

View file

@ -151,15 +151,16 @@
(mf/defc snap-points
{::mf/wrap [mf/memo]}
[{:keys [layout zoom objects selected page-id drawing transform modifiers] :as props}]
[{:keys [layout zoom objects selected page-id drawing transform modifiers focus] :as props}]
(us/assert set? selected)
(let [shapes (into [] (keep (d/getf objects)) selected)
filter-shapes
(into selected (mapcat #(cph/get-children-ids objects %)) selected)
remove-snap? (mf/with-memo [layout filter-shapes]
(snap/make-remove-snap layout filter-shapes))
remove-snap?
(mf/with-memo [layout filter-shapes objects focus]
(snap/make-remove-snap layout filter-shapes objects focus))
shapes (if drawing [drawing] shapes)]
(when (or drawing transform)

View file

@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport.widgets
(:require
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
@ -165,7 +166,8 @@
[:g.frame-titles
(for [frame frames]
[:& frame-title {:frame frame
[:& frame-title {:key (dm/str "frame-title-" (:id frame))
:frame frame
:selected? (contains? selected (:id frame))
:zoom zoom
:show-artboard-names? show-artboard-names?

View file

@ -107,6 +107,7 @@
(mapv #(array-map
:type :guide
:id (:id guide)
:frame-id (:frame-id guide)
:pt %)))]
(if-let [frame-id (:frame-id guide)]
;; Guide inside frame, we add the information only on that frame

View file

@ -3543,3 +3543,15 @@ msgstr "Update"
msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
msgid "workspace.focus.selection"
msgstr "Selection"
msgid "workspace.focus.focus-mode"
msgstr "Focus mode"
msgid "workspace.focus.focus-on"
msgstr "Focus on"
msgid "workspace.focus.focus-off"
msgstr "Focus off"

View file

@ -3557,3 +3557,15 @@ msgstr "Actualizar"
msgid "workspace.viewport.click-to-close-path"
msgstr "Pulsar para cerrar la ruta"
msgid "workspace.focus.selection"
msgstr "Selección"
msgid "workspace.focus.focus-mode"
msgstr "Modo foco"
msgid "workspace.focus.focus-on"
msgstr "Activar modo foco"
msgid "workspace.focus.focus-off"
msgstr "Desactivar modo foco"