0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-09 00:28:20 -05:00
penpot/frontend/uxbox/ui/workspace/canvas.cljs
2016-01-07 17:48:20 +02:00

260 lines
9.3 KiB
Clojure

(ns uxbox.ui.workspace.canvas
(:require [sablono.core :as html :refer-macros [html]]
[rum.core :as rum]
[beicon.core :as rx]
[cats.labs.lens :as l]
[goog.events :as events]
[uxbox.router :as r]
[uxbox.rstore :as rs]
[uxbox.state :as st]
[uxbox.shapes :as shapes]
[uxbox.util.lens :as ul]
[uxbox.library.icons :as _icons]
[uxbox.data.projects :as dp]
[uxbox.data.workspace :as dw]
[uxbox.ui.mixins :as mx]
[uxbox.ui.dom :as dom]
[uxbox.ui.util :as util]
[uxbox.ui.workspace.base :as wb]
[uxbox.ui.workspace.grid :refer (grid)]
[uxbox.ui.workspace.options :refer (element-opts)])
(:import goog.events.EventType))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Lenses
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:static shapes-by-id
(as-> (l/key :shapes-by-id) $
(l/focus-atom $ st/state)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Background
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn background-render
[]
(html
[:rect
{:x 0 :y 0 :width "100%" :height "100%" :fill "white"}]))
(def background
(util/component
{:render background-render
:name "background"
:mixins [mx/static]}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Select Rect
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn selrect-render
[own]
(when-let [data (rum/react wb/selrect-pos)]
(let [{:keys [x y width height]} (wb/selrect->rect data)]
(html
[:rect.selection-rect
{:x x
:y y
:width width
:height height}]))))
(def ^:static selrect
(util/component
{:render selrect-render
:name "selrect"
:mixins [mx/static rum/reactive]}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Selected shapes group
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private
selection-circle-style
{:fillOpacity "0.5"
:strokeWidth "1px"
:vectorEffect "non-scaling-stroke"})
(def ^:private
default-selection-props
{:r 4 :style selection-circle-style
:fill "lavender"
:stroke "gray"})
(defn- selected-shapes-render
[own shapes selected]
(let [local (:rum/local own)
x (apply min (map :x shapes))
y (apply min (map :y shapes))
x' (apply max (map (fn [{:keys [x width]}] (+ x width)) shapes))
y' (apply max (map (fn [{:keys [y height]}] (+ y height)) shapes))
width (- x' x)
height (- y' y)]
(letfn [(on-mouse-down [event]
(dom/stop-propagation event)
(swap! local assoc :init-coords [x y])
(reset! wb/shapes-dragging? true))
(on-mouse-up [event]
(dom/stop-propagation event)
(reset! wb/shapes-dragging? false))]
(html
[:g {:class "shape selected"
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
(for [item shapes]
(shapes/render item))
[:g.controls
[:rect {:x x :y y :width width :height height
:style {:stroke "black" :fill "transparent"
:stroke-opacity "0.5"}}]
[:circle.top-left (merge default-selection-props
{:cx x :cy y})]
[:circle.top-right (merge default-selection-props
{:cx (+ x width) :cy y})]
[:circle.bottom-left (merge default-selection-props
{:cx x :cy (+ y height)})]
[:circle.bottom-right (merge default-selection-props
{:cx (+ x width) :cy (+ y height)})]]]))))
(def selected-shapes
(util/component
{:render selected-shapes-render
:name "selected-shapes"
:mixins [mx/static rum/reactive (mx/local {})]}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shape
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn shape-render
[own shape selected]
(let [{:keys [id x y width height] :as shape} shape
selected? (contains? selected id)]
(letfn [(on-mouse-down [event]
(let [local (:rum/local own)]
(dom/stop-propagation event)
(swap! local assoc :init-coords [x y])
(reset! wb/shapes-dragging? true))
(cond
(and (not selected?)
(empty? selected))
(rs/emit! (dw/select-shape id))
(and (not selected?)
(not (empty? selected)))
(if (.-ctrlKey event)
(rs/emit! (dw/select-shape id))
(rs/emit! (dw/deselect-all)
(dw/select-shape id)))))
(on-mouse-up [event]
(dom/stop-propagation event)
(reset! wb/shapes-dragging? false))]
(html
[:g.shape {:class (when selected? "selected")
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
(shapes/render shape)
(if selected?
[:g.controls
[:rect {:x x :y y :width width :height height
:style {:stroke "black" :fill "transparent"
:stroke-opacity "0.5"}}]
[:circle.top-left (merge default-selection-props
{:cx x :cy y})]
[:circle.top-right (merge default-selection-props
{:cx (+ x width) :cy y})]
[:circle.bottom-left (merge default-selection-props
{:cx x :cy (+ y height)})]
[:circle.bottom-right (merge default-selection-props
{:cx (+ x width) :cy (+ y height)})]])]))))
(def shape
(util/component
{:render shape-render
:name "shape"
:mixins [mx/static (mx/local {})]}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Canvas
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- canvas-did-mount
[own]
(letfn [(on-mousemove [event page [offset-x offset-y]]
(let [x (.-clientX event)
y (.-clientY event)
event {:id (:id page)
:coords [(- x offset-x)
(- y offset-y)]}]
(rx/push! wb/mouse-b event)))]
(let [[page] (:rum/props own)
canvas (util/get-ref-dom own (str "canvas" (:id page)))
brect (.getBoundingClientRect canvas)
brect [(.-left brect) (.-top brect)]
key (events/listen js/document EventType.MOUSEMOVE
#(on-mousemove % page brect))]
(swap! wb/bounding-rect assoc (:id page) brect)
(assoc own ::eventkey key))))
(defn- canvas-will-unmount
[own]
(let [key (::eventkey own)
[page] (:rum/props own)]
(swap! wb/bounding-rect dissoc (:id page))
(events/unlistenByKey key)
(dissoc own ::eventkey)))
(defn- canvas-transfer-state
[old-own own]
(let [key (::eventkey old-own)]
(assoc own ::eventkey key)))
(defn- canvas-render
[own {:keys [width height id] :as page}]
(let [workspace (rum/react wb/workspace-state)
shapes-by-id (rum/react shapes-by-id)
workspace-selected (:selected workspace)
shapes (map #(get shapes-by-id %) (:shapes page))
shapes-selected (filter (comp workspace-selected :id) shapes)
shapes-notselected (filter (comp not workspace-selected :id) shapes)]
(letfn [(on-mouse-down [event]
(dom/stop-propagation event)
(when-not (empty? shapes-selected)
(rs/emit! (dw/deselect-all)))
(reset! wb/selrect-dragging? true))
(on-mouse-up [event]
(dom/stop-propagation event)
(reset! wb/shapes-dragging? false)
(reset! wb/selrect-dragging? false))]
(html
[:svg#page-canvas.page-canvas {:x wb/document-start-x
:y wb/document-start-y
:ref (str "canvas" id)
:width width
:height height
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
(background)
(grid 1)
[:svg.page-layout {}
(for [item shapes-notselected
:let [component (shape item workspace-selected)]]
(rum/with-key component (str (:id item))))
(cond
(= (count shapes-selected) 1)
(let [item (first shapes-selected)]
(shape item workspace-selected))
(> (count shapes-selected) 1)
(selected-shapes shapes-selected))
(selrect)]]))))
(def canvas
(util/component
{:render canvas-render
:did-mount canvas-did-mount
:will-unmount canvas-will-unmount
:transfer-state canvas-transfer-state
:name "canvas"
:mixins [mx/static rum/reactive]}))