0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-11 01:28:30 -05:00

Remodeled the mouse position <-> canvas coords matching.

This commit is contained in:
Andrey Antukh 2016-01-03 18:46:19 +02:00
parent f8a6342b98
commit beda689dfc
3 changed files with 168 additions and 149 deletions

View file

@ -2,7 +2,7 @@
(:require [beicon.core :as rx] (:require [beicon.core :as rx]
[cats.labs.lens :as l] [cats.labs.lens :as l]
[uxbox.rstore :as rs] [uxbox.rstore :as rs]
[uxbox.state :as s] [uxbox.state :as st]
[uxbox.data.projects :as dp] [uxbox.data.projects :as dp]
[uxbox.data.workspace :as dw] [uxbox.data.workspace :as dw]
[uxbox.util.lens :as ul] [uxbox.util.lens :as ul]
@ -16,32 +16,20 @@
(def ^:static project-state (def ^:static project-state
(as-> (ul/dep-in [:projects-by-id] [:workspace :project]) $ (as-> (ul/dep-in [:projects-by-id] [:workspace :project]) $
(l/focus-atom $ s/state))) (l/focus-atom $ st/state)))
(def ^:static page-state (def ^:static page-state
(as-> (ul/dep-in [:pages-by-id] [:workspace :page]) $ (as-> (ul/dep-in [:pages-by-id] [:workspace :page]) $
(l/focus-atom $ s/state))) (l/focus-atom $ st/state)))
(def ^:static pages-state (def ^:static pages-state
(as-> (ul/getter #(let [pid (get-in % [:workspace :project])] (as-> (ul/getter #(let [pid (get-in % [:workspace :project])]
(dp/project-pages % pid))) $ (dp/project-pages % pid))) $
(l/focus-atom $ s/state))) (l/focus-atom $ st/state)))
(def ^:static shapes-state
(as-> (ul/getter (fn [state]
(let [pid (get-in state [:workspace :page])
shapes (->> (vals (:shapes-by-id state))
(filter #(= (:page %) pid)))]
(into [] shapes)))) $
(l/focus-atom $ s/state)))
(def ^:static workspace-state (def ^:static workspace-state
(as-> (l/in [:workspace]) $ (as-> (l/in [:workspace]) $
(l/focus-atom $ s/state))) (l/focus-atom $ st/state)))
(def ^:static selected-state
(as-> (l/in [:workspace :selected]) $
(l/focus-atom $ s/state)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Scroll Stream ;; Scroll Stream
@ -68,12 +56,14 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defonce shapes-dragging? (atom false)) (defonce shapes-dragging? (atom false))
(defonce selrect-dragging? (atom false)) (defonce selrect-dragging? (atom false))
(defonce selrect-pos (atom nil)) (defonce selrect-pos (atom nil))
(defonce mouse-b (rx/bus)) (defonce mouse-b (rx/bus))
(defonce mouse-s (rx/dedupe mouse-b)) (defonce mouse-s
(->> mouse-b
(rx/filter #(= (:id %) (:id @page-state)))
(rx/map :coords)))
;; Deltas ;; Deltas
@ -94,8 +84,11 @@
(as-> mouse-delta-s $ (as-> mouse-delta-s $
(rx/filter #(deref shapes-dragging?) $) (rx/filter #(deref shapes-dragging?) $)
(rx/on-value $ (fn [delta] (rx/on-value $ (fn [delta]
(doseq [sid @selected-state] (let [pageid (get-in @st/state [:workspace :page])
(rs/emit! (dw/move-shape sid delta))))))) selected (get-in @st/state [:workspace :selected])
page (get-in @st/state [:pages-by-id pageid])]
(doseq [sid (filter selected (:shapes page))]
(rs/emit! (dw/move-shape sid delta))))))))
(defn selrect->rect (defn selrect->rect
[data] [data]
@ -140,38 +133,12 @@
;; Materialized views ;; Materialized views
(defonce mouse-position (rx/to-atom (rx/sample 50 mouse-s))) (defonce mouse-position
(->> mouse-s
(rx/sample 50)
(rx/to-atom)))
(defn- mouse-mixin-did-mount (defonce bounding-rect (atom {}))
[own]
(letfn [(on-mousemove [event]
(let [canvas (util/get-ref-dom own "canvas")
brect (.getBoundingClientRect canvas)
offset-x (.-left brect)
offset-y (.-top brect)
x (.-clientX event)
y (.-clientY event)]
(rx/push! mouse-b [(- x offset-x)
(- y offset-y)])))]
(let [key (events/listen js/document EventType.MOUSEMOVE on-mousemove)]
(js/console.log "mouse-mixin-did-mount" key)
(assoc own ::eventkey key))))
(defn- mouse-mixin-will-unmount
[own]
(let [key (::eventkey own)]
(events/unlistenByKey key)
(dissoc own ::eventkey)))
(defn- mouse-mixin-transfer-state
[old-own own]
(let [key (::eventkey old-own)]
(assoc own ::eventkey key)))
(def ^:static mouse-mixin
{:did-mount mouse-mixin-did-mount
:will-unmount mouse-mixin-will-unmount
:transfer-state mouse-mixin-transfer-state})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Constants ;; Constants

View file

@ -2,10 +2,13 @@
(:require [sablono.core :as html :refer-macros [html]] (:require [sablono.core :as html :refer-macros [html]]
[rum.core :as rum] [rum.core :as rum]
[beicon.core :as rx] [beicon.core :as rx]
[cats.labs.lens :as l]
[goog.events :as events]
[uxbox.router :as r] [uxbox.router :as r]
[uxbox.rstore :as rs] [uxbox.rstore :as rs]
[uxbox.state :as s] [uxbox.state :as st]
[uxbox.shapes :as shapes] [uxbox.shapes :as shapes]
[uxbox.util.lens :as ul]
[uxbox.library.icons :as _icons] [uxbox.library.icons :as _icons]
[uxbox.data.projects :as dp] [uxbox.data.projects :as dp]
[uxbox.data.workspace :as dw] [uxbox.data.workspace :as dw]
@ -14,7 +17,17 @@
[uxbox.ui.util :as util] [uxbox.ui.util :as util]
[uxbox.ui.workspace.base :as wb] [uxbox.ui.workspace.base :as wb]
[uxbox.ui.workspace.grid :refer (grid)] [uxbox.ui.workspace.grid :refer (grid)]
[uxbox.ui.workspace.toolboxes :as toolboxes])) [uxbox.ui.workspace.options :refer (element-opts)]
[uxbox.ui.workspace.toolboxes :as toolboxes])
(:import goog.events.EventType))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Lenses
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:static shapes-by-id
(as-> (l/key :shapes-by-id) $
(l/focus-atom $ st/state)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Background ;; Background
@ -33,7 +46,28 @@
:mixins [mx/static]})) :mixins [mx/static]}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shape ;; 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 (def ^:private
@ -48,10 +82,56 @@
:fill "lavender" :fill "lavender"
:stroke "gray"}) :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 (defn shape-render
[own {:keys [id x y width height] :as shape}] [own shape selected]
(let [selected (rum/react wb/selected-state) (let [{:keys [id x y width height] :as shape} shape
selected? (contains? selected id)] selected? (contains? selected id)]
(println "shape-render" id)
(letfn [(on-mouse-down [event] (letfn [(on-mouse-down [event]
(let [local (:rum/local own)] (let [local (:rum/local own)]
(dom/stop-propagation event) (dom/stop-propagation event)
@ -94,88 +174,55 @@
(util/component (util/component
{:render shape-render {:render shape-render
:name "shape" :name "shape"
:mixins [mx/static rum/reactive (mx/local {})]})) :mixins [mx/static (mx/local {})]}))
(defn- selected-shapes-render
[own shapes]
(let [selected (rum/react wb/selected-state)
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 {})]}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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]}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Canvas ;; Canvas
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn canvas-render (defn- canvas-did-mount
[own] [own]
(let [page (rum/react wb/page-state) (letfn [(on-mousemove [event page [offset-x offset-y]]
shapes (rum/react wb/shapes-state) (let [x (.-clientX event)
selected-ids (rum/react wb/selected-state) y (.-clientY event)
selected (filter (comp selected-ids :id) shapes) event {:id (:id page)
nonselected (filter (comp not selected-ids :id) shapes) :coords [(- x offset-x)
page-width (:width page) (- y offset-y)]}]
page-height (:height page)] (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}]
(println "canvas-render" id)
(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] (letfn [(on-mouse-down [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not (empty? selected-ids) (when-not (empty? shapes-selected)
(rs/emit! (dw/deselect-all))) (rs/emit! (dw/deselect-all)))
(reset! wb/selrect-dragging? true)) (reset! wb/selrect-dragging? true))
(on-mouse-up [event] (on-mouse-up [event]
@ -183,30 +230,34 @@
(reset! wb/shapes-dragging? false) (reset! wb/shapes-dragging? false)
(reset! wb/selrect-dragging? false))] (reset! wb/selrect-dragging? false))]
(html (html
[:svg.page-canvas {:x wb/document-start-x [:svg#page-canvas.page-canvas {:x wb/document-start-x
:y wb/document-start-y :y wb/document-start-y
:ref "canvas" :ref (str "canvas" id)
:width page-width :width width
:height page-height :height height
:on-mouse-down on-mouse-down :on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up} :on-mouse-up on-mouse-up}
(background) (background)
(grid 1) (grid 1)
[:svg.page-layout {} [:svg.page-layout {}
(for [item nonselected] (for [item shapes-notselected
(rum/with-key (shape item) (str (:id item)))) :let [component (shape item workspace-selected)]]
(rum/with-key component (str (:id item))))
(cond (cond
(= (count selected) 1) (= (count shapes-selected) 1)
(shape (first selected)) (let [item (first shapes-selected)]
(shape item workspace-selected))
(> (count selected) 1)
(selected-shapes selected))
(> (count shapes-selected) 1)
(selected-shapes shapes-selected))
(selrect)]])))) (selrect)]]))))
(def canvas (def canvas
(util/component (util/component
{:render canvas-render {:render canvas-render
:did-mount canvas-did-mount
:will-unmount canvas-will-unmount
:transfer-state canvas-transfer-state
:name "canvas" :name "canvas"
:mixins [rum/reactive wb/mouse-mixin]})) :mixins [mx/static rum/reactive]}))

View file

@ -33,6 +33,7 @@
(defn viewport-render (defn viewport-render
[own] [own]
(let [workspace (rum/react wb/workspace-state) (let [workspace (rum/react wb/workspace-state)
page (rum/react wb/page-state)
drawing? (:drawing workspace) drawing? (:drawing workspace)
zoom 1] zoom 1]
(html (html
@ -41,8 +42,8 @@
:on-click #(on-click % workspace) :on-click #(on-click % workspace)
:class (when drawing? "drawing")} :class (when drawing? "drawing")}
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")} [:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
(canvas) (if page
#_(grid zoom)]]))) (canvas page))]])))
(def viewport (def viewport
(util/component (util/component