mirror of
https://github.com/penpot/penpot.git
synced 2025-04-04 19:11:20 -05:00
Refactor user interaction locking.
This commit is contained in:
parent
5afc297e93
commit
b8e5239ee3
18 changed files with 468 additions and 545 deletions
|
@ -1,26 +0,0 @@
|
|||
(ns uxbox.main.ui.core
|
||||
(:require [beicon.core :as rx]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Actions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defonce lock (atom ""))
|
||||
(defonce actions-s (rx/bus))
|
||||
|
||||
(defn acquire-action!
|
||||
([type]
|
||||
(acquire-action! type nil))
|
||||
([type payload]
|
||||
(when (empty? @lock)
|
||||
(reset! lock type)
|
||||
(rx/push! actions-s {:type type :payload payload}))))
|
||||
|
||||
(defn release-action!
|
||||
([type]
|
||||
(when (str/contains? @lock type)
|
||||
(rx/push! actions-s {:type ""})
|
||||
(reset! lock "")))
|
||||
([type & more]
|
||||
(run! release-action! (cons type more))))
|
|
@ -22,12 +22,10 @@
|
|||
(let [{:keys [id x y width height group]} shape
|
||||
selected (mx/react common/selected-shapes-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)
|
||||
on-mouse-up #(common/on-mouse-up % shape)]
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
(html
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
:on-mouse-down on-mouse-down}
|
||||
(circle-shape shape identity)])))
|
||||
|
||||
(def circle-component
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.core :as ui]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.workspace.rlocks :as rlocks]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
|||
(do
|
||||
(dom/stop-propagation event)
|
||||
(rs/emit! (uds/select-shape id))
|
||||
(ui/acquire-action! "ui.shape.move"))
|
||||
(rlocks/acquire! :shape/move))
|
||||
|
||||
(and (not selected?) (not (empty? selected)))
|
||||
(do
|
||||
|
@ -51,21 +51,9 @@
|
|||
(do
|
||||
(rs/emit! (uds/deselect-all)
|
||||
(uds/select-shape id))
|
||||
(ui/acquire-action! "ui.shape.move"))))
|
||||
(rlocks/acquire! :shape/move))))
|
||||
|
||||
:else
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(ui/acquire-action! "ui.shape.move"))))))
|
||||
|
||||
(defn on-mouse-up
|
||||
[event {:keys [id group] :as shape}]
|
||||
(cond
|
||||
(and group (:locked (geom/resolve-parent shape)))
|
||||
nil
|
||||
|
||||
:else
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(ui/release-action! "ui.shape"))))
|
||||
|
||||
(rlocks/acquire! :shape/move))))))
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
[uxbox.main.ui.shapes.circle :as circle]
|
||||
[uxbox.main.ui.shapes.text :as text]
|
||||
[uxbox.main.ui.shapes.line :as line]
|
||||
[uxbox.main.ui.shapes.path :as path]
|
||||
[uxbox.main.geom :as geom]))
|
||||
|
||||
;; --- Helpers
|
||||
|
@ -25,12 +26,14 @@
|
|||
|
||||
(defn render-component
|
||||
[{:keys [type] :as shape}]
|
||||
;; (println "render-component" shape)
|
||||
(case type
|
||||
:group (group-component shape)
|
||||
:text (text/text-component shape)
|
||||
:line (line/line-component shape)
|
||||
:icon (icon/icon-component shape)
|
||||
:rect (rect/rect-component shape)
|
||||
:path (path/path-component shape)
|
||||
:circle (circle/circle-component shape)))
|
||||
|
||||
;; --- Group Component
|
||||
|
@ -42,13 +45,11 @@
|
|||
(let [{:keys [id x y width height group]} shape
|
||||
selected (mx/react common/selected-shapes-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)
|
||||
on-mouse-up #(common/on-mouse-up % shape)]
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
(html
|
||||
[:g.shape.group-shape
|
||||
{:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
:on-mouse-down on-mouse-down}
|
||||
(group-shape shape render-component)])))
|
||||
|
||||
(def group-component
|
||||
|
|
|
@ -17,16 +17,13 @@
|
|||
(declare icon-shape)
|
||||
|
||||
(defn- icon-component-render
|
||||
[own shape]
|
||||
(let [{:keys [id x y width height group]} shape
|
||||
selected (mx/react common/selected-shapes-ref)
|
||||
[own {:keys [id] :as shape}]
|
||||
(let [selected (mx/react common/selected-shapes-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)
|
||||
on-mouse-up #(common/on-mouse-up % shape)]
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
(html
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
:on-mouse-down on-mouse-down}
|
||||
(icon-shape shape identity)])))
|
||||
|
||||
(def icon-component
|
||||
|
|
|
@ -21,12 +21,10 @@
|
|||
(let [{:keys [id x y width height group]} shape
|
||||
selected (mx/react common/selected-shapes-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)
|
||||
on-mouse-up #(common/on-mouse-up % shape)]
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
(html
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
:on-mouse-down on-mouse-down}
|
||||
(line-shape shape identity)])))
|
||||
|
||||
(def line-component
|
||||
|
|
|
@ -22,12 +22,10 @@
|
|||
(let [{:keys [id x y width height group]} shape
|
||||
selected (mx/react common/selected-shapes-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)
|
||||
on-mouse-up #(common/on-mouse-up % shape)]
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
(html
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
:on-mouse-down on-mouse-down}
|
||||
(rect-shape shape identity)])))
|
||||
|
||||
(def rect-component
|
||||
|
|
|
@ -5,20 +5,18 @@
|
|||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.text
|
||||
(:require [sablono.core :refer-macros [html]]
|
||||
[cuerdas.core :as str]
|
||||
[rum.core :as rum]
|
||||
(:require [cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[goog.events :as events]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.color :as color]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.core :as ui]
|
||||
[uxbox.util.mixins :as mx]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.color :as color]
|
||||
[uxbox.util.dom :as dom])
|
||||
[uxbox.main.ui.workspace.rlocks :as rlocks]
|
||||
[uxbox.main.geom :as geom])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; --- Events
|
||||
|
@ -37,7 +35,8 @@
|
|||
(declare text-shape)
|
||||
(declare text-shape-edit)
|
||||
|
||||
(defn- text-component-render
|
||||
(mx/defcs text-component
|
||||
{:mixins [mx/static mx/reactive (mx/local)]}
|
||||
[own {:keys [id x1 y1 content group] :as shape}]
|
||||
(let [selected (mx/react common/selected-shapes-ref)
|
||||
selected? (and (contains? selected id)
|
||||
|
@ -45,28 +44,18 @@
|
|||
local (:rum/local own)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(handle-mouse-down event local shape selected))
|
||||
(on-mouse-up [event]
|
||||
(common/on-mouse-up event shape))
|
||||
(on-done [_]
|
||||
(swap! local assoc :edition false))
|
||||
(on-double-click [event]
|
||||
(swap! local assoc :edition true)
|
||||
(ui/acquire-action! "ui.text.edit"))]
|
||||
(html
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:ref "main"
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
(if (:edition @local false)
|
||||
(text-shape-edit shape on-done)
|
||||
(text-shape shape))]))))
|
||||
|
||||
(def text-component
|
||||
(mx/component
|
||||
{:render text-component-render
|
||||
:name "text-componet"
|
||||
:mixins [mx/static mx/reactive (mx/local)]}))
|
||||
(rlocks/acquire! :ui/text-edit))]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:ref "main"
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
(if (:edition @local false)
|
||||
(text-shape-edit shape on-done)
|
||||
(text-shape shape))])))
|
||||
|
||||
;; --- Text Styles Helpers
|
||||
|
||||
|
@ -112,8 +101,10 @@
|
|||
(.focus dom)
|
||||
own))
|
||||
|
||||
(defn- text-shape-edit-render
|
||||
[own {:keys [id x1 y1 content] :as shape} on-done]
|
||||
(mx/defc text-shape-edit
|
||||
{:did-mount text-shape-edit-did-mount
|
||||
:mixins [mx/static]}
|
||||
[{:keys [id x1 y1 content] :as shape} on-done]
|
||||
(let [size (geom/size shape)
|
||||
style (make-style shape)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
|
@ -121,33 +112,25 @@
|
|||
:transform (str rfm)}
|
||||
props (merge props size)]
|
||||
(letfn [(on-blur [ev]
|
||||
(ui/release-action! "ui.text.edit")
|
||||
(rlocks/release! :ui/text-edit)
|
||||
(on-done))
|
||||
(on-input [ev]
|
||||
(let [content (dom/event->inner-text ev)
|
||||
sid (:id (first (:rum/args own)))]
|
||||
(rs/emit! (uds/update-text sid {:content content}))))]
|
||||
(html
|
||||
[:g
|
||||
[:rect (merge props +select-rect-attrs+)]
|
||||
[:foreignObject props
|
||||
[:p {:ref "container"
|
||||
:on-blur on-blur
|
||||
:on-input on-input
|
||||
:contentEditable true
|
||||
:style style}]]]))))
|
||||
|
||||
(def text-shape-edit
|
||||
(mx/component
|
||||
{:render text-shape-edit-render
|
||||
:did-mount text-shape-edit-did-mount
|
||||
:name "text-shape-edit"
|
||||
:mixins [mx/static]}))
|
||||
(let [content (dom/event->inner-text ev)]
|
||||
(rs/emit! (uds/update-text id {:content content}))))]
|
||||
[:g
|
||||
[:rect (merge props +select-rect-attrs+)]
|
||||
[:foreignObject props
|
||||
[:p {:ref "container"
|
||||
:on-blur on-blur
|
||||
:on-input on-input
|
||||
:contentEditable true
|
||||
:style style}]]])))
|
||||
|
||||
;; --- Text Shape
|
||||
|
||||
(defn- text-shape-render
|
||||
[own {:keys [id x1 y1 content] :as shape}]
|
||||
(mx/defc text-shape
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id x1 y1 content] :as shape}]
|
||||
(let [key (str "shape-" id)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
size (geom/size shape)
|
||||
|
@ -155,12 +138,5 @@
|
|||
:transform (str rfm)}
|
||||
attrs (merge props size)
|
||||
style (make-style shape)]
|
||||
(html
|
||||
[:foreignObject attrs
|
||||
[:p {:style style} content]])))
|
||||
|
||||
(def text-shape
|
||||
(mx/component
|
||||
{:render text-shape-render
|
||||
:name "text-shape"
|
||||
:mixins [mx/static]}))
|
||||
[:foreignObject attrs
|
||||
[:p {:style style} content]]))
|
||||
|
|
|
@ -77,11 +77,19 @@
|
|||
(defonce scroll-a
|
||||
(rx/to-atom scroll-s))
|
||||
|
||||
|
||||
;; --- Events
|
||||
|
||||
(defonce mouse-events-b (rx/bus))
|
||||
(defonce mouse-events-s (rx/dedupe mouse-events-b))
|
||||
|
||||
(defonce keyboard-events-b (rx/bus))
|
||||
(defonce keyboard-events-s (rx/dedupe keyboard-events-b))
|
||||
|
||||
;; --- Mouse Position Stream
|
||||
|
||||
(defonce mouse-b (rx/bus))
|
||||
(defonce mouse-s
|
||||
(rx/dedupe mouse-b))
|
||||
(defonce mouse-s (rx/dedupe mouse-b))
|
||||
|
||||
(defonce mouse-canvas-s
|
||||
(->> mouse-s
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.canvas
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[beicon.core :as rx]
|
||||
(:require [beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[goog.events :as events]
|
||||
[uxbox.main.constants :as c]
|
||||
|
@ -19,14 +17,13 @@
|
|||
[uxbox.main.geom.point :as gpt]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int)]
|
||||
[uxbox.main.ui.core :as uuc]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.shapes :as uus]
|
||||
[uxbox.main.ui.shapes.path :as spath]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.base :as uuwb]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.rlocks :as rlocks]
|
||||
[uxbox.main.ui.workspace.drawarea :refer (draw-area)]
|
||||
[uxbox.main.ui.workspace.movement :as cmov]
|
||||
[uxbox.main.ui.workspace.resize :as cres]
|
||||
[uxbox.main.ui.workspace.ruler :refer (ruler)]
|
||||
[uxbox.main.ui.workspace.selection :refer (selection-handlers)]
|
||||
[uxbox.main.ui.workspace.selrect :refer (selrect)]
|
||||
|
@ -45,67 +42,41 @@
|
|||
|
||||
;; --- Canvas
|
||||
|
||||
(defn- canvas-render
|
||||
[own {:keys [width height id] :as page}]
|
||||
(let [workspace (mx/react uuwb/workspace-ref)
|
||||
flags (:flags workspace)]
|
||||
(html
|
||||
[:svg.page-canvas {:x c/canvas-start-x
|
||||
:y c/canvas-start-y
|
||||
:ref (str "canvas" id)
|
||||
:width width
|
||||
:height height}
|
||||
(background)
|
||||
[:svg.page-layout {}
|
||||
[:g.main {}
|
||||
(for [item (reverse (:shapes page))]
|
||||
(-> (uus/shape item)
|
||||
(rum/with-key (str item))))
|
||||
(selection-handlers)
|
||||
(draw-area)]]])))
|
||||
(def ^:private test-path-shape
|
||||
{:type :path
|
||||
:id #uuid "042951a0-804a-4cf1-b606-3e97157f55b5"
|
||||
:stroke-type :solid
|
||||
:stroke "#000000"
|
||||
:stroke-width 2
|
||||
:fill "transparent"
|
||||
:close? true
|
||||
:points [(gpt/point 100 100)
|
||||
(gpt/point 300 100)
|
||||
(gpt/point 200 300)
|
||||
]})
|
||||
|
||||
(def canvas
|
||||
(mx/component
|
||||
{:render canvas-render
|
||||
:name "canvas"
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
(mx/defc canvas
|
||||
{:mixins [mx/reactive]}
|
||||
[{:keys [width height id] :as page}]
|
||||
(let [workspace (mx/react wb/workspace-ref)
|
||||
flags (:flags workspace)]
|
||||
[:svg.page-canvas {:x c/canvas-start-x
|
||||
:y c/canvas-start-y
|
||||
:ref (str "canvas" id)
|
||||
:width width
|
||||
:height height}
|
||||
(background)
|
||||
[:svg.page-layout {}
|
||||
[:g.main {}
|
||||
(for [item (reverse (:shapes page))]
|
||||
(-> (uus/shape item)
|
||||
(mx/with-key (str item))))
|
||||
(spath/path-component test-path-shape)
|
||||
(selection-handlers)
|
||||
(draw-area)]]]))
|
||||
|
||||
;; --- Viewport
|
||||
|
||||
(defn viewport-render
|
||||
[own]
|
||||
(let [workspace (mx/react uuwb/workspace-ref)
|
||||
page (mx/react uuwb/page-ref)
|
||||
flags (:flags workspace)
|
||||
drawing? (:drawing workspace)
|
||||
zoom (or (:zoom workspace) 1)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
(if-let [shape (:drawing workspace)]
|
||||
(uuc/acquire-action! "ui.shape.draw")
|
||||
(do
|
||||
(when-not (empty? (:selected workspace))
|
||||
(rs/emit! (uds/deselect-all)))
|
||||
(uuc/acquire-action! "ui.selrect"))))
|
||||
(on-mouse-up [event]
|
||||
(dom/stop-propagation event)
|
||||
(uuc/release-action! "ui.shape"
|
||||
"ui.selrect"))]
|
||||
(html
|
||||
[:svg.viewport {:width (* c/viewport-width zoom)
|
||||
:height (* c/viewport-height zoom)
|
||||
:ref "viewport"
|
||||
:class (when drawing? "drawing")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
||||
(if page
|
||||
(canvas page))
|
||||
(if (contains? flags :grid)
|
||||
(grid))]
|
||||
(ruler)
|
||||
(selrect)]))))
|
||||
|
||||
(defn- viewport-did-mount
|
||||
[own]
|
||||
(letfn [(translate-point-to-viewport [pt]
|
||||
|
@ -127,12 +98,18 @@
|
|||
(gpt/subtract brect))))))
|
||||
|
||||
(on-key-down [event]
|
||||
(rx/push! wb/keyboard-events-b {:type :keyboard/down
|
||||
:key (.-keyCode event)
|
||||
:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)})
|
||||
(when (kbd/space? event)
|
||||
(uuc/acquire-action! "ui.workspace.scroll")))
|
||||
(rlocks/acquire! :workspace/scroll)))
|
||||
|
||||
(on-key-up [event]
|
||||
(when (kbd/space? event)
|
||||
(uuc/release-action! "ui.workspace.scroll")))
|
||||
(rx/push! wb/keyboard-events-b {:type :keyboard/up
|
||||
:key (.-keyCode event)
|
||||
:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}))
|
||||
|
||||
(on-mousemove [event]
|
||||
(let [wpt (gpt/point (.-clientX event)
|
||||
|
@ -144,16 +121,12 @@
|
|||
:window-coords wpt
|
||||
:viewport-coords vppt
|
||||
:canvas-coords cvpt}]
|
||||
(rx/push! uuwb/mouse-b event)))]
|
||||
(rx/push! wb/mouse-b event)))]
|
||||
|
||||
(let [key1 (events/listen js/document EventType.MOUSEMOVE on-mousemove)
|
||||
key2 (events/listen js/document EventType.KEYDOWN on-key-down)
|
||||
key3 (events/listen js/document EventType.KEYUP on-key-up)
|
||||
sub1 (cmov/watch-move-actions)
|
||||
sub2 (cres/watch-resize-actions)]
|
||||
key3 (events/listen js/document EventType.KEYUP on-key-up)]
|
||||
(assoc own
|
||||
::sub1 sub1
|
||||
::sub2 sub2
|
||||
::key1 key1
|
||||
::key2 key2
|
||||
::key3 key3))))
|
||||
|
@ -163,14 +136,39 @@
|
|||
(events/unlistenByKey (::key1 own))
|
||||
(events/unlistenByKey (::key2 own))
|
||||
(events/unlistenByKey (::key3 own))
|
||||
(.close (::sub1 own))
|
||||
(.close (::sub2 own))
|
||||
(dissoc own ::key1 ::key2 ::key3 ::sub1 ::sub2))
|
||||
(dissoc own ::key1 ::key2 ::key3))
|
||||
|
||||
(mx/defc viewport
|
||||
{:did-mount viewport-did-mount
|
||||
:will-unmount viewport-will-unmount
|
||||
:mixins [mx/reactive]}
|
||||
[]
|
||||
(let [workspace (mx/react wb/workspace-ref)
|
||||
page (mx/react wb/page-ref)
|
||||
flags (:flags workspace)
|
||||
drawing? (:drawing workspace)
|
||||
zoom (or (:zoom workspace) 1)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! wb/mouse-events-b :mouse/down)
|
||||
(if (:drawing workspace)
|
||||
(rlocks/acquire! :ui/draw)
|
||||
(rlocks/acquire! :ui/selrect)))
|
||||
(on-mouse-up [event]
|
||||
(rx/push! wb/mouse-events-b :mouse/up)
|
||||
(dom/stop-propagation event))]
|
||||
[:svg.viewport {:width (* c/viewport-width zoom)
|
||||
:height (* c/viewport-height zoom)
|
||||
:ref "viewport"
|
||||
:class (when drawing? "drawing")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
||||
(if page
|
||||
(canvas page))
|
||||
(if (contains? flags :grid)
|
||||
(grid))]
|
||||
(ruler)
|
||||
(selrect)])))
|
||||
|
||||
|
||||
(def viewport
|
||||
(mx/component
|
||||
{:render viewport-render
|
||||
:name "viewport"
|
||||
:did-mount viewport-did-mount
|
||||
:will-unmount viewport-will-unmount
|
||||
:mixins [mx/reactive]}))
|
||||
|
|
|
@ -6,18 +6,16 @@
|
|||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.drawarea
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
"Draw interaction and component."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.core :as uuc]
|
||||
[uxbox.main.ui.shapes :as shapes]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.rlocks :as rlocks]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.geom.point :as gpt]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
@ -27,6 +25,10 @@
|
|||
(defonce drawing-shape (atom nil))
|
||||
(defonce drawing-position (atom nil))
|
||||
|
||||
(def ^:private canvas-coords
|
||||
(gpt/point c/canvas-start-x
|
||||
c/canvas-start-y))
|
||||
|
||||
;; --- Draw Area (Component)
|
||||
|
||||
(declare watch-draw-actions)
|
||||
|
@ -52,48 +54,41 @@
|
|||
(geom/resize position)
|
||||
(shapes/render-component)))))
|
||||
|
||||
;; --- Drawing Logic
|
||||
;; --- Drawing Initialization
|
||||
|
||||
(declare initialize)
|
||||
(declare on-init)
|
||||
(declare on-init-draw-icon)
|
||||
(declare on-init-draw-generic)
|
||||
(declare on-draw-start)
|
||||
(declare on-draw)
|
||||
(declare on-draw-complete)
|
||||
|
||||
(defn- watch-draw-actions
|
||||
[]
|
||||
(let [stream (->> uuc/actions-s
|
||||
(rx/map :type)
|
||||
(rx/dedupe)
|
||||
(rx/filter #(= "ui.shape.draw" %))
|
||||
(rx/map #(:drawing @wb/workspace-ref))
|
||||
(rx/filter identity))]
|
||||
(rx/subscribe stream initialize)))
|
||||
(let [stream (->> (rx/map first rlocks/stream)
|
||||
(rx/filter #(= % :ui/draw)))]
|
||||
(rx/subscribe stream on-init)))
|
||||
|
||||
(declare initialize-icon-drawing)
|
||||
(declare initialize-shape-drawing)
|
||||
(defn- on-init
|
||||
"Function execution when draw shape operation is requested.
|
||||
This is a entry point for the draw interaction."
|
||||
[]
|
||||
(when-let [shape (:drawing @wb/workspace-ref)]
|
||||
(case (:type shape)
|
||||
:icon (on-init-draw-icon shape)
|
||||
(on-init-draw-generic shape))))
|
||||
|
||||
(defn- initialize
|
||||
[shape]
|
||||
(if (= (:type shape) :icon)
|
||||
(initialize-icon-drawing shape)
|
||||
(initialize-shape-drawing shape)))
|
||||
|
||||
(defn- initialize-icon-drawing
|
||||
"A drawing handler for icons."
|
||||
(defn- on-init-draw-icon
|
||||
[shape]
|
||||
(let [{:keys [x y]} (gpt/divide @wb/mouse-canvas-a @wb/zoom-ref)
|
||||
props {:x1 x :y1 y :x2 (+ x 100) :y2 (+ y 100)}
|
||||
shape (geom/setup shape props)]
|
||||
(rs/emit! (uds/add-shape shape)
|
||||
(udw/select-for-drawing nil)
|
||||
(uds/select-first-shape))))
|
||||
(uds/select-first-shape))
|
||||
(rlocks/release! :ui/draw)))
|
||||
|
||||
(def ^:private canvas-coords
|
||||
(gpt/point c/canvas-start-x
|
||||
c/canvas-start-y))
|
||||
|
||||
(declare on-draw)
|
||||
(declare on-draw-complete)
|
||||
(declare on-first-draw)
|
||||
|
||||
(defn- initialize-shape-drawing
|
||||
(defn- on-init-draw-generic
|
||||
[shape]
|
||||
(let [mouse (->> (rx/sample 10 wb/mouse-viewport-s)
|
||||
(rx/mapcat (fn [point]
|
||||
|
@ -101,20 +96,21 @@
|
|||
(uds/align-point point)
|
||||
(rx/of point))))
|
||||
(rx/map #(gpt/subtract % canvas-coords)))
|
||||
stoper (->> uuc/actions-s
|
||||
(rx/map :type)
|
||||
(rx/filter #(empty? %))
|
||||
|
||||
stoper (->> wb/mouse-events-s
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/pr-log "mouse-events-s")
|
||||
(rx/take 1))
|
||||
|
||||
firstpos (rx/take 1 mouse)
|
||||
stream (->> mouse
|
||||
(rx/take-until stoper)
|
||||
stream (->> (rx/take-until stoper mouse)
|
||||
(rx/skip-while #(nil? @drawing-shape))
|
||||
(rx/with-latest-from vector wb/mouse-ctrl-s))]
|
||||
|
||||
(rx/subscribe firstpos #(on-first-draw shape %))
|
||||
(rx/subscribe firstpos #(on-draw-start shape %))
|
||||
(rx/subscribe stream on-draw nil on-draw-complete)))
|
||||
|
||||
(defn- on-first-draw
|
||||
(defn- on-draw-start
|
||||
[shape {:keys [x y] :as pt}]
|
||||
(let [shape (geom/setup shape {:x1 x :y1 y :x2 x :y2 y})]
|
||||
(reset! drawing-shape shape)))
|
||||
|
@ -133,4 +129,5 @@
|
|||
(udw/select-for-drawing nil)
|
||||
(uds/select-first-shape))
|
||||
(reset! drawing-position nil)
|
||||
(reset! drawing-shape nil)))
|
||||
(reset! drawing-shape nil)
|
||||
(rlocks/release! :ui/draw)))
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
;; 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) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.movement
|
||||
"Shape movement in workspace logic."
|
||||
(:require [beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.ui.core :as uuc]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.geom.point :as gpt]))
|
||||
|
||||
;; --- Public Api
|
||||
|
||||
(declare watch-movement)
|
||||
|
||||
(defn watch-move-actions
|
||||
[]
|
||||
(let [initialize #(run! watch-movement @wb/selected-shapes-ref)
|
||||
stream (rx/filter #(= "ui.shape.move" (:type %)) uuc/actions-s)]
|
||||
(rx/subscribe stream initialize)))
|
||||
|
||||
;; --- Implementation
|
||||
|
||||
(defn- watch-movement
|
||||
[shape]
|
||||
(let [stoper (->> uuc/actions-s
|
||||
(rx/map :type)
|
||||
(rx/filter empty?)
|
||||
(rx/take 1))
|
||||
stream (->> wb/mouse-delta-s
|
||||
(rx/take-until stoper))]
|
||||
(when @wb/alignment-ref
|
||||
(rs/emit! (uds/initial-align-shape shape)))
|
||||
(rx/subscribe stream #(rs/emit! (uds/move-shape shape %)))))
|
|
@ -1,48 +0,0 @@
|
|||
;; 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) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.resize
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.core :as uuc]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.geom.point :as gpt]))
|
||||
|
||||
(declare initialize)
|
||||
|
||||
;; --- Public Api
|
||||
|
||||
(defn watch-resize-actions
|
||||
[]
|
||||
(as-> uuc/actions-s $
|
||||
(rx/dedupe $)
|
||||
(rx/filter #(= (:type %) "ui.shape.resize") $)
|
||||
(rx/on-value $ initialize)))
|
||||
|
||||
;; --- Implementation
|
||||
|
||||
(declare handle-resize)
|
||||
|
||||
(defn- initialize
|
||||
[event]
|
||||
(let [{:keys [vid shape] :as payload} (:payload event)
|
||||
stoper (->> uuc/actions-s
|
||||
(rx/map :type)
|
||||
(rx/filter #(empty? %))
|
||||
(rx/take 1))
|
||||
stream (->> wb/mouse-delta-s
|
||||
(rx/take-until stoper)
|
||||
(rx/with-latest-from vector wb/mouse-ctrl-s))]
|
||||
(when @wb/alignment-ref
|
||||
(rs/emit! (uds/initial-vertext-align shape vid)))
|
||||
(rx/subscribe stream #(handle-resize shape vid %))))
|
||||
|
||||
(defn- handle-resize
|
||||
[shape vid [delta ctrl?]]
|
||||
(let [params {:vid vid :delta (assoc delta :lock ctrl?)}]
|
||||
(rs/emit! (uds/update-vertex-position shape params))))
|
35
src/uxbox/main/ui/workspace/rlocks.cljs
Normal file
35
src/uxbox/main/ui/workspace/rlocks.cljs
Normal file
|
@ -0,0 +1,35 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.rlocks
|
||||
"Reactive locks abstraction.
|
||||
|
||||
Mainly used for lock the interface to do one concrete user action
|
||||
such can be draw new shape, scroll, move shape, etc, and avoid
|
||||
that other posible actions interfere in the locked one."
|
||||
(:require [beicon.core :as rx]))
|
||||
|
||||
(defonce lock (atom ::none))
|
||||
(defonce stream (rx/bus))
|
||||
|
||||
(defn acquire!
|
||||
([type]
|
||||
(when (= @lock ::none)
|
||||
(println "acquire!" type)
|
||||
(reset! lock type)
|
||||
(rx/push! stream [type nil])))
|
||||
([type payload]
|
||||
(when (= @lock ::none)
|
||||
(reset! lock type)
|
||||
(rx/push! stream [type payload]))))
|
||||
|
||||
(defn release!
|
||||
[type]
|
||||
(when (= @lock type)
|
||||
(println "release!" type)
|
||||
(reset! lock ::none)
|
||||
(rx/push! stream [::none nil])))
|
||||
|
|
@ -6,32 +6,30 @@
|
|||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.scroll
|
||||
"Workspace scroll events handling."
|
||||
(:require [beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as ust]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.core :as uuc]
|
||||
[uxbox.util.mixins :as mx]
|
||||
[uxbox.main.ui.workspace.base :as uuwb]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.rlocks :as rlocks]
|
||||
[uxbox.main.geom.point :as gpt]))
|
||||
|
||||
(defn watch-scroll-interactions
|
||||
[own]
|
||||
(letfn [(handle-scroll-interaction []
|
||||
(let [stoper (->> uuc/actions-s
|
||||
(rx/map :type)
|
||||
(rx/filter #(empty? %))
|
||||
(letfn [(is-space-up? [{:keys [key type]}]
|
||||
(and (= 32 key) (= :keyboard/up type)))
|
||||
|
||||
(on-start []
|
||||
(let [stoper (->> wb/keyboard-events-s
|
||||
(rx/filter is-space-up?)
|
||||
(rx/take 1))
|
||||
local (:rum/local own)
|
||||
initial @uuwb/mouse-viewport-a]
|
||||
initial @wb/mouse-viewport-a
|
||||
stream (rx/take-until stoper wb/mouse-viewport-s)]
|
||||
(swap! local assoc :scrolling true)
|
||||
(as-> uuwb/mouse-viewport-s $
|
||||
(rx/take-until stoper $)
|
||||
(rx/subscribe $ #(on-scroll % initial) nil on-scroll-end))))
|
||||
(rx/subscribe stream #(on-scroll % initial) nil on-scroll-end)))
|
||||
|
||||
(on-scroll-end []
|
||||
(rlocks/release! :workspace/scroll)
|
||||
(let [local (:rum/local own)]
|
||||
(swap! local assoc :scrolling false)))
|
||||
|
||||
|
@ -42,8 +40,7 @@
|
|||
cy (.-scrollTop el)]
|
||||
(set! (.-scrollLeft el) (- cx x))
|
||||
(set! (.-scrollTop el) (- cy y))))]
|
||||
(as-> uuc/actions-s $
|
||||
(rx/map :type $)
|
||||
(rx/dedupe $)
|
||||
(rx/filter #(= "ui.workspace.scroll" %) $)
|
||||
(rx/on-value $ handle-scroll-interaction))))
|
||||
|
||||
(let [stream (->> (rx/map first rlocks/stream)
|
||||
(rx/filter #(= % :workspace/scroll)))]
|
||||
(rx/subscribe stream on-start))))
|
||||
|
|
|
@ -7,18 +7,21 @@
|
|||
|
||||
(ns uxbox.main.ui.workspace.selection
|
||||
"Multiple selection handlers component."
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
(:require [lentes.core :as l]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.mixins :as mx]
|
||||
[uxbox.main.ui.core :as uuc]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.rlocks :as rlocks]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.geom.point :as gpt]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Constants
|
||||
;; --- Refs & Constants
|
||||
|
||||
(def +circle-props+
|
||||
(def ^:private +circle-props+
|
||||
{:r 6
|
||||
:style {:fillOpacity "1"
|
||||
:strokeWidth "1px"
|
||||
|
@ -26,100 +29,150 @@
|
|||
:fill "#31e6e0"
|
||||
:stroke "#28c4d4"})
|
||||
|
||||
;; --- Lenses
|
||||
(defn- focus-selected-shapes
|
||||
[state]
|
||||
(let [selected (get-in state [:workspace :selected])]
|
||||
(mapv #(get-in state [:shapes-by-id %]) selected)))
|
||||
|
||||
(def selected-shapes-ref
|
||||
(letfn [(getter [state]
|
||||
(let [selected (get-in state [:workspace :selected])]
|
||||
(mapv #(get-in state [:shapes-by-id %]) selected)))]
|
||||
(-> (l/lens getter)
|
||||
(l/derive st/state))))
|
||||
(def ^:private selected-shapes-ref
|
||||
(-> (l/lens focus-selected-shapes)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Resize
|
||||
|
||||
(declare on-resize-start)
|
||||
|
||||
(defn watch-resize-actions
|
||||
[]
|
||||
(let [stream (->> rlocks/stream
|
||||
(rx/filter #(= (first %) :shape/resize))
|
||||
(rx/map second))]
|
||||
(rx/subscribe stream on-resize-start)))
|
||||
|
||||
(defn- on-resize
|
||||
[shape vid [delta ctrl?]]
|
||||
(let [params {:vid vid :delta (assoc delta :lock ctrl?)}]
|
||||
(rs/emit! (uds/update-vertex-position shape params))))
|
||||
|
||||
(defn- on-resize-stop
|
||||
[]
|
||||
(rlocks/release! :shape/resize))
|
||||
|
||||
(defn- on-resize-start
|
||||
[[vid shape]]
|
||||
(let [stoper (->> wb/mouse-events-s
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (->> wb/mouse-delta-s
|
||||
(rx/take-until stoper)
|
||||
(rx/with-latest-from vector wb/mouse-ctrl-s))]
|
||||
(when @wb/alignment-ref
|
||||
(rs/emit! (uds/initial-vertext-align shape vid)))
|
||||
(rx/subscribe stream (partial on-resize shape vid) nil on-resize-stop)))
|
||||
|
||||
;; --- Movement
|
||||
|
||||
(declare on-move-start)
|
||||
|
||||
(defn watch-move-actions
|
||||
[]
|
||||
(-> (rx/filter #(= (first %) :shape/move) rlocks/stream)
|
||||
(rx/subscribe #(run! on-move-start @wb/selected-shapes-ref))))
|
||||
|
||||
(defn- on-move-start
|
||||
[shape]
|
||||
(let [stoper (->> wb/mouse-events-s
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (rx/take-until stoper wb/mouse-delta-s)
|
||||
on-move #(rs/emit! (uds/move-shape shape %))
|
||||
on-stop #(rlocks/release! :shape/move)]
|
||||
(when @wb/alignment-ref
|
||||
(rs/emit! (uds/initial-align-shape shape)))
|
||||
(rx/subscribe stream on-move nil on-stop)))
|
||||
|
||||
;; --- Selection Handlers (Component)
|
||||
|
||||
(defn- multiple-selection-handlers-render
|
||||
(mx/defc multiple-selection-handlers
|
||||
[shapes]
|
||||
(let [{:keys [width height x y]} (geom/outer-rect-coll shapes)]
|
||||
(html
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y :width width :height height :stroke-dasharray "5,5"
|
||||
:style {:stroke "#333" :fill "transparent" :stroke-opacity "1"}}]])))
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y
|
||||
:width width
|
||||
:height height
|
||||
:stroke-dasharray "5,5"
|
||||
:style {:stroke "#333" :fill "transparent"
|
||||
:stroke-opacity "1"}}]]))
|
||||
|
||||
(defn- single-selection-handlers-render
|
||||
[shape]
|
||||
(letfn [
|
||||
(on-mouse-down [vid event]
|
||||
(mx/defc single-selection-handlers
|
||||
[{:keys [id] :as shape}]
|
||||
(letfn [(on-mouse-down [vid event]
|
||||
(dom/stop-propagation event)
|
||||
(uuc/acquire-action! "ui.shape.resize"
|
||||
{:vid vid :shape (:id shape)}))
|
||||
|
||||
(on-mouse-up [vid event]
|
||||
(dom/stop-propagation event)
|
||||
(uuc/release-action! "ui.shape.resize"))]
|
||||
(rlocks/acquire! :shape/resize [vid id]))]
|
||||
(let [{:keys [x y width height]} (geom/outer-rect shape)]
|
||||
(html
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y :width width :height height :stroke-dasharray "5,5"
|
||||
:style {:stroke "#333" :fill "transparent" :stroke-opacity "1"}}]
|
||||
[:circle.top
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :top %)
|
||||
:on-mouse-down #(on-mouse-down :top %)
|
||||
:cx (+ x (/ width 2))
|
||||
:cy (- y 2)})]
|
||||
[:circle.right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :right %)
|
||||
:on-mouse-down #(on-mouse-down :right %)
|
||||
:cy (+ y (/ height 2))
|
||||
:cx (+ x width 1)})]
|
||||
[:circle.bottom
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :bottom %)
|
||||
:on-mouse-down #(on-mouse-down :bottom %)
|
||||
:cx (+ x (/ width 2))
|
||||
:cy (+ y height 2)})]
|
||||
[:circle.left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :left %)
|
||||
:on-mouse-down #(on-mouse-down :left %)
|
||||
:cy (+ y (/ height 2))
|
||||
:cx (- x 3)})]
|
||||
[:circle.top-left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :top-left %)
|
||||
:on-mouse-down #(on-mouse-down :top-left %)
|
||||
:cx x
|
||||
:cy y})]
|
||||
[:circle.top-right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :top-right %)
|
||||
:on-mouse-down #(on-mouse-down :top-right %)
|
||||
:cx (+ x width)
|
||||
:cy y})]
|
||||
[:circle.bottom-left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :bottom-left %)
|
||||
:on-mouse-down #(on-mouse-down :bottom-left %)
|
||||
:cx x
|
||||
:cy (+ y height)})]
|
||||
[:circle.bottom-right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-up #(on-mouse-up :bottom-right %)
|
||||
:on-mouse-down #(on-mouse-down :bottom-right %)
|
||||
:cx (+ x width)
|
||||
:cy (+ y height)})]]))))
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y :width width :height height :stroke-dasharray "5,5"
|
||||
:style {:stroke "#333" :fill "transparent" :stroke-opacity "1"}}]
|
||||
[:circle.top
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :top %)
|
||||
:cx (+ x (/ width 2))
|
||||
:cy (- y 2)})]
|
||||
[:circle.right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :right %)
|
||||
:cy (+ y (/ height 2))
|
||||
:cx (+ x width 1)})]
|
||||
[:circle.bottom
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :bottom %)
|
||||
:cx (+ x (/ width 2))
|
||||
:cy (+ y height 2)})]
|
||||
[:circle.left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :left %)
|
||||
:cy (+ y (/ height 2))
|
||||
:cx (- x 3)})]
|
||||
[:circle.top-left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :top-left %)
|
||||
:cx x
|
||||
:cy y})]
|
||||
[:circle.top-right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :top-right %)
|
||||
:cx (+ x width)
|
||||
:cy y})]
|
||||
[:circle.bottom-left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :bottom-left %)
|
||||
:cx x
|
||||
:cy (+ y height)})]
|
||||
[:circle.bottom-right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :bottom-right %)
|
||||
:cx (+ x width)
|
||||
:cy (+ y height)})]])))
|
||||
|
||||
(defn selection-handlers-render
|
||||
(defn- selection-handlers-will-mount
|
||||
[own]
|
||||
(assoc own
|
||||
::sub1 (watch-resize-actions)
|
||||
::sub2 (watch-move-actions)))
|
||||
|
||||
(defn- selection-handlers-will-unmount
|
||||
[own]
|
||||
(.close (::sub1 own))
|
||||
(.close (::sub2 own))
|
||||
(dissoc own ::sub1 ::sub2))
|
||||
|
||||
(mx/defc selection-handlers
|
||||
{:mixins [mx/reactive mx/static]
|
||||
:will-mount selection-handlers-will-mount
|
||||
:will-unmount selection-handlers-will-unmount}
|
||||
[]
|
||||
(let [shapes (mx/react selected-shapes-ref)
|
||||
shapes-num (count shapes)]
|
||||
(cond
|
||||
(> shapes-num 1) (multiple-selection-handlers-render shapes)
|
||||
(= shapes-num 1) (single-selection-handlers-render (first shapes)))))
|
||||
|
||||
(def selection-handlers
|
||||
(mx/component
|
||||
{:render selection-handlers-render
|
||||
:name "selection-handlers"
|
||||
:mixins [mx/reactive mx/static]}))
|
||||
(> shapes-num 1) (multiple-selection-handlers shapes)
|
||||
(= shapes-num 1) (single-selection-handlers (first shapes)))))
|
||||
|
|
|
@ -6,17 +6,15 @@
|
|||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.selrect
|
||||
"Components for indicate the user selection and selected shapes group."
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.constants :as c]
|
||||
"Mouse selection interaction and component."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.core :as uuc]
|
||||
[uxbox.util.mixins :as mx]
|
||||
[uxbox.main.ui.workspace.base :as wb]))
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.rlocks :as rlocks]))
|
||||
|
||||
(defonce position (atom nil))
|
||||
|
||||
|
@ -25,35 +23,29 @@
|
|||
(declare selrect->rect)
|
||||
(declare watch-selrect-actions)
|
||||
|
||||
(defn- selrect-render
|
||||
[own]
|
||||
(when-let [data (mx/react position)]
|
||||
(let [{:keys [x y width height]} (selrect->rect data)]
|
||||
(html
|
||||
[:rect.selection-rect
|
||||
{:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height}]))))
|
||||
|
||||
(defn- selrect-will-mount
|
||||
(defn- will-mount
|
||||
[own]
|
||||
(assoc own ::sub (watch-selrect-actions)))
|
||||
|
||||
(defn- selrect-will-unmount
|
||||
(defn- will-unmount
|
||||
[own]
|
||||
(.close (::sub own))
|
||||
(dissoc own ::sub))
|
||||
|
||||
(def selrect
|
||||
(mx/component
|
||||
{:render selrect-render
|
||||
:name "selrect"
|
||||
:will-mount selrect-will-mount
|
||||
:will-unmount selrect-will-unmount
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
(mx/defc selrect
|
||||
{:will-mount will-mount
|
||||
:will-unmount will-unmount
|
||||
:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(when-let [data (mx/react position)]
|
||||
(let [{:keys [x y width height]} (selrect->rect data)]
|
||||
[:rect.selection-rect
|
||||
{:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height}])))
|
||||
|
||||
;; --- Implementation
|
||||
;; --- Interaction
|
||||
|
||||
(defn- selrect->rect
|
||||
[data]
|
||||
|
@ -82,31 +74,37 @@
|
|||
:width (/ (:width rect) zoom)
|
||||
:height (/ (:height rect) zoom))))
|
||||
|
||||
(declare on-start)
|
||||
|
||||
(defn- watch-selrect-actions
|
||||
[]
|
||||
(letfn [(on-value [pos]
|
||||
(swap! position assoc :current pos))
|
||||
(let [stream (->> (rx/map first rlocks/stream)
|
||||
(rx/filter #(= % :ui/selrect)))]
|
||||
(rx/subscribe stream on-start)))
|
||||
|
||||
(on-complete []
|
||||
(rs/emit! (-> (selrect->rect @position)
|
||||
(translate-to-canvas)
|
||||
(uds/select-shapes)))
|
||||
(reset! position nil))
|
||||
(defn- on-move
|
||||
"Function executed on each mouse movement while selrect
|
||||
interaction is active."
|
||||
[pos]
|
||||
(swap! position assoc :current pos))
|
||||
|
||||
(init []
|
||||
(let [stoper (->> uuc/actions-s
|
||||
(rx/map :type)
|
||||
(rx/filter #(empty? %))
|
||||
(rx/take 1))
|
||||
pos @wb/mouse-viewport-a]
|
||||
(reset! position {:start pos :current pos})
|
||||
(defn- on-complete
|
||||
"Function executed when the selection rect
|
||||
interaction is terminated."
|
||||
[]
|
||||
(rs/emit! (-> (selrect->rect @position)
|
||||
(translate-to-canvas)
|
||||
(uds/select-shapes)))
|
||||
(rlocks/release! :ui/selrect)
|
||||
(reset! position nil))
|
||||
|
||||
(as-> wb/mouse-viewport-s $
|
||||
(rx/take-until stoper $)
|
||||
(rx/subscribe $ on-value nil on-complete))))]
|
||||
|
||||
(as-> uuc/actions-s $
|
||||
(rx/map :type $)
|
||||
(rx/dedupe $)
|
||||
(rx/filter #(= "ui.selrect" %) $)
|
||||
(rx/on-value $ init))))
|
||||
(defn- on-start
|
||||
"Function execution when selrect action is started."
|
||||
[]
|
||||
(let [stoper (->> wb/mouse-events-s
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (rx/take-until stoper wb/mouse-viewport-s)
|
||||
pos @wb/mouse-viewport-a]
|
||||
(reset! position {:start pos :current pos})
|
||||
(rx/subscribe stream on-move nil on-complete)))
|
||||
|
|
|
@ -7,35 +7,30 @@
|
|||
|
||||
(ns uxbox.main.ui.workspace.sidebar.drawtools
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.library :as library]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx]
|
||||
[uxbox.util.dom :as dom]))
|
||||
[uxbox.main.ui.icons :as i]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Lenses
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Refs
|
||||
|
||||
(def ^:private drawing-shape
|
||||
"A focused vision of the drawing property
|
||||
of the workspace status. This avoids
|
||||
rerender the whole toolbox on each workspace
|
||||
change."
|
||||
(as-> (l/in [:workspace :drawing]) $
|
||||
(l/derive $ st/state)))
|
||||
(-> (l/in [:workspace :drawing])
|
||||
(l/derive st/state)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Draw Tools
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Constants
|
||||
|
||||
(def +draw-tool-rect+
|
||||
{:type :rect
|
||||
|
@ -52,6 +47,12 @@
|
|||
:stroke-type :solid
|
||||
:stroke "#000000"})
|
||||
|
||||
(def +draw-tool-path+
|
||||
{:type :path
|
||||
:name "Path"
|
||||
:stroke-type :solid
|
||||
:stroke "#000000"})
|
||||
|
||||
(def +draw-tool-text+
|
||||
{:type :text
|
||||
:name "Text"
|
||||
|
@ -77,41 +78,38 @@
|
|||
{:icon i/text
|
||||
:help (tr "ds.help.text")
|
||||
:shape +draw-tool-text+
|
||||
:priority 4}})
|
||||
:priority 4}
|
||||
:path
|
||||
{:icon i/curve
|
||||
:help (tr "ds.help.path")
|
||||
:shape +draw-tool-path+
|
||||
:priority 5}})
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Draw Tool Box
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Draw Toolbox (Component)
|
||||
|
||||
(defn- select-for-draw
|
||||
[shape]
|
||||
(rs/emit! (dw/select-for-drawing shape)))
|
||||
|
||||
(defn draw-tools-render
|
||||
[open-toolboxes]
|
||||
(mx/defc draw-toolbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own]
|
||||
(let [workspace (mx/react wb/workspace-ref)
|
||||
drawing (mx/react drawing-shape)
|
||||
close #(rs/emit! (dw/toggle-flag :drawtools))
|
||||
tools (->> (into [] +draw-tools+)
|
||||
(sort-by (comp :priority second)))]
|
||||
(html
|
||||
[:div#form-tools.tool-window.drawing-tools
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/window]
|
||||
[:span (tr "ds.draw-tools")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
(for [[key props] tools
|
||||
:let [selected? (= drawing (:shape props))]]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (:help props)
|
||||
:class (when selected? "selected")
|
||||
:key (name key)
|
||||
:on-click (partial select-for-draw (:shape props))}
|
||||
(:icon props)])]])))
|
||||
|
||||
(def draw-toolbox
|
||||
(mx/component
|
||||
{:render draw-tools-render
|
||||
:name "draw-tools"
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
[:div#form-tools.tool-window.drawing-tools
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/window]
|
||||
[:span (tr "ds.draw-tools")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
(for [[key props] tools
|
||||
:let [selected? (= drawing (:shape props))]]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (:help props)
|
||||
:class (when selected? "selected")
|
||||
:key (name key)
|
||||
:on-click (partial select-for-draw (:shape props))}
|
||||
(:icon props)])]]))
|
||||
|
|
Loading…
Add table
Reference in a new issue