♻️ Reorder functions, for more clarity, and add some comments

This commit is contained in:
Andrés Moya 2021-07-08 15:20:43 +02:00 committed by Alonso Torres
parent 741d3050ad
commit 56795f8d26
8 changed files with 560 additions and 529 deletions

View file

@ -1139,33 +1139,6 @@
(gpr/assign-proportions))))]
(rx/of (dch/update-shapes [id] assign-proportions))))))
;; --- Update Shape Position
(s/def ::x number?)
(s/def ::y number?)
(s/def ::position
(s/keys :opt-un [::x ::y]))
(defn update-position
[id position]
(us/verify ::us/uuid id)
(us/verify ::position position)
(ptk/reify ::update-position
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
shape (get objects id)
bbox (-> shape :points gsh/points->selrect)
cpos (gpt/point (:x bbox) (:y bbox))
pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox)))
displ (gmt/translate-matrix (gpt/subtract pos cpos))]
(rx/of (dwt/set-modifiers [id] {:displacement displ})
(dwt/apply-modifiers [id]))))))
;; --- Update Shape Flags
(defn update-shape-flags
@ -1811,15 +1784,13 @@
;; Transform
(d/export dwt/start-rotate)
(d/export dwt/start-resize)
(d/export dwt/update-dimensions)
(d/export dwt/start-rotate)
(d/export dwt/increase-rotation)
(d/export dwt/start-move-selected)
(d/export dwt/move-selected)
(d/export dwt/set-rotation)
(d/export dwt/increase-rotation)
(d/export dwt/set-modifiers)
(d/export dwt/apply-modifiers)
(d/export dwt/update-dimensions)
(d/export dwt/update-position)
(d/export dwt/flip-horizontal-selected)
(d/export dwt/flip-vertical-selected)

View file

@ -24,18 +24,13 @@
[cljs.spec.alpha :as s]
[potok.core :as ptk]))
;; -- Declarations
(declare set-modifiers)
(declare set-rotation)
(declare apply-modifiers)
;; -- Helpers --------------------------------------------------------
;; -- Helpers
;; For each of the 8 handlers gives the modifier for resize
;; For each of the 8 handlers gives the multiplier for resize
;; for example, right will only grow in the x coordinate and left
;; will grow in the inverse of the x coordinate
(def ^:private handler-modifiers
(def ^:private handler-multipliers
{:right [ 1 0]
:bottom [ 0 1]
:left [-1 0]
@ -45,13 +40,16 @@
:bottom-right [ 1 1]
:bottom-left [-1 1]})
;; Given a handler returns the coordinate origin for resizes
;; this is the opposite of the handler so for right we want the
;; left side as origin of the resize
;; sx, sy => start x/y
;; mx, my => middle x/y
;; ex, ey => end x/y
(defn- handler-resize-origin [{sx :x sy :y :keys [width height]} handler]
(defn- handler-resize-origin
"Given a handler, return the coordinate origin for resizes.
This is the opposite of the handler so for right we want the
left side as origin of the resize.
sx, sy => start x/y
mx, my => middle x/y
ex, ey => end x/y
"
[{sx :x sy :y :keys [width height]} handler]
(let [mx (+ sx (/ width 2))
my (+ sy (/ height 2))
ex (+ sx width)
@ -95,8 +93,193 @@
(update [_ state]
(update state :workspace-local dissoc :transform))))
;; -- RESIZE
;; -- Temporary modifiers -------------------------------------------
;; During an interactive transformation of shapes (e.g. when resizing or rotating
;; a group with the mouse), there are a lot of objects that need to be modified
;; (in this case, the group and all its children).
;;
;; To avoid updating the shapes theirselves, and forcing redraw of all components
;; that depend on the "objects" global state, we set a "modifiers" structure, with
;; the changes that need to be applied, and store it in :workspace-modifiers global
;; variable. The viewport reads this and merges it into the objects list it uses to
;; paint the viewport content, redrawing only the objects that have new modifiers.
;;
;; When the interaction is finished (e.g. user releases mouse button), the
;; apply-modifiers event is done, that consolidates all modifiers into the base
;; geometric attributes of the shapes.
(declare set-modifiers-recursive)
(declare check-delta)
(declare set-local-displacement)
(declare clear-local-transform)
(defn- set-modifiers
([ids] (set-modifiers ids nil))
([ids modifiers]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::set-modifiers
ptk/UpdateEvent
(update [_ state]
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))]
(reduce (fn [state id]
(update state :workspace-modifiers
#(set-modifiers-recursive %
objects
(get objects id)
modifiers
nil
nil)))
state
ids))))))
;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
(defn- set-rotation-modifiers
([angle shapes]
(set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect)))
([angle shapes center]
(ptk/reify ::set-rotation-modifiers
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
id->obj #(get objects %)
get-children (fn [shape] (map id->obj (cp/get-children (:id shape) objects)))
shapes (->> shapes (into [] (remove #(get % :blocked false))))
shapes (->> shapes (mapcat get-children) (concat shapes))
update-shape
(fn [modifiers shape]
(let [rotate-modifiers (gsh/rotation-modifiers shape center angle)]
(assoc-in modifiers [(:id shape) :modifiers] rotate-modifiers)))]
(-> state
(update :workspace-modifiers
#(reduce update-shape % shapes))))))))
(defn- apply-modifiers
[ids]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
children-ids (->> ids (mapcat #(cp/get-children % objects)))
ids-with-children (d/concat [] children-ids ids)
object-modifiers (get state :workspace-modifiers)
ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)]
(rx/of (dwu/start-undo-transaction)
(dch/update-shapes
ids-with-children
(fn [shape]
(-> shape
(merge (get object-modifiers (:id shape)))
(gsh/transform-shape)))
{:reg-objects? true
:ignore-tree ignore-tree
;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes
:attrs [:selrect :points
:x :y
:width :height
:content
:transform
:transform-inverse
:rotation
:flip-x
:flip-y]})
(clear-local-transform)
(dwu/commit-undo-transaction))))))
(defn- set-modifiers-recursive
[modif-tree objects shape modifiers root transformed-root]
(let [children (->> (get shape :shapes [])
(map #(get objects %)))
transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers))
[root transformed-root ignore-geometry?]
(check-delta shape root transformed-shape transformed-root objects)
modifiers (assoc modifiers :ignore-geometry? ignore-geometry?)
set-child (fn [modif-tree child]
(let [child-modifiers (gsh/calc-child-modifiers shape
child
modifiers)]
(set-modifiers-recursive modif-tree
objects
child
child-modifiers
root
transformed-root)))]
(reduce set-child
(update-in modif-tree [(:id shape) :modifiers] #(merge % modifiers))
children)))
(defn- check-delta
"If the shape is a component instance, check its relative position respect the
root of the component, and see if it changes after applying a transformation."
[shape root transformed-shape transformed-root objects]
(let [root (cond
(:component-root? shape)
shape
(nil? root)
(cp/get-root-shape shape objects)
:else root)
transformed-root (cond
(:component-root? transformed-shape)
transformed-shape
(nil? transformed-root)
(cp/get-root-shape transformed-shape objects)
:else transformed-root)
shape-delta (when root
(gpt/point (- (:x shape) (:x root))
(- (:y shape) (:y root))))
transformed-shape-delta (when transformed-root
(gpt/point (- (:x transformed-shape) (:x transformed-root))
(- (:y transformed-shape) (:y transformed-root))))
ignore-geometry? (= shape-delta transformed-shape-delta)]
[root transformed-root ignore-geometry?]))
(defn- set-local-displacement [point]
(ptk/reify ::start-local-displacement
ptk/UpdateEvent
(update [_ state]
(let [mtx (gmt/translate-matrix point)]
(-> state
(assoc-in [:workspace-local :modifiers] {:displacement mtx}))))))
(defn- clear-local-transform []
(ptk/reify ::clear-local-transform
ptk/UpdateEvent
(update [_ state]
(-> state
(dissoc :workspace-modifiers)
(update :workspace-local dissoc :modifiers :current-move-selected)))))
;; -- Resize --------------------------------------------------------
(defn start-resize
"Enter mouse resize mode, until mouse button is released."
[handler ids shape]
(letfn [(resize [shape initial layout [point lock? point-snap]]
(let [{:keys [width height]} (:selrect shape)
@ -113,12 +296,12 @@
lock? (or lock? scale-text)
;; Vector modifiers depending on the handler
handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y))
handler-mult (let [[x y] (handler-multipliers handler)] (gpt/point x y))
;; Difference between the origin point in the coordinate system of the rotation
deltav (-> (gpt/to-vec initial (if (= rotation 0) point-snap point))
(gpt/transform (gmt/rotate-matrix (- rotation)))
(gpt/multiply handler-modif))
(gpt/multiply handler-mult))
;; Resize vector
scalev (gpt/divide (gpt/add shapev deltav) shapev)
@ -185,8 +368,43 @@
(rx/of (apply-modifiers ids)
(finish-transform))))))))
(defn update-dimensions
"Change size of shapes, from the sideber options form."
[ids attr value]
(us/verify (s/coll-of ::us/uuid) ids)
(us/verify #{:width :height} attr)
(us/verify ::us/number value)
(ptk/reify ::update-dimensions
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects])]
(reduce (fn [state id]
(let [shape (get objects id)
modifiers (gsh/resize-modifiers shape attr value)]
(update state :workspace-modifiers
#(set-modifiers-recursive %
objects
shape
modifiers
nil
nil))))
state
ids)))
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))]
(rx/of (apply-modifiers ids))))))
;; -- Rotate --------------------------------------------------------
(defn start-rotate
"Enter mouse rotate mode, until mouse button is released."
[shapes]
(ptk/reify ::start-rotate
ptk/UpdateEvent
@ -218,19 +436,37 @@
(rx/with-latest vector ms/mouse-position-ctrl)
(rx/map (fn [[pos ctrl?]]
(let [delta-angle (calculate-angle pos ctrl?)]
(set-rotation delta-angle shapes group-center))))
(set-rotation-modifiers delta-angle shapes group-center))))
(rx/take-until stoper))
(rx/of (apply-modifiers (map :id shapes))
(finish-transform)))))))
;; -- MOVE
(defn increase-rotation
"Rotate shapes a fixed angle, from a keyboard action."
[ids rotation]
(ptk/reify ::increase-rotation
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
rotate-shape (fn [shape]
(let [delta (- rotation (:rotation shape))]
(set-rotation-modifiers delta [shape])))]
(rx/concat
(rx/from (->> ids (map #(get objects %)) (map rotate-shape)))
(rx/of (apply-modifiers ids)))))))
;; -- Move ----------------------------------------------------------
(declare start-move)
(declare start-move-duplicate)
(declare set-local-displacement)
(declare clear-local-transform)
(declare calculate-frame-for-move)
(declare get-displacement)
(defn start-move-selected
"Enter mouse move mode, until mouse button is released."
[]
(ptk/reify ::start-move-selected
ptk/WatchEvent
@ -255,7 +491,8 @@
;; Otherwise just plain old move
(rx/of (start-move initial selected)))))))))))
(defn start-move-duplicate [from-position]
(defn- start-move-duplicate
[from-position]
(ptk/reify ::start-move-selected
ptk/WatchEvent
(watch [_ _ stream]
@ -264,45 +501,7 @@
(rx/first)
(rx/map #(start-move from-position))))))
(defn calculate-frame-for-move [ids]
(ptk/reify ::calculate-frame-for-move
ptk/WatchEvent
(watch [it state _]
(let [position @ms/mouse-position
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
frame-id (cp/frame-id-by-position objects position)
moving-shapes (->> ids
(cp/clean-loops objects)
(map #(get objects %))
(remove #(or (nil? %)
(= (:frame-id %) frame-id))))
rch [{:type :mov-objects
:page-id page-id
:parent-id frame-id
:shapes (mapv :id moving-shapes)}]
uch (->> moving-shapes
(reverse)
(mapv (fn [shape]
{:type :mov-objects
:page-id page-id
:parent-id (:parent-id shape)
:index (cp/get-index-in-parent objects (:id shape))
:shapes [(:id shape)]})))]
(when-not (empty? uch)
(rx/of dwu/pop-undo-into-transaction
(dch/commit-changes {:redo-changes rch
:undo-changes uch
:origin it})
(dwu/commit-undo-transaction)
(dwc/expand-collapse frame-id)))))))
(defn start-move
(defn- start-move
([from-position] (start-move from-position nil))
([from-position ids]
(ptk/reify ::start-move
@ -349,19 +548,10 @@
(calculate-frame-for-move ids)
(finish-transform)))))))))
(defn- get-displacement
"Retrieve the correct displacement delta point for the
provided direction speed and distances thresholds."
[direction]
(case direction
:up (gpt/point 0 (- 1))
:down (gpt/point 0 1)
:left (gpt/point (- 1) 0)
:right (gpt/point 1 0)))
(s/def ::direction #{:up :down :right :left})
(defn move-selected
"Move shapes a fixed increment in one direction, from a keyboard action."
[direction shift?]
(us/verify ::direction direction)
(us/verify boolean? shift?)
@ -405,209 +595,83 @@
(finish-transform))))
(rx/empty))))))
(s/def ::x number?)
(s/def ::y number?)
(s/def ::position
(s/keys :opt-un [::x ::y]))
;; -- Apply modifiers
(defn- check-delta
"If the shape is a component instance, check its relative position respect the
root of the component, and see if it changes after applying a transformation."
[shape root transformed-shape transformed-root objects]
(let [root (cond
(:component-root? shape)
shape
(nil? root)
(cp/get-root-shape shape objects)
:else root)
transformed-root (cond
(:component-root? transformed-shape)
transformed-shape
(nil? transformed-root)
(cp/get-root-shape transformed-shape objects)
:else transformed-root)
shape-delta (when root
(gpt/point (- (:x shape) (:x root))
(- (:y shape) (:y root))))
transformed-shape-delta (when transformed-root
(gpt/point (- (:x transformed-shape) (:x transformed-root))
(- (:y transformed-shape) (:y transformed-root))))
ignore-geometry? (= shape-delta transformed-shape-delta)]
[root transformed-root ignore-geometry?]))
(defn- set-modifiers-recursive
"Apply the modifiers to one shape, and the corresponding ones to all children,
depending on the child constraints. The modifiers are not directly applied to
the objects tree, but to a separated structure (modif-tree), that may be
merged later with the real objects. This way, the objects are changed only
once, avoiding unnecesary redrawings."
[modif-tree objects shape modifiers root transformed-root]
(let [children (->> (get shape :shapes [])
(map #(get objects %)))
transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers))
[root transformed-root ignore-geometry?]
(check-delta shape root transformed-shape transformed-root objects)
modifiers (assoc modifiers :ignore-geometry? ignore-geometry?)
set-child (fn [modif-tree child]
(let [child-modifiers (gsh/calc-child-modifiers shape
child
modifiers)]
(set-modifiers-recursive modif-tree
objects
child
child-modifiers
root
transformed-root)))]
(reduce set-child
(update-in modif-tree [(:id shape) :modifiers] #(merge % modifiers))
children)))
(defn set-modifiers
([ids] (set-modifiers ids nil))
([ids modifiers]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::set-modifiers
ptk/UpdateEvent
(update [_ state]
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))]
(reduce (fn [state id]
(update state :workspace-modifiers
#(set-modifiers-recursive %
objects
(get objects id)
modifiers
nil
nil)))
state
ids))))))
;; Set-rotation is custom because applies different modifiers to each
;; shape adjusting their position.
(defn set-rotation
([angle shapes]
(set-rotation angle shapes (-> shapes gsh/selection-rect gsh/center-selrect)))
([angle shapes center]
(ptk/reify ::set-rotation
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
id->obj #(get objects %)
get-children (fn [shape] (map id->obj (cp/get-children (:id shape) objects)))
shapes (->> shapes (into [] (remove #(get % :blocked false))))
shapes (->> shapes (mapcat get-children) (concat shapes))
update-shape
(fn [modifiers shape]
(let [rotate-modifiers (gsh/rotation-modifiers shape center angle)]
(assoc-in modifiers [(:id shape) :modifiers] rotate-modifiers)))]
(-> state
(update :workspace-modifiers
#(reduce update-shape % shapes))))))))
(defn increase-rotation [ids rotation]
(ptk/reify ::increase-rotation
(defn update-position
"Move shapes to a new position, from the sidebar options form."
[id position]
(us/verify ::us/uuid id)
(us/verify ::position position)
(ptk/reify ::update-position
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
rotate-shape (fn [shape]
(let [delta (- rotation (:rotation shape))]
(set-rotation delta [shape])))]
(rx/concat
(rx/from (->> ids (map #(get objects %)) (map rotate-shape)))
(rx/of (apply-modifiers ids)))))))
shape (get objects id)
(defn apply-modifiers
bbox (-> shape :points gsh/points->selrect)
cpos (gpt/point (:x bbox) (:y bbox))
pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox)))
displ (gmt/translate-matrix (gpt/subtract pos cpos))]
(rx/of (set-modifiers [id] {:displacement displ})
(apply-modifiers [id]))))))
(defn- calculate-frame-for-move
[ids]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::apply-modifiers
(ptk/reify ::calculate-frame-for-move
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
children-ids (->> ids (mapcat #(cp/get-children % objects)))
ids-with-children (d/concat [] children-ids ids)
object-modifiers (get state :workspace-modifiers)
ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)]
(rx/of (dwu/start-undo-transaction)
(dch/update-shapes
ids-with-children
(fn [shape]
(-> shape
(merge (get object-modifiers (:id shape)))
(gsh/transform-shape)))
{:reg-objects? true
:ignore-tree ignore-tree
;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes
:attrs [:selrect :points
:x :y
:width :height
:content
:transform
:transform-inverse
:rotation
:flip-x
:flip-y]})
(clear-local-transform)
(dwu/commit-undo-transaction))))))
;; --- Update Dimensions
;; Event mainly used for handling user modification of the size of the
;; object from workspace sidebar options inputs.
(defn update-dimensions
[ids attr value]
(us/verify (s/coll-of ::us/uuid) ids)
(us/verify #{:width :height} attr)
(us/verify ::us/number value)
(ptk/reify ::update-dimensions
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects])]
(reduce (fn [state id]
(let [shape (get objects id)
modifiers (gsh/resize-modifiers shape attr value)]
(update state :workspace-modifiers
#(set-modifiers-recursive %
objects
shape
modifiers
nil
nil))))
state
ids)))
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
(watch [it state _]
(let [position @ms/mouse-position
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))]
(rx/of (apply-modifiers ids))))))
frame-id (cp/frame-id-by-position objects position)
moving-shapes (->> ids
(cp/clean-loops objects)
(map #(get objects %))
(remove #(or (nil? %)
(= (:frame-id %) frame-id))))
rch [{:type :mov-objects
:page-id page-id
:parent-id frame-id
:shapes (mapv :id moving-shapes)}]
uch (->> moving-shapes
(reverse)
(mapv (fn [shape]
{:type :mov-objects
:page-id page-id
:parent-id (:parent-id shape)
:index (cp/get-index-in-parent objects (:id shape))
:shapes [(:id shape)]})))]
(when-not (empty? uch)
(rx/of dwu/pop-undo-into-transaction
(dch/commit-changes {:redo-changes rch
:undo-changes uch
:origin it})
(dwu/commit-undo-transaction)
(dwc/expand-collapse frame-id)))))))
(defn- get-displacement
"Retrieve the correct displacement delta point for the
provided direction speed and distances thresholds."
[direction]
(case direction
:up (gpt/point 0 (- 1))
:down (gpt/point 0 1)
:left (gpt/point (- 1) 0)
:right (gpt/point 1 0)))
;; -- Flip ----------------------------------------------------------
(defn flip-horizontal-selected []
(ptk/reify ::flip-horizontal-selected
@ -641,19 +705,6 @@
:displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))})
(apply-modifiers selected))))))
(defn set-local-displacement [point]
(ptk/reify ::start-local-displacement
ptk/UpdateEvent
(update [_ state]
(let [mtx (gmt/translate-matrix point)]
(-> state
(assoc-in [:workspace-local :modifiers] {:displacement mtx}))))))
(defn clear-local-transform []
(ptk/reify ::clear-local-transform
ptk/UpdateEvent
(update [_ state]
(-> state
(dissoc :workspace-modifiers)
(update :workspace-local dissoc :modifiers :current-move-selected)))))
;; -- Transform to path ---------------------------------------------

View file

@ -9,6 +9,7 @@
"A collection of derived refs."
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.store :as st]
@ -239,7 +240,7 @@
modifiers (:workspace-modifiers state)
objects (cond-> objects
with-modifiers?
(cp/merge-modifiers modifiers))
(gsh/merge-modifiers modifiers))
xform (comp (map #(get objects %))
(remove nil?))]
(into [] xform ids)))

View file

@ -7,7 +7,7 @@
(ns app.main.ui.workspace.viewport
(:require
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.geom.shapes :as gsh]
[app.main.refs :as refs]
[app.main.ui.context :as ctx]
[app.main.ui.measurements :as msr]
@ -64,7 +64,7 @@
object-modifiers (mf/deref refs/workspace-modifiers)
objects (mf/use-memo
(mf/deps objects object-modifiers)
#(cp/merge-modifiers objects object-modifiers))
#(gsh/merge-modifiers objects object-modifiers))
background (get options :background "#E8E9EA")
;; STATE