diff --git a/frontend/resources/images/icons/boolean-difference.svg b/frontend/resources/images/icons/boolean-difference.svg
new file mode 100644
index 000000000..4d5c7f6a8
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-difference.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/boolean-exclude.svg b/frontend/resources/images/icons/boolean-exclude.svg
new file mode 100644
index 000000000..6a3865703
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-exclude.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/boolean-intersection.svg b/frontend/resources/images/icons/boolean-intersection.svg
new file mode 100644
index 000000000..3480e6366
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-intersection.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/boolean-union.svg b/frontend/resources/images/icons/boolean-union.svg
new file mode 100644
index 000000000..fdeb117b7
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-union.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/styles/main/partials/sidebar-align-options.scss b/frontend/resources/styles/main/partials/sidebar-align-options.scss
index 7b9750426..634c40861 100644
--- a/frontend/resources/styles/main/partials/sidebar-align-options.scss
+++ b/frontend/resources/styles/main/partials/sidebar-align-options.scss
@@ -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;
diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss
index f167e9814..a6ad2df1e 100644
--- a/frontend/resources/styles/main/partials/workspace.scss
+++ b/frontend/resources/styles/main/partials/workspace.scss
@@ -47,6 +47,17 @@
&:hover {
background-color: $color-primary-lighter;
}
+
+ .submenu-icon {
+ position: absolute;
+ right: 1rem;
+
+ svg {
+ width: 10px;
+ height: 10px;
+ }
+ }
+
}
}
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 476046781..cb055c688 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -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)
diff --git a/frontend/src/app/main/data/workspace/booleans.cljs b/frontend/src/app/main/data/workspace/booleans.cljs
index 6702af71d..d4aa7455a 100644
--- a/frontend/src/app/main/data/workspace/booleans.cljs
+++ b/frontend/src/app/main/data/workspace/booleans.cljs
@@ -42,7 +42,6 @@
:shapes []}
(gsh/setup selrect))))
-
(defn create-bool
[bool-type]
(ptk/reify ::create-bool-union
diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs
index 7373d6e76..15dd66f66 100644
--- a/frontend/src/app/main/data/workspace/shortcuts.cljs
+++ b/frontend/src/app/main/data/workspace/shortcuts.cljs
@@ -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))}
})
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index abf60ef6e..35c5285d2 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -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
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index 7e71d5c4a..b239a9185 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -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
diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs
index ed9933297..327864592 100644
--- a/frontend/src/app/main/ui/workspace/context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/context_menu.cljs
@@ -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;")]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
index 0f147b1f2..f3c51c963 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
@@ -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]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
index 89248b58b..e99d25802 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
@@ -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)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/align.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
similarity index 98%
rename from frontend/src/app/main/ui/workspace/sidebar/align.cljs
rename to frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
index acb3c6a42..7b32c969a 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/align.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
@@ -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]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs
new file mode 100644
index 000000000..0ad6b3ddd
--- /dev/null
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs
@@ -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]]]))
+
diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs
index 1f952fb35..a170481fc 100644
--- a/frontend/src/app/util/dom.cljs
+++ b/frontend/src/app/util/dom.cljs
@@ -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]
diff --git a/frontend/src/app/util/path/bool.cljs b/frontend/src/app/util/path/bool.cljs
index 24e840840..e6a41c884 100644
--- a/frontend/src/app/util/path/bool.cljs
+++ b/frontend/src/app/util/path/bool.cljs
@@ -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))]
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index e5211a76d..27c40c21c 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -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"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 6b716a299..a1020f9c3 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -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"