mirror of
https://github.com/penpot/penpot.git
synced 2025-03-11 23:31:21 -05:00
✨ Add UI for boolean operations
This commit is contained in:
parent
9f08153a85
commit
0b4b2d3814
20 changed files with 220 additions and 31 deletions
3
frontend/resources/images/icons/boolean-difference.svg
Normal file
3
frontend/resources/images/icons/boolean-difference.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M0 0v337.797h162.201V500H500V162.203H337.797V0Zm34.031 34.033h269.735v269.733H34.03Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 169 B |
3
frontend/resources/images/icons/boolean-exclude.svg
Normal file
3
frontend/resources/images/icons/boolean-exclude.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M0 0v337.797h162.203V500H500V162.203H337.799V0Zm196.234 196.234h107.532v107.532H196.234Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 173 B |
3
frontend/resources/images/icons/boolean-intersection.svg
Normal file
3
frontend/resources/images/icons/boolean-intersection.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M0 0v337.797h162.201V500H500V162.203H337.797V0Zm34.031 34.033h269.735v128.17H162.2v141.563H34.031Zm303.766 162.201h128.17v269.733H196.234v-128.17h141.563z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 239 B |
3
frontend/resources/images/icons/boolean-union.svg
Normal file
3
frontend/resources/images/icons/boolean-union.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M0 0v337.797h162.203V500H500V162.203H337.797V0H0z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 134 B |
|
@ -14,7 +14,7 @@
|
|||
.align-group {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
width: 50%;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: solid 1px $color-gray-60;
|
||||
|
|
|
@ -47,6 +47,17 @@
|
|||
&:hover {
|
||||
background-color: $color-primary-lighter;
|
||||
}
|
||||
|
||||
.submenu-icon {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
|
||||
svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,8 +49,7 @@
|
|||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
))
|
||||
[potok.core :as ptk]))
|
||||
|
||||
;; (log/set-level! :trace)
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
:shapes []}
|
||||
(gsh/setup selrect))))
|
||||
|
||||
|
||||
(defn create-bool
|
||||
[bool-type]
|
||||
(ptk/reify ::create-bool-union
|
||||
|
|
|
@ -261,9 +261,21 @@
|
|||
:type "keyup"
|
||||
:fn #(st/emit! (dw/toggle-distances-display false))}
|
||||
|
||||
:create-union {:tooltip (ds/alt "U")
|
||||
:command ["alt" "u"]
|
||||
:fn #(st/emit! (dw/create-bool :union))}
|
||||
:boolean-union {:tooltip (ds/alt "U")
|
||||
:command ["alt" "u"]
|
||||
:fn #(st/emit! (dw/create-bool :union))}
|
||||
|
||||
:boolean-difference {:tooltip (ds/alt "D")
|
||||
:command ["alt" "d"]
|
||||
:fn #(st/emit! (dw/create-bool :difference))}
|
||||
|
||||
:boolean-intersection {:tooltip (ds/alt "I")
|
||||
:command ["alt" "i"]
|
||||
:fn #(st/emit! (dw/create-bool :intersection))}
|
||||
|
||||
:boolean-exclude {:tooltip (ds/alt "E")
|
||||
:command ["alt" "e"]
|
||||
:fn #(st/emit! (dw/create-bool :exclude))}
|
||||
|
||||
})
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
(:require-macros [app.main.ui.icons :refer [icon-xref]])
|
||||
(:require [rumext.alpha :as mf]))
|
||||
|
||||
;; Keep the list of icons sorted
|
||||
|
||||
(def action (icon-xref :action))
|
||||
(def actions (icon-xref :actions))
|
||||
(def align-bottom (icon-xref :align-bottom))
|
||||
|
@ -23,6 +25,10 @@
|
|||
(def auto-fix (icon-xref :auto-fix))
|
||||
(def auto-height (icon-xref :auto-height))
|
||||
(def auto-width (icon-xref :auto-width))
|
||||
(def boolean-difference (icon-xref :boolean-difference))
|
||||
(def boolean-exclude (icon-xref :boolean-exclude))
|
||||
(def boolean-intersection (icon-xref :boolean-intersection))
|
||||
(def boolean-union (icon-xref :boolean-union))
|
||||
(def box (icon-xref :box))
|
||||
(def chain (icon-xref :chain))
|
||||
(def chat (icon-xref :chat))
|
||||
|
@ -152,6 +158,7 @@
|
|||
(def uppercase (icon-xref :uppercase))
|
||||
(def user (icon-xref :user))
|
||||
|
||||
|
||||
(def loader-pencil
|
||||
(mf/html
|
||||
[:svg
|
||||
|
|
|
@ -202,10 +202,10 @@
|
|||
h
|
||||
(str (* s 100) "%")
|
||||
(str (* l 100) "%")))]
|
||||
(dom/set-css-property node "--color" (str/join ", " rgb))
|
||||
(dom/set-css-property node "--hue-rgb" (str/join ", " hue-rgb))
|
||||
(dom/set-css-property node "--saturation-grad-from" (format-hsl hsl-from))
|
||||
(dom/set-css-property node "--saturation-grad-to" (format-hsl hsl-to)))))
|
||||
(dom/set-css-property! node "--color" (str/join ", " rgb))
|
||||
(dom/set-css-property! node "--hue-rgb" (str/join ", " hue-rgb))
|
||||
(dom/set-css-property! node "--saturation-grad-from" (format-hsl hsl-from))
|
||||
(dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to)))))
|
||||
|
||||
;; When closing the modal we update the recent-color list
|
||||
(mf/use-effect
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr] :as i18n]
|
||||
[app.util.timers :as timers]
|
||||
|
@ -31,10 +32,53 @@
|
|||
(dom/stop-propagation event))
|
||||
|
||||
(mf/defc menu-entry
|
||||
[{:keys [title shortcut on-click] :as props}]
|
||||
[:li {:on-click on-click}
|
||||
[:span.title title]
|
||||
[:span.shortcut (or shortcut "")]])
|
||||
[{:keys [title shortcut submenu-ref on-click children] :as props}]
|
||||
(let [entry-ref (mf/use-ref nil)
|
||||
submenu-ref (mf/use-ref nil)
|
||||
hovering? (mf/use-ref false)
|
||||
|
||||
on-pointer-enter
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(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")))))
|
||||
|
||||
on-pointer-leave
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(mf/set-ref-val! hovering? false)
|
||||
(let [submenu-node (mf/ref-val submenu-ref)]
|
||||
(when (some? submenu-node)
|
||||
(timers/schedule
|
||||
200
|
||||
#(when-not (mf/ref-val hovering?)
|
||||
(dom/set-css-property! submenu-node "display" "none")))))))
|
||||
|
||||
set-dom-node
|
||||
(mf/use-callback
|
||||
(fn [dom]
|
||||
(let [submenu-node (mf/ref-val submenu-ref)]
|
||||
(when (and (some? dom) (some? submenu-node))
|
||||
(dom/set-css-property! submenu-node "top" (str (.-offsetTop dom) "px"))))))]
|
||||
|
||||
[:li {:ref set-dom-node
|
||||
:on-click on-click
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave}
|
||||
[:span.title title]
|
||||
[:span.shortcut (or shortcut "")]
|
||||
|
||||
(when (> (count children) 1)
|
||||
[:span.submenu-icon i/arrow-slide])
|
||||
|
||||
(when (> (count children) 1)
|
||||
[:ul.workspace-context-menu
|
||||
{:ref submenu-ref
|
||||
:style {:display "none" :left 250}
|
||||
:on-context-menu prevent-default}
|
||||
children])]))
|
||||
|
||||
(mf/defc menu-separator
|
||||
[]
|
||||
|
@ -100,12 +144,12 @@
|
|||
do-navigate-component-file (st/emitf (dwl/nav-to-component-file
|
||||
(:component-file shape)))
|
||||
|
||||
do-create-bool-shape (st/emitf (dw/create-bool :union))]
|
||||
do-boolean-union (st/emitf (dw/create-bool :union))
|
||||
do-boolean-difference (st/emitf (dw/create-bool :difference))
|
||||
do-boolean-intersection (st/emitf (dw/create-bool :intersection))
|
||||
do-boolean-exclude (st/emitf (dw/create-bool :exclude))
|
||||
]
|
||||
[:*
|
||||
;;
|
||||
[:& menu-entry {:title ">BOOL"
|
||||
:on-click do-create-bool-shape}]
|
||||
;;
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.copy")
|
||||
:shortcut (sc/get-tooltip :copy)
|
||||
:on-click do-copy}]
|
||||
|
@ -171,6 +215,20 @@
|
|||
:shortcut (sc/get-tooltip :start-editing)
|
||||
:on-click do-start-editing}])
|
||||
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.path")}
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.union")
|
||||
:shortcut (sc/get-tooltip :boolean-union)
|
||||
:on-click do-boolean-union}]
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.difference")
|
||||
:shortcut (sc/get-tooltip :boolean-difference)
|
||||
:on-click do-boolean-difference}]
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.intersection")
|
||||
:shortcut (sc/get-tooltip :boolean-intersection)
|
||||
:on-click do-boolean-intersection}]
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.exclude")
|
||||
:shortcut (sc/get-tooltip :boolean-exclude)
|
||||
:on-click do-boolean-exclude}]]
|
||||
|
||||
(if (:hidden shape)
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.show")
|
||||
:on-click do-show-shape}]
|
||||
|
@ -246,7 +304,7 @@
|
|||
(when dropdown
|
||||
(let [bounding-rect (dom/get-bounding-rect dropdown)
|
||||
window-size (dom/get-window-size)
|
||||
delta-x (max (- (:right bounding-rect) (:width window-size)) 0)
|
||||
delta-x (max (- (+ (:right bounding-rect) 250) (:width window-size)) 0)
|
||||
delta-y (max (- (:bottom bounding-rect) (:height window-size)) 0)
|
||||
new-style (str "top: " (- top delta-y) "px; "
|
||||
"left: " (- left delta-x) "px;")]
|
||||
|
|
|
@ -39,6 +39,11 @@
|
|||
(if (:masked-group? shape)
|
||||
i/mask
|
||||
i/folder))
|
||||
:bool (case (:bool-type shape)
|
||||
:difference i/boolean-difference
|
||||
:exclude i/boolean-exclude
|
||||
:intersection i/boolean-intersection
|
||||
#_:default i/boolean-union)
|
||||
:svg-raw i/file-svg
|
||||
nil))
|
||||
|
||||
|
@ -292,7 +297,8 @@
|
|||
:shape-ref
|
||||
:touched
|
||||
:metadata
|
||||
:masked-group?]))
|
||||
:masked-group?
|
||||
:bool-type]))
|
||||
|
||||
(defn- strip-objects
|
||||
[objects]
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.workspace.sidebar.align :refer [align-options]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.booleans :refer [booleans-options]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.page :as page]
|
||||
|
@ -60,6 +61,7 @@
|
|||
:title (tr "workspace.options.design")}
|
||||
[:div.element-options
|
||||
[:& align-options]
|
||||
[:& booleans-options]
|
||||
(case (count selected)
|
||||
0 [:& page/options]
|
||||
1 [:& shape-options {:shape (first shapes)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.sidebar.align
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.align
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
|
@ -0,0 +1,54 @@
|
|||
;; 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.ui.workspace.sidebar.options.menus.booleans
|
||||
(:require
|
||||
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]
|
||||
))
|
||||
|
||||
(mf/defc booleans-options
|
||||
[]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
disabled (and (some? selected)
|
||||
(<= (count selected) 1))
|
||||
|
||||
do-boolean-union (st/emitf (dw/create-bool :union))
|
||||
do-boolean-difference (st/emitf (dw/create-bool :difference))
|
||||
do-boolean-intersection (st/emitf (dw/create-bool :intersection))
|
||||
do-boolean-exclude (st/emitf (dw/create-bool :exclude))]
|
||||
|
||||
[:div.align-options
|
||||
[:div.align-group
|
||||
[:div.align-button.tooltip.tooltip-bottom
|
||||
{:alt (tr "workspace.shape.menu.union")
|
||||
:class (when disabled "disabled")
|
||||
:on-click do-boolean-union}
|
||||
i/boolean-union]
|
||||
|
||||
[:div.align-button.tooltip.tooltip-bottom
|
||||
{:alt (tr "workspace.shape.menu.difference")
|
||||
:class (when disabled "disabled")
|
||||
:on-click do-boolean-difference}
|
||||
i/boolean-difference]
|
||||
|
||||
[:div.align-button.tooltip.tooltip-bottom
|
||||
{:alt (tr "workspace.shape.menu.intersection")
|
||||
:class (when disabled "disabled")
|
||||
:on-click do-boolean-intersection}
|
||||
i/boolean-intersection]
|
||||
|
||||
[:div.align-button.tooltip.tooltip-bottom
|
||||
{:alt (tr "workspace.shape.menu.exclude")
|
||||
:class (when disabled "disabled")
|
||||
:on-click do-boolean-exclude}
|
||||
i/boolean-exclude]]]))
|
||||
|
|
@ -281,7 +281,7 @@
|
|||
(defn set-text! [node text]
|
||||
(set! (.-textContent node) text))
|
||||
|
||||
(defn set-css-property [node property value]
|
||||
(defn set-css-property! [node property value]
|
||||
(.setProperty (.-style ^js node) property value))
|
||||
|
||||
(defn capture-pointer [event]
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
[app.util.path.geom :as upg]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def ^:const curve-curve-precision 0.001)
|
||||
(def ^:const curve-curve-precision 0.1)
|
||||
|
||||
(defn curve->rect
|
||||
[[from-p to-p :as curve]]
|
||||
|
@ -133,10 +133,9 @@
|
|||
r2 (curve-range->rect c2 c2-from c2-to)]
|
||||
|
||||
(when (gsi/overlaps-rects? r1 r2)
|
||||
|
||||
(if (and (< (mth/abs (- c1-from c1-to)) curve-curve-precision)
|
||||
(< (mth/abs (- c2-from c2-to)) curve-curve-precision))
|
||||
|
||||
(if (< (gpt/distance (gpp/curve-values c1 c1-from)
|
||||
(gpp/curve-values c2 c2-from))
|
||||
curve-curve-precision)
|
||||
[(sorted-set (mth/precision c1-from 4))
|
||||
(sorted-set (mth/precision c2-from 4))]
|
||||
|
||||
|
|
|
@ -3114,4 +3114,19 @@ msgid "viewer.breaking-change.message"
|
|||
msgstr "Sorry!"
|
||||
|
||||
msgid "viewer.breaking-change.description"
|
||||
msgstr "This shareable link is no longer valid. Create a new one or ask the owner for a new one.
|
||||
msgstr "This shareable link is no longer valid. Create a new one or ask the owner for a new one."
|
||||
|
||||
msgid "workspace.shape.menu.path"
|
||||
msgstr "Path"
|
||||
|
||||
msgid "workspace.shape.menu.union"
|
||||
msgstr "Union"
|
||||
|
||||
msgid "workspace.shape.menu.difference"
|
||||
msgstr "Difference"
|
||||
|
||||
msgid "workspace.shape.menu.intersection"
|
||||
msgstr "Intersection"
|
||||
|
||||
msgid "workspace.shape.menu.exclude"
|
||||
msgstr "Exclude"
|
||||
|
|
|
@ -3000,3 +3000,18 @@ msgstr "¡Lo sentimos!"
|
|||
|
||||
msgid "viewer.breaking-change.description"
|
||||
msgstr "Este link compartido ya no funciona. Crea uno nuevo o pídelo a la persona que lo creó."
|
||||
|
||||
msgid "workspace.shape.menu.path"
|
||||
msgstr "Path"
|
||||
|
||||
msgid "workspace.shape.menu.union"
|
||||
msgstr "Unión"
|
||||
|
||||
msgid "workspace.shape.menu.difference"
|
||||
msgstr "Diferencia"
|
||||
|
||||
msgid "workspace.shape.menu.intersection"
|
||||
msgstr "Intersección"
|
||||
|
||||
msgid "workspace.shape.menu.exclude"
|
||||
msgstr "Exclusión"
|
||||
|
|
Loading…
Add table
Reference in a new issue