0
Fork 0
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:
Andrey Antukh 2016-08-10 19:15:13 +03:00
parent 5afc297e93
commit b8e5239ee3
No known key found for this signature in database
GPG key ID: 4DFEBCB8316A8B95
18 changed files with 468 additions and 545 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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