0
Fork 0
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:
alonso.torres 2021-09-10 10:48:55 +02:00
parent 9f08153a85
commit 0b4b2d3814
20 changed files with 220 additions and 31 deletions

View 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

View 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

View 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

View 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

View file

@ -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;

View file

@ -47,6 +47,17 @@
&:hover {
background-color: $color-primary-lighter;
}
.submenu-icon {
position: absolute;
right: 1rem;
svg {
width: 10px;
height: 10px;
}
}
}
}

View file

@ -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)

View file

@ -42,7 +42,6 @@
:shapes []}
(gsh/setup selrect))))
(defn create-bool
[bool-type]
(ptk/reify ::create-bool-union

View file

@ -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))}
})

View file

@ -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

View file

@ -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

View file

@ -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;")]

View file

@ -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]

View file

@ -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)

View file

@ -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]

View file

@ -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]]]))

View file

@ -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]

View file

@ -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))]

View file

@ -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"

View file

@ -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"