0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-09 00:28:20 -05:00

Merge pull request #2114 from penpot/andrewzhurov-2645-hovering-layers-bounding-box

Layer outlines hightlight on hovering
This commit is contained in:
Alejandro 2022-07-28 11:43:43 +02:00 committed by GitHub
commit aa95114860
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 109 deletions

View file

@ -5,12 +5,15 @@
### :boom: Breaking changes & Deprecations
### :sparkles: New features
- Add some cosmetic changes in viewer mode [Taiga #3688](https://tree.taiga.io/project/penpot/us/3688)
- Add cosmetic changes in viewer mode [Taiga #3688](https://tree.taiga.io/project/penpot/us/3688)
- Outline highlights on layer hovering [Taiga #2645](https://tree.taiga.io/project/penpot/us/2645) by @andrewzhurov
### :bug: Bugs fixed
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)
- To @andrewzhurov for many code contributions on this release.
## 1.15.0-beta

View file

@ -35,6 +35,7 @@
[app.main.data.workspace.fix-bool-contents :as fbc]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.guides :as dwgu]
[app.main.data.workspace.highlight :as dwh]
[app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.layout :as layout]
@ -1732,6 +1733,10 @@
(dm/export dws/select-shape)
(dm/export dws/shift-select-shapes)
;; Highlight
(dm/export dwh/highlight-shape)
(dm/export dwh/dehighlight-shape)
;; Groups
(dm/export dwg/mask-group)
(dm/export dwg/unmask-group)

View file

@ -0,0 +1,29 @@
;; 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.highlight
(:require
[app.common.spec :as us]
[clojure.set :as set]
[potok.core :as ptk]))
;; --- Manage shape's highlight status
(defn highlight-shape
[id]
(us/verify ::us/uuid id)
(ptk/reify ::highlight-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :highlighted] set/union #{id}))))
(defn dehighlight-shape
[id]
(us/verify ::us/uuid id)
(ptk/reify ::dehighlight-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :highlighted] disj id))))

View file

@ -251,6 +251,8 @@
[page-id]
(l/derived #(wsh/lookup-page-objects % page-id) st/state =))
;; TODO: Looks like using the `=` comparator can be pretty expensive
;; on large pages, we are using this for some reason?
(def workspace-page-objects
(l/derived wsh/lookup-page-objects st/state =))

View file

@ -8,6 +8,7 @@
"A workspace specific context menu (mouse right click)."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.types.page :as ctp]
[app.main.data.events :as ev]
@ -38,7 +39,7 @@
(dom/stop-propagation event))
(mf/defc menu-entry
[{:keys [title shortcut on-click children selected? icon] :as props}]
[{:keys [title shortcut on-click on-pointer-enter on-pointer-leave on-unmount children selected? icon] :as props}]
(let [submenu-ref (mf/use-ref nil)
hovering? (mf/use-ref false)
@ -48,7 +49,8 @@
(mf/set-ref-val! hovering? true)
(let [submenu-node (mf/ref-val submenu-ref)]
(when (some? submenu-node)
(dom/set-css-property! submenu-node "display" "block")))))
(dom/set-css-property! submenu-node "display" "block")))
(when on-pointer-enter (on-pointer-enter))))
on-pointer-leave
(mf/use-callback
@ -59,7 +61,8 @@
(timers/schedule
200
#(when-not (mf/ref-val hovering?)
(dom/set-css-property! submenu-node "display" "none")))))))
(dom/set-css-property! submenu-node "display" "none")))))
(when on-pointer-leave (on-pointer-leave))))
set-dom-node
(mf/use-callback
@ -68,6 +71,11 @@
(when (and (some? dom) (some? submenu-node))
(dom/set-css-property! submenu-node "top" (str (.-offsetTop dom) "px"))))))]
(mf/use-effect
(mf/deps on-unmount)
(constantly on-unmount))
(if icon
[:li.icon-menu-item {:ref set-dom-node
:on-click on-click
@ -123,19 +131,34 @@
[:& menu-separator]]))
(mf/defc context-menu-layer-position
[{:keys [hover-objs shapes]}]
(let [do-bring-forward #(st/emit! (dw/vertical-order-selected :up))
do-bring-to-front #(st/emit! (dw/vertical-order-selected :top))
do-send-backward #(st/emit! (dw/vertical-order-selected :down))
do-send-to-back #(st/emit! (dw/vertical-order-selected :bottom))
select-shapes (fn [id] #(st/emit! (dws/select-shape id)))]
[{:keys [shapes]}]
(let [do-bring-forward (mf/use-fn #(st/emit! (dw/vertical-order-selected :up)))
do-bring-to-front (mf/use-fn #(st/emit! (dw/vertical-order-selected :top)))
do-send-backward (mf/use-fn #(st/emit! (dw/vertical-order-selected :down)))
do-send-to-back (mf/use-fn #(st/emit! (dw/vertical-order-selected :bottom)))
select-shapes (fn [id] #(st/emit! (dws/select-shape id)))
on-pointer-enter (fn [id] #(st/emit! (dw/highlight-shape id)))
on-pointer-leave (fn [id] #(st/emit! (dw/dehighlight-shape id)))
on-unmount (fn [id] #(st/emit! (dw/dehighlight-shape id)))
;; NOTE: we use deref instead of mf/deref on objects because
;; we really don't want rerender on object changes
hover-ids (deref refs/current-hover-ids)
objects (deref refs/workspace-page-objects)
hover-objs (into [] (keep (d/getf objects)) hover-ids)]
[:*
(when (> (count hover-objs) 1)
[:& menu-entry {:title (tr "workspace.shape.menu.select-layer")}
(for [object hover-objs]
[:& menu-entry {:title (:name object)
:key (dm/str (:id object))
:selected? (some #(= object %) shapes)
:on-click (select-shapes (:id object))
:on-pointer-enter (on-pointer-enter (:id object))
:on-pointer-leave (on-pointer-leave (:id object))
:on-unmount (on-unmount (:id object))
:icon (si/element-icon {:shape object})}])])
[:& menu-entry {:title (tr "workspace.shape.menu.forward")
:shortcut (sc/get-tooltip :bring-forward)
@ -435,14 +458,11 @@
:on-click do-delete}]))
(mf/defc shape-context-menu
{::mf/wrap [mf/memo]}
[{:keys [mdata] :as props}]
(let [{:keys [disable-booleans? disable-flatten?]} mdata
shapes (mf/deref refs/selected-objects)
hover-ids (mf/deref refs/current-hover-ids)
hover-objs (mf/deref (refs/objects-by-id hover-ids))
props #js {:shapes shapes
:hover-objs hover-objs
:disable-booleans? disable-booleans?
:disable-flatten? disable-flatten?}]
(when-not (empty? shapes)

View file

@ -84,91 +84,114 @@
(:name shape "")
(when (seq (:touched shape)) " *")])))
(defn- make-collapsed-iref
[id]
#(-> (l/in [:expanded id])
(l/derived refs/workspace-local)))
(mf/defc layer-item
[{:keys [index item selected objects] :as props}]
(let [id (:id item)
selected? (contains? selected id)
container? (or (cph/frame-shape? item)
(cph/group-shape? item))
blocked? (:blocked item)
hidden? (:hidden item)
disable-drag (mf/use-state false)
scroll-to-middle? (mf/use-var true)
expanded-iref (mf/with-memo [id]
(-> (l/in [:expanded id])
(l/derived refs/workspace-local)))
expanded-iref (mf/use-memo
(mf/deps id)
(make-collapsed-iref id))
expanded? (mf/deref expanded-iref)
expanded? (mf/deref expanded-iref)
selected? (contains? selected id)
container? (or (cph/frame-shape? item)
(cph/group-shape? item))
toggle-collapse
(fn [event]
(dom/stop-propagation event)
(if (and expanded? (kbd/shift? event))
(st/emit! (dwc/collapse-all))
(st/emit! (dwc/toggle-collapse id))))
(mf/use-fn
(mf/deps expanded?)
(fn [event]
(dom/stop-propagation event)
(if (and expanded? (kbd/shift? event))
(st/emit! (dwc/collapse-all))
(st/emit! (dwc/toggle-collapse id)))))
toggle-blocking
(fn [event]
(dom/stop-propagation event)
(if (:blocked item)
(st/emit! (dw/update-shape-flags [id] {:blocked false}))
(st/emit! (dw/update-shape-flags [id] {:blocked true})
(dw/deselect-shape id))))
(mf/use-fn
(mf/deps id blocked?)
(fn [event]
(dom/stop-propagation event)
(if blocked?
(st/emit! (dw/update-shape-flags [id] {:blocked false}))
(st/emit! (dw/update-shape-flags [id] {:blocked true})
(dw/deselect-shape id)))))
toggle-visibility
(fn [event]
(dom/stop-propagation event)
(if (:hidden item)
(st/emit! (dw/update-shape-flags [id] {:hidden false}))
(st/emit! (dw/update-shape-flags [id] {:hidden true}))))
(mf/use-fn
(mf/deps hidden?)
(fn [event]
(dom/stop-propagation event)
(if hidden?
(st/emit! (dw/update-shape-flags [id] {:hidden false}))
(st/emit! (dw/update-shape-flags [id] {:hidden true})))))
select-shape
(fn [event]
(dom/prevent-default event)
(reset! scroll-to-middle? false)
(let [id (:id item)]
(cond
(kbd/shift? event)
(st/emit! (dw/shift-select-shapes id))
(mf/use-fn
(mf/deps id)
(fn [event]
(dom/prevent-default event)
(reset! scroll-to-middle? false)
(cond
(kbd/shift? event)
(st/emit! (dw/shift-select-shapes id))
(kbd/mod? event)
(st/emit! (dw/select-shape id true))
(kbd/mod? event)
(st/emit! (dw/select-shape id true))
(> (count selected) 1)
(st/emit! (dw/select-shape id))
:else
(st/emit! (dw/select-shape id)))))
(> (count selected) 1)
(st/emit! (dw/select-shape id))
:else
(st/emit! (dw/select-shape id)))))
on-pointer-enter
(mf/use-fn
(mf/deps id)
(fn [_event]
(st/emit! (dw/highlight-shape id))))
on-pointer-leave
(mf/use-fn
(mf/deps id)
(fn [_event]
(st/emit! (dw/dehighlight-shape id))))
on-context-menu
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [pos (dom/get-client-position event)]
(st/emit! (dw/show-shape-context-menu {:position pos
:shape item}))))
(mf/use-fn
(mf/deps item)
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [pos (dom/get-client-position event)]
(st/emit! (dw/show-shape-context-menu {:position pos :shape item})))))
on-drag
(fn [{:keys [id]}]
(when (not (contains? selected id))
(st/emit! (dw/select-shape id))))
(mf/use-fn
(mf/deps id selected)
(fn [{:keys [id]}]
(when (not (contains? selected id))
(st/emit! (dw/select-shape id)))))
on-drop
(fn [side _data]
(if (= side :center)
(st/emit! (dw/relocate-selected-shapes (:id item) 0))
(let [to-index (if (= side :top) (inc index) index)
parent-id (cph/get-parent-id objects (:id item))]
(st/emit! (dw/relocate-selected-shapes parent-id to-index)))))
(mf/use-fn
(mf/deps id)
(fn [side _data]
(if (= side :center)
(st/emit! (dw/relocate-selected-shapes id 0))
(let [to-index (if (= side :top) (inc index) index)
parent-id (cph/get-parent-id objects id)]
(st/emit! (dw/relocate-selected-shapes parent-id to-index))))))
on-hold
(fn []
(when-not expanded?
(st/emit! (dwc/toggle-collapse (:id item)))))
(mf/use-fn
(mf/deps id expanded?)
(fn []
(when-not expanded?
(st/emit! (dwc/toggle-collapse id)))))
[dprops dref] (hooks/use-sortable
:data-type "penpot/layer"
@ -183,25 +206,23 @@
ref (mf/use-ref)]
(mf/use-effect
(mf/deps selected? selected)
(fn []
(let [single? (= (count selected) 1)
node (mf/ref-val ref)
(mf/with-effect [selected? selected]
(let [single? (= (count selected) 1)
node (mf/ref-val ref)
subid
(when (and single? selected?)
(let [scroll-to @scroll-to-middle?]
(ts/schedule
100
#(if scroll-to
(dom/scroll-into-view! node #js {:block "center", :behavior "smooth"})
(do
(dom/scroll-into-view-if-needed! node #js {:block "center", :behavior "smooth"})
(reset! scroll-to-middle? true))))))]
subid
(when (and single? selected?)
(let [scroll-to @scroll-to-middle?]
(ts/schedule
100
#(if scroll-to
(dom/scroll-into-view! node #js {:block "center", :behavior "smooth"})
(do
(dom/scroll-into-view-if-needed! node #js {:block "center", :behavior "smooth"})
(reset! scroll-to-middle? true))))))]
#(when (some? subid)
(rx/dispose! subid)))))
#(when (some? subid)
(rx/dispose! subid))))
[:li {:on-context-menu on-context-menu
:ref dref
@ -217,6 +238,8 @@
[:div.element-list-body {:class (dom/classnames :selected selected?
:icon-layer (= (:type item) :icon))
:on-click select-shape
:on-pointer-enter on-pointer-enter
:on-pointer-leave on-pointer-leave
:on-double-click #(dom/stop-propagation %)}
[:& si/element-icon {:shape item}]
[:& layer-name {:shape item
@ -373,7 +396,7 @@
(and
(:show-search-box @filter-state)
(or (d/not-empty? (:search-text @filter-state))
(d/not-empty? (:active-filters @filter-state))))
(d/not-empty? (:active-filters @filter-state))))
search-and-filters
(fn [[id shape]]
@ -426,7 +449,7 @@
[filtered-objects
handle-show-more
(mf/html
(if (:show-search-box @filter-state)
[:*
@ -487,7 +510,7 @@
(fn [entries]
(when (and (.-isIntersecting (first entries)) (some? show-more))
(show-more)))
on-render-container
(fn [element]
(let [options #js {:root element}

View file

@ -53,6 +53,7 @@
panning
selrect
transform
highlighted
vbox
vport
zoom
@ -286,6 +287,7 @@
{:objects base-objects
:selected selected
:hover #{(:id @hover) @frame-hover}
:highlighted highlighted
:edition edition
:zoom zoom}])

View file

@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport.outline
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.shapes :as gsh]
[app.util.object :as obj]
@ -75,26 +76,33 @@
:zoom zoom
:color color}])))
(defn- show-outline?
[shape]
(and (not (:hidden shape))
(not (:blocked shape))))
(mf/defc shape-outlines
{::mf/wrap-props false}
[props]
(let [selected (or (obj/get props "selected") #{})
hover (or (obj/get props "hover") #{})
(let [selected (or (obj/get props "selected") #{})
hover (or (obj/get props "hover") #{})
highlighted (or (obj/get props "highlighted") #{})
objects (obj/get props "objects")
edition (obj/get props "edition")
zoom (obj/get props "zoom")
objects (obj/get props "objects")
edition (obj/get props "edition")
zoom (obj/get props "zoom")
outlines-ids (set/union selected hover)
show-outline? (fn [shape] (and (not (:hidden shape))
(not (:blocked shape))))
lookup (d/getf objects)
edition? (fn [o] (= edition o))
shapes (->> outlines-ids
(filter #(not= edition %))
(map #(get objects %))
(filterv show-outline?)
(filter some?))]
shapes (-> #{}
(into (comp (remove edition?)
(keep lookup)
(filter show-outline?))
(set/union selected hover))
(into (comp (remove edition?)
(keep lookup))
highlighted))]
[:g.outlines
[:& shape-outlines-render {:shapes shapes
:zoom zoom}]]))
[:& shape-outlines-render {:shapes shapes :zoom zoom}]]))