mirror of
https://github.com/penpot/penpot.git
synced 2025-04-13 15:31:26 -05:00
Merge pull request #187 from uxbox/245/shapes_transforms_improvements
245/shapes transforms improvements
This commit is contained in:
commit
a7cb90919f
29 changed files with 1330 additions and 1062 deletions
|
@ -108,10 +108,11 @@
|
|||
}
|
||||
|
||||
.workspace-viewport {
|
||||
height: calc(100% - 40px);
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
transition: none;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
|
||||
.viewport {
|
||||
&.drawing {
|
||||
|
|
File diff suppressed because it is too large
Load diff
273
frontend/src/uxbox/main/data/workspace/common.cljs
Normal file
273
frontend/src/uxbox/main/data/workspace/common.cljs
Normal file
|
@ -0,0 +1,273 @@
|
|||
(ns uxbox.main.data.workspace.common
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[cljs.spec.alpha :as s]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.worker :as uw]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.common.uuid :as uuid]))
|
||||
|
||||
;; --- Protocols
|
||||
(defprotocol IBatchedChange)
|
||||
(defprotocol IUpdateGroup
|
||||
(get-ids [this]))
|
||||
|
||||
(declare append-undo)
|
||||
(declare reset-undo)
|
||||
(declare commit-changes)
|
||||
(declare calculate-shape-to-frame-relationship-changes)
|
||||
|
||||
(defn- retrieve-toplevel-shapes
|
||||
[objects]
|
||||
(let [lookup #(get objects %)
|
||||
root (lookup uuid/zero)
|
||||
childs (:shapes root)]
|
||||
(loop [id (first childs)
|
||||
ids (rest childs)
|
||||
res []]
|
||||
(if (nil? id)
|
||||
res
|
||||
(let [obj (lookup id)
|
||||
typ (:type obj)]
|
||||
(recur (first ids)
|
||||
(rest ids)
|
||||
(if (= :frame typ)
|
||||
(into res (:shapes obj))
|
||||
(conj res id))))))))
|
||||
|
||||
(defn rehash-shape-frame-relationship
|
||||
[ids]
|
||||
(ptk/reify ::rehash-shape-frame-relationship
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
ids (retrieve-toplevel-shapes objects)
|
||||
[rch uch] (calculate-shape-to-frame-relationship-changes objects ids)]
|
||||
|
||||
(when-not (empty? rch)
|
||||
(rx/of (commit-changes rch uch {:commit-local? true})))))))
|
||||
|
||||
(defn- generate-operations
|
||||
[ma mb]
|
||||
(let [ma-keys (set (keys ma))
|
||||
mb-keys (set (keys mb))
|
||||
added (set/difference mb-keys ma-keys)
|
||||
removed (set/difference ma-keys mb-keys)
|
||||
both (set/intersection ma-keys mb-keys)]
|
||||
(d/concat
|
||||
(mapv #(array-map :type :set :attr % :val (get mb %)) added)
|
||||
(mapv #(array-map :type :set :attr % :val nil) removed)
|
||||
(loop [k (first both)
|
||||
r (rest both)
|
||||
rs []]
|
||||
(if k
|
||||
(let [vma (get ma k)
|
||||
vmb (get mb k)]
|
||||
(if (= vma vmb)
|
||||
(recur (first r) (rest r) rs)
|
||||
(recur (first r) (rest r) (conj rs {:type :set
|
||||
:attr k
|
||||
:val vmb}))))
|
||||
rs)))))
|
||||
|
||||
(defn- generate-changes
|
||||
[prev curr]
|
||||
(letfn [(impl-diff [res id]
|
||||
(let [prev-obj (get-in prev [:objects id])
|
||||
curr-obj (get-in curr [:objects id])
|
||||
ops (generate-operations (dissoc prev-obj :shapes :frame-id)
|
||||
(dissoc curr-obj :shapes :frame-id))]
|
||||
(if (empty? ops)
|
||||
res
|
||||
(conj res {:type :mod-obj
|
||||
:operations ops
|
||||
:id id}))))]
|
||||
(reduce impl-diff [] (set/union (set (keys (:objects prev)))
|
||||
(set (keys (:objects curr)))))))
|
||||
|
||||
(defn- update-selection-index
|
||||
[page-id]
|
||||
(ptk/reify ::update-selection-index
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(let [objects (get-in state [:workspace-pages page-id :data :objects])
|
||||
lookup #(get objects %)]
|
||||
(uw/ask! {:cmd :selection/update-index
|
||||
:page-id page-id
|
||||
:objects objects})))))
|
||||
|
||||
(defn commit-changes
|
||||
([changes undo-changes] (commit-changes changes undo-changes {}))
|
||||
([changes undo-changes {:keys [save-undo?
|
||||
commit-local?]
|
||||
:or {save-undo? true
|
||||
commit-local? false}
|
||||
:as opts}]
|
||||
(us/verify ::cp/changes changes)
|
||||
(us/verify ::cp/changes undo-changes)
|
||||
|
||||
(ptk/reify ::commit-changes
|
||||
cljs.core/IDeref
|
||||
(-deref [_] changes)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
state (update-in state [:workspace-pages page-id :data] cp/process-changes changes)]
|
||||
(cond-> state
|
||||
commit-local? (update-in [:workspace-data page-id] cp/process-changes changes))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page (:workspace-page state)
|
||||
uidx (get-in state [:workspace-local :undo-index] ::not-found)]
|
||||
(rx/concat
|
||||
(rx/of (update-selection-index (:id page)))
|
||||
|
||||
(when (and save-undo? (not= uidx ::not-found))
|
||||
(rx/of (reset-undo uidx)))
|
||||
|
||||
(when save-undo?
|
||||
(let [entry {:undo-changes undo-changes
|
||||
:redo-changes changes}]
|
||||
(rx/of (append-undo entry))))))))))
|
||||
|
||||
(defn diff-and-commit-changes
|
||||
[page-id]
|
||||
(ptk/reify ::diff-and-commit-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
curr (get-in state [:workspace-data page-id])
|
||||
prev (get-in state [:workspace-pages page-id :data])
|
||||
|
||||
changes (generate-changes prev curr)
|
||||
undo-changes (generate-changes curr prev)]
|
||||
(when-not (empty? changes)
|
||||
(rx/of (commit-changes changes undo-changes)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Undo/Redo
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def MAX-UNDO-SIZE 50)
|
||||
|
||||
(defn- conj-undo-entry
|
||||
[undo data]
|
||||
(let [undo (conj undo data)]
|
||||
(if (> (count undo) MAX-UNDO-SIZE)
|
||||
(into [] (take MAX-UNDO-SIZE undo))
|
||||
undo)))
|
||||
|
||||
(defn- materialize-undo
|
||||
[changes index]
|
||||
(ptk/reify ::materialize-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)]
|
||||
(-> state
|
||||
(update-in [:workspace-data page-id] cp/process-changes changes)
|
||||
(assoc-in [:workspace-local :undo-index] index))))))
|
||||
|
||||
(defn- reset-undo
|
||||
[index]
|
||||
(ptk/reify ::reset-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-local dissoc :undo-index)
|
||||
(update-in [:workspace-local :undo]
|
||||
(fn [queue]
|
||||
(into [] (take (inc index) queue))))))))
|
||||
|
||||
(s/def ::undo-changes ::cp/changes)
|
||||
(s/def ::redo-changes ::cp/changes)
|
||||
(s/def ::undo-entry
|
||||
(s/keys :req-un [::undo-changes ::redo-changes]))
|
||||
|
||||
(defn- append-undo
|
||||
[entry]
|
||||
(us/verify ::undo-entry entry)
|
||||
(ptk/reify ::append-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-local :undo] (fnil conj-undo-entry []) entry))))
|
||||
|
||||
(def undo
|
||||
(ptk/reify ::undo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [local (:workspace-local state)
|
||||
undo (:undo local [])
|
||||
index (or (:undo-index local)
|
||||
(dec (count undo)))]
|
||||
(when-not (or (empty? undo) (= index -1))
|
||||
(let [changes (get-in undo [index :undo-changes])]
|
||||
(rx/of (materialize-undo changes (dec index))
|
||||
(commit-changes changes [] {:save-undo? false}))))))))
|
||||
|
||||
(def redo
|
||||
(ptk/reify ::redo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [local (:workspace-local state)
|
||||
undo (:undo local [])
|
||||
index (or (:undo-index local)
|
||||
(dec (count undo)))]
|
||||
(when-not (or (empty? undo) (= index (dec (count undo))))
|
||||
(let [changes (get-in undo [(inc index) :redo-changes])]
|
||||
(rx/of (materialize-undo changes (inc index))
|
||||
(commit-changes changes [] {:save-undo? false}))))))))
|
||||
|
||||
(def reinitialize-undo
|
||||
(ptk/reify ::reset-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :undo-index :undo))))
|
||||
|
||||
|
||||
(defn- calculate-frame-overlap
|
||||
[objects shape]
|
||||
(let [rshp (geom/shape->rect-shape shape)
|
||||
|
||||
xfmt (comp
|
||||
(filter #(= :frame (:type %)))
|
||||
(filter #(not= (:id shape) (:id %)))
|
||||
(filter #(not= uuid/zero (:id %)))
|
||||
(filter #(geom/overlaps? % rshp)))
|
||||
|
||||
frame (->> (vals objects)
|
||||
(sequence xfmt)
|
||||
(first))]
|
||||
|
||||
(or (:id frame) uuid/zero)))
|
||||
|
||||
(defn- calculate-shape-to-frame-relationship-changes
|
||||
[objects ids]
|
||||
(loop [id (first ids)
|
||||
ids (rest ids)
|
||||
rch []
|
||||
uch []]
|
||||
(if (nil? id)
|
||||
[rch uch]
|
||||
(let [obj (get objects id)
|
||||
fid (calculate-frame-overlap objects obj)]
|
||||
(if (not= fid (:frame-id obj))
|
||||
(recur (first ids)
|
||||
(rest ids)
|
||||
(conj rch {:type :mov-objects
|
||||
:parent-id fid
|
||||
:shapes [id]})
|
||||
(conj uch {:type :mov-objects
|
||||
:parent-id (:frame-id obj)
|
||||
:shapes [id]}))
|
||||
(recur (first ids)
|
||||
(rest ids)
|
||||
rch
|
||||
uch))))))
|
||||
|
321
frontend/src/uxbox/main/data/workspace/transforms.cljs
Normal file
321
frontend/src/uxbox/main/data/workspace/transforms.cljs
Normal file
|
@ -0,0 +1,321 @@
|
|||
(ns uxbox.main.data.workspace.transforms
|
||||
"Events related with shapes transformations"
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.streams :as ms]
|
||||
[uxbox.util.geom.shapes :as gsh]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.main.data.helpers :as helpers]
|
||||
[uxbox.main.data.workspace.common :refer [IBatchedChange IUpdateGroup] :as common]))
|
||||
|
||||
;; -- Declarations
|
||||
|
||||
(declare set-modifiers)
|
||||
(declare set-rotation)
|
||||
(declare apply-modifiers)
|
||||
|
||||
;; -- Helpers
|
||||
|
||||
(defn- apply-zoom
|
||||
[point]
|
||||
(gpt/divide point (gpt/point @refs/selected-zoom)))
|
||||
|
||||
;; For each of the 8 handlers gives the modifier 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
|
||||
{:right [ 1 0]
|
||||
:bottom [ 0 1]
|
||||
:left [-1 0]
|
||||
:top [ 0 -1]
|
||||
:top-right [ 1 -1]
|
||||
:top-left [-1 -1]
|
||||
: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]
|
||||
(let [mx (+ sx (/ width 2))
|
||||
my (+ sy (/ height 2))
|
||||
ex (+ sx width)
|
||||
ey (+ sy height)
|
||||
|
||||
[x y] (case handler
|
||||
:right [sx my]
|
||||
:bottom [mx sy]
|
||||
:left [ex my]
|
||||
:top [mx ey]
|
||||
:top-right [sx ey]
|
||||
:top-left [ex ey]
|
||||
:bottom-right [sx sy]
|
||||
:bottom-left [ex sy])]
|
||||
(gpt/point x y)))
|
||||
|
||||
;; -- RESIZE
|
||||
(defn start-resize
|
||||
[handler ids shape objects]
|
||||
(letfn [(resize [shape initial [point lock?]]
|
||||
(let [frame (get objects (:frame-id shape))
|
||||
{:keys [width height rotation]} shape
|
||||
|
||||
center (gpt/center shape)
|
||||
shapev (-> (gpt/point width height))
|
||||
|
||||
;; Vector modifiers depending on the handler
|
||||
handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y))
|
||||
|
||||
;; Difference between the origin point in the coordinate system of the rotation
|
||||
deltav (-> (gpt/subtract point initial)
|
||||
(gpt/transform (gmt/rotate-matrix (- rotation)))
|
||||
(gpt/multiply handler-modif))
|
||||
|
||||
;; Resize vector
|
||||
scalev (gpt/divide (gpt/add shapev deltav) shapev)
|
||||
|
||||
scalev (if lock? (let [v (max (:x scalev) (:y scalev))] (gpt/point v v)) scalev)
|
||||
|
||||
shape-transform (:transform shape (gmt/matrix))
|
||||
shape-transform-inverse (:transform-inverse shape (gmt/matrix))
|
||||
|
||||
;; Resize origin point given the selected handler
|
||||
origin (-> (handler-resize-origin shape handler)
|
||||
(gsh/transform-shape-point shape shape-transform))]
|
||||
|
||||
(rx/of (set-modifiers ids
|
||||
{:resize-vector scalev
|
||||
:resize-origin origin
|
||||
:resize-transform shape-transform
|
||||
:resize-transform-inverse shape-transform-inverse}
|
||||
false))))
|
||||
|
||||
;; Unifies the instantaneous proportion lock modifier
|
||||
;; activated by Ctrl key and the shapes own proportion
|
||||
;; lock flag that can be activated on element options.
|
||||
(normalize-proportion-lock [[point ctrl?]]
|
||||
(let [proportion-lock? (:proportion-lock shape)]
|
||||
[point (or proportion-lock? ctrl?)]))
|
||||
|
||||
;; Applies alginment to point if it is currently
|
||||
;; activated on the current workspace
|
||||
;; (apply-grid-alignment [point]
|
||||
;; (if @refs/selected-alignment
|
||||
;; (uwrk/align-point point)
|
||||
;; (rx/of point)))
|
||||
]
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [initial (apply-zoom @ms/mouse-position)
|
||||
shape (gsh/shape->rect-shape shape)
|
||||
stoper (rx/filter ms/mouse-up? stream)]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/map apply-zoom)
|
||||
;; (rx/mapcat apply-grid-alignment)
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/map normalize-proportion-lock)
|
||||
(rx/mapcat (partial resize shape initial))
|
||||
(rx/take-until stoper))
|
||||
#_(rx/empty)
|
||||
(rx/of (apply-modifiers ids))))))))
|
||||
|
||||
|
||||
;; -- ROTATE
|
||||
(defn start-rotate
|
||||
[shapes]
|
||||
(ptk/reify ::start-rotate
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter ms/mouse-up? stream)
|
||||
group (gsh/selection-rect shapes)
|
||||
group-center (gpt/center group)
|
||||
initial-angle (gpt/angle (apply-zoom @ms/mouse-position) group-center)
|
||||
calculate-angle (fn [pos ctrl?]
|
||||
(let [angle (- (gpt/angle pos group-center) initial-angle)
|
||||
angle (if (neg? angle) (+ 360 angle) angle)
|
||||
modval (mod angle 90)
|
||||
angle (if ctrl?
|
||||
(if (< 50 modval)
|
||||
(+ angle (- 90 modval))
|
||||
(- angle modval))
|
||||
angle)
|
||||
angle (if (= angle 360)
|
||||
0
|
||||
angle)]
|
||||
angle))]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/map apply-zoom)
|
||||
(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))))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (apply-modifiers (map :id shapes))))))))
|
||||
|
||||
;; -- MOVE
|
||||
|
||||
(defn start-move-selected []
|
||||
(ptk/reify ::start-move-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
stoper (rx/filter ms/mouse-up? stream)
|
||||
zero-point? #(= % (gpt/point 0 0))
|
||||
initial (apply-zoom @ms/mouse-position)
|
||||
position @ms/mouse-position]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/map apply-zoom)
|
||||
(rx/filter (complement zero-point?))
|
||||
(rx/map #(gpt/subtract % initial))
|
||||
(rx/map #(set-modifiers selected {:displacement (gmt/translate-matrix %)}))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (apply-modifiers selected)))))))
|
||||
|
||||
(defn- get-displacement-with-grid
|
||||
"Retrieve the correct displacement delta point for the
|
||||
provided direction speed and distances thresholds."
|
||||
[shape direction options]
|
||||
(let [grid-x (:grid-x options 10)
|
||||
grid-y (:grid-y options 10)
|
||||
x-mod (mod (:x shape) grid-x)
|
||||
y-mod (mod (:y shape) grid-y)]
|
||||
(case direction
|
||||
:up (gpt/point 0 (- (if (zero? y-mod) grid-y y-mod)))
|
||||
:down (gpt/point 0 (- grid-y y-mod))
|
||||
:left (gpt/point (- (if (zero? x-mod) grid-x x-mod)) 0)
|
||||
:right (gpt/point (- grid-x x-mod) 0))))
|
||||
|
||||
(defn- get-displacement
|
||||
"Retrieve the correct displacement delta point for the
|
||||
provided direction speed and distances thresholds."
|
||||
[shape direction]
|
||||
(case direction
|
||||
:up (gpt/point 0 (- 1))
|
||||
:down (gpt/point 0 1)
|
||||
:left (gpt/point (- 1) 0)
|
||||
:right (gpt/point 1 0)))
|
||||
|
||||
(defn move-selected
|
||||
[direction align?]
|
||||
(us/verify ::direction direction)
|
||||
(us/verify boolean? align?)
|
||||
|
||||
(ptk/reify ::move-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (:current-page-id state)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
options (get-in state [:workspace-data pid :options])
|
||||
shapes (map #(get-in state [:workspace-data pid :objects %]) selected)
|
||||
shape (gsh/shapes->rect-shape shapes)
|
||||
displacement (if align?
|
||||
(get-displacement-with-grid shape direction options)
|
||||
(get-displacement shape direction))]
|
||||
(rx/of (set-modifiers selected displacement)
|
||||
(apply-modifiers selected))))))
|
||||
|
||||
|
||||
;; -- Apply modifiers
|
||||
|
||||
(defn set-modifiers
|
||||
([ids modifiers] (set-modifiers ids modifiers true))
|
||||
([ids modifiers recurse-frames?]
|
||||
(us/verify (s/coll-of uuid?) ids)
|
||||
(ptk/reify ::set-modifiers
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
not-frame-id? (fn [shape-id]
|
||||
(let [shape (get objects shape-id)]
|
||||
(or recurse-frames? (not (= :frame (:type shape))))))
|
||||
|
||||
;; ID's + Children but remove frame children if the flag is set to false
|
||||
ids-with-children (concat ids (mapcat #(helpers/get-children % objects) (filter not-frame-id? ids)))
|
||||
|
||||
;; For each shape updates the modifiers given as arguments
|
||||
update-shape (fn [state shape-id]
|
||||
(update-in
|
||||
state
|
||||
[:workspace-data page-id :objects shape-id :modifiers]
|
||||
#(merge % modifiers)))]
|
||||
(reduce update-shape state ids-with-children))))))
|
||||
|
||||
(defn rotation-modifiers [center shape angle]
|
||||
(let [displacement (let [shape-center (gsh/center shape)]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/rotate angle center)
|
||||
(gmt/rotate (- angle) shape-center)))]
|
||||
{:rotation angle
|
||||
:displacement displacement}))
|
||||
|
||||
|
||||
;; Set-rotation is custom because applies different modifiers to each shape adjusting their position
|
||||
(defn set-rotation
|
||||
[delta-rotation shapes]
|
||||
(ptk/reify ::set-rotation
|
||||
IUpdateGroup
|
||||
(get-ids [_] (map :id shapes))
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)]
|
||||
(letfn [(rotate-shape [state angle shape center]
|
||||
(let [objects (get-in state [:workspace-data page-id :objects])
|
||||
path [:workspace-data page-id :objects (:id shape) :modifiers]
|
||||
modifiers (rotation-modifiers center shape angle)]
|
||||
(-> state
|
||||
(update-in path merge modifiers))))
|
||||
|
||||
(rotate-around-center [state angle center shapes]
|
||||
(reduce #(rotate-shape %1 angle %2 center) state shapes))]
|
||||
|
||||
(let [center (-> shapes gsh/selection-rect gpt/center)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
id->obj #(get objects %)
|
||||
get-children (fn [shape] (map id->obj (helpers/get-children (:id shape) objects)))
|
||||
shapes (concat shapes (mapcat get-children shapes))]
|
||||
(rotate-around-center state delta-rotation center shapes)))))))
|
||||
|
||||
(defn apply-modifiers
|
||||
[ids]
|
||||
(us/verify (s/coll-of uuid?) ids)
|
||||
(ptk/reify ::apply-modifiers
|
||||
IUpdateGroup
|
||||
(get-ids [_] ids)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
|
||||
;; ID's + Children
|
||||
ids-with-children (concat ids (mapcat #(helpers/get-children % objects) ids))
|
||||
|
||||
;; For each shape applies the modifiers by transforming the objects
|
||||
update-shape
|
||||
(fn [state shape-id]
|
||||
(update-in state [:workspace-data page-id :objects shape-id] gsh/transform-shape))]
|
||||
|
||||
(reduce update-shape state ids-with-children)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)]
|
||||
(rx/of (common/diff-and-commit-changes page-id)
|
||||
(common/rehash-shape-frame-relationship ids))))))
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.main.ui.shapes.frame :as frame]
|
||||
|
|
46
frontend/src/uxbox/main/ui/shapes/bounding_box.cljs
Normal file
46
frontend/src/uxbox/main/ui/shapes/bounding_box.cljs
Normal file
|
@ -0,0 +1,46 @@
|
|||
;; 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) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.shapes.bounding-box
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.util.debug :as debug]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.debug :refer [debug?]]))
|
||||
|
||||
(defn fix [num]
|
||||
(when num (.toFixed num 2)))
|
||||
|
||||
(mf/defc bounding-box
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(when (debug? :bounding-boxes)
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
selrect (geom/transform-selrect frame shape)
|
||||
shape-path (-> shape
|
||||
(geom/transform-apply-modifiers)
|
||||
(geom/translate-to-frame frame))
|
||||
shape-center (geom/center shape-path)]
|
||||
[:g
|
||||
[:text {:x (:x selrect)
|
||||
:y (- (:y selrect) 5)
|
||||
:font-size 10
|
||||
:fill "red"
|
||||
:stroke "white"
|
||||
:stroke-width 0.1}
|
||||
(str/format "%s - (%s, %s)" (str/slice (str (:id shape)) 0 8) (fix (:x shape)) (fix (:y shape)))]
|
||||
|
||||
[:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}]
|
||||
|
||||
[:rect {:x (:x selrect)
|
||||
:y (:y selrect)
|
||||
:width (:width selrect)
|
||||
:height (:height selrect)
|
||||
:style {:stroke "red" :fill "transparent" :stroke-width "1px" :stroke-opacity 0.5}}]])))
|
|
@ -7,13 +7,14 @@
|
|||
(ns uxbox.main.ui.shapes.circle
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.util.interop :as itr]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]))
|
||||
|
||||
;; --- Circle Wrapper
|
||||
|
||||
|
@ -28,19 +29,22 @@
|
|||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-context-menu on-context-menu}
|
||||
[:& circle-shape {:shape (geom/transform-shape frame shape)}]]))
|
||||
[:& circle-shape {:shape (geom/transform-shape frame shape)}]
|
||||
[:& bounding-box {:shape shape :frame frame}]]))
|
||||
|
||||
;; --- Circle Shape
|
||||
|
||||
(mf/defc circle-shape
|
||||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [id cx cy rx ry rotation]} shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [id x y width height]} shape
|
||||
transform (geom/transform-matrix shape)
|
||||
|
||||
center (gpt/point cx cy)
|
||||
rotation (or rotation 0)
|
||||
transform (when (pos? rotation)
|
||||
(str (-> (gmt/matrix)
|
||||
(gmt/rotate rotation center))))
|
||||
cx (+ x (/ width 2))
|
||||
cy (+ y (/ height 2))
|
||||
rx (/ width 2)
|
||||
ry (/ height 2)
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(itr/obj-assign!
|
||||
|
|
|
@ -20,44 +20,12 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.streams :as uws]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Shape Movement (by mouse)
|
||||
|
||||
(def start-move-selected
|
||||
(ptk/reify ::start-move-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
stoper (rx/filter uws/mouse-up? stream)
|
||||
zero-point? #(= % (gpt/point 0 0))
|
||||
position @uws/mouse-position]
|
||||
(rx/concat
|
||||
(->> (uws/mouse-position-deltas position)
|
||||
(rx/filter (complement zero-point?))
|
||||
(rx/map #(dw/apply-displacement-in-bulk selected %))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-displacement-in-bulk selected)))))))
|
||||
|
||||
(def start-move-frame
|
||||
(ptk/reify ::start-move-frame
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
stoper (rx/filter uws/mouse-up? stream)
|
||||
zero-point? #(= % (gpt/point 0 0))
|
||||
frame-id (first selected)
|
||||
position @uws/mouse-position]
|
||||
(rx/concat
|
||||
(->> (uws/mouse-position-deltas position)
|
||||
(rx/filter (complement zero-point?))
|
||||
(rx/map #(dw/apply-frame-displacement frame-id %))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-frame-displacement frame-id)))))))
|
||||
|
||||
(defn on-mouse-down
|
||||
[event {:keys [id type] :as shape}]
|
||||
(let [selected @refs/selected-shapes
|
||||
|
@ -75,14 +43,14 @@
|
|||
(= type :frame)
|
||||
(when selected?
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! start-move-frame))
|
||||
(st/emit! (dw/start-move-selected)))
|
||||
|
||||
(and (not selected?) (empty? selected))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! dw/deselect-all
|
||||
(dw/select-shape id)
|
||||
start-move-selected))
|
||||
(dw/start-move-selected)))
|
||||
|
||||
(and (not selected?) (not (empty? selected)))
|
||||
(do
|
||||
|
@ -91,11 +59,11 @@
|
|||
(st/emit! (dw/select-shape id))
|
||||
(st/emit! dw/deselect-all
|
||||
(dw/select-shape id)
|
||||
start-move-selected)))
|
||||
(dw/start-move-selected))))
|
||||
:else
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! start-move-selected))))))
|
||||
(st/emit! (dw/start-move-selected)))))))
|
||||
|
||||
|
||||
(defn on-context-menu
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
[uxbox.common.data :as d]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
|
|
|
@ -11,17 +11,17 @@
|
|||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.interop :as itr]
|
||||
[uxbox.util.debug :refer [debug?]]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.streams :as ms]))
|
||||
|
||||
(defonce ^:dynamic *debug* (atom false))
|
||||
[uxbox.main.streams :as ms]
|
||||
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]))
|
||||
|
||||
(defn- equals?
|
||||
[np op]
|
||||
|
@ -75,7 +75,9 @@
|
|||
{:frame frame
|
||||
:shape (geom/transform-shape frame shape)
|
||||
:children children
|
||||
:is-child-selected? is-child-selected?}]]))))
|
||||
:is-child-selected? is-child-selected?}]
|
||||
(when (not is-child-selected?)
|
||||
[:& bounding-box {:shape shape :frame frame}])]))))
|
||||
|
||||
(defn group-shape
|
||||
[shape-wrapper]
|
||||
|
@ -86,28 +88,18 @@
|
|||
shape (unchecked-get props "shape")
|
||||
children (unchecked-get props "children")
|
||||
is-child-selected? (unchecked-get props "is-child-selected?")
|
||||
{:keys [id x y width height rotation
|
||||
displacement-modifier
|
||||
resize-modifier]} shape
|
||||
|
||||
transform (when (and rotation (pos? rotation))
|
||||
(str/format "rotate(%s %s %s)"
|
||||
rotation
|
||||
(+ x (/ width 2))
|
||||
(+ y (/ height 2))))]
|
||||
[:g {:transform transform}
|
||||
{:keys [id x y width height]} shape
|
||||
transform (geom/transform-matrix shape)]
|
||||
[:g
|
||||
(for [item children]
|
||||
[:& shape-wrapper
|
||||
{:frame frame
|
||||
:shape (cond-> item
|
||||
displacement-modifier (assoc :displacement-modifier displacement-modifier)
|
||||
resize-modifier (assoc :resize-modifier resize-modifier))
|
||||
:key (:id item)}])
|
||||
{:frame frame :shape item :key (:id item)}])
|
||||
|
||||
(when (not is-child-selected?)
|
||||
[:rect {:x x
|
||||
[:rect {:transform transform
|
||||
:x x
|
||||
:y y
|
||||
:fill (if (deref *debug*) "red" "transparent")
|
||||
:fill (if (debug? :group) "red" "transparent")
|
||||
:opacity 0.5
|
||||
:id (str "group-" id)
|
||||
:width width
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
|
|
|
@ -9,13 +9,14 @@
|
|||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.data.images :as udi]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.util.interop :as itr]
|
||||
[uxbox.util.geom.matrix :as gmt]))
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]))
|
||||
|
||||
;; --- Image Wrapper
|
||||
|
||||
|
@ -36,19 +37,15 @@
|
|||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-context-menu on-context-menu}
|
||||
[:& image-shape {:shape (geom/transform-shape frame shape)}]]))
|
||||
[:& image-shape {:shape (geom/transform-shape frame shape)}]
|
||||
[:& bounding-box {:shape shape :frame frame}]]))
|
||||
|
||||
;; --- Image Shape
|
||||
|
||||
(mf/defc image-shape
|
||||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [id x y width height rotation metadata]} shape
|
||||
|
||||
transform (when (and rotation (pos? rotation))
|
||||
(str/format "rotate(%s %s %s)"
|
||||
rotation
|
||||
(+ x (/ width 2))
|
||||
(+ y (/ height 2))))
|
||||
transform (geom/transform-matrix shape)
|
||||
uri (if (or (> (:thumb-width metadata) width)
|
||||
(> (:thumb-height metadata) height))
|
||||
(:thumb-uri metadata)
|
||||
|
|
|
@ -9,13 +9,14 @@
|
|||
[cuerdas.core :as str :include-macros true]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.util.interop :as itr]
|
||||
[uxbox.util.geom.matrix :as gmt]))
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]))
|
||||
|
||||
;; --- Path Wrapper
|
||||
|
||||
|
@ -25,17 +26,23 @@
|
|||
[{:keys [shape frame] :as props}]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected (:id shape))
|
||||
on-mouse-down #(common/on-mouse-down % shape)
|
||||
on-context-menu #(common/on-context-menu % shape)
|
||||
on-double-click
|
||||
(fn [event]
|
||||
(when selected?
|
||||
(st/emit! (dw/start-edition-mode (:id shape)))))]
|
||||
on-mouse-down (mf/use-callback
|
||||
(mf/deps shape)
|
||||
#(common/on-mouse-down % shape))
|
||||
on-context-menu (mf/use-callback
|
||||
(mf/deps shape)
|
||||
#(common/on-context-menu % shape))
|
||||
on-double-click (mf/use-callback
|
||||
(mf/deps shape)
|
||||
(fn [event]
|
||||
(when selected?
|
||||
(st/emit! (dw/start-edition-mode (:id shape))))))]
|
||||
|
||||
[:g.shape {:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-context-menu on-context-menu}
|
||||
[:& path-shape {:shape (geom/transform-shape frame shape)
|
||||
:background? true}]]))
|
||||
[:& path-shape {:shape (geom/transform-shape frame shape) :background? true}]
|
||||
[:& bounding-box {:shape shape :frame frame}]]))
|
||||
|
||||
;; --- Path Shape
|
||||
|
||||
|
@ -62,14 +69,8 @@
|
|||
|
||||
(mf/defc path-shape
|
||||
[{:keys [shape background?] :as props}]
|
||||
(let [{:keys [id x y width height rotation]} (geom/shape->rect-shape shape)
|
||||
|
||||
transform (when (and rotation (pos? rotation))
|
||||
(str/format "rotate(%s %s %s)"
|
||||
rotation
|
||||
(+ x (/ width 2))
|
||||
(+ y (/ height 2))))
|
||||
|
||||
(let [{:keys [id x y width height]} (geom/shape->rect-shape shape)
|
||||
transform (geom/transform-matrix shape)
|
||||
pdata (render-path shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(itr/obj-assign!
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
[cuerdas.core :as str]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.util.interop :as itr]))
|
||||
[uxbox.util.interop :as itr]
|
||||
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]))
|
||||
|
||||
;; --- Rect Wrapper
|
||||
|
||||
|
@ -33,7 +34,8 @@
|
|||
#(common/on-context-menu % shape))]
|
||||
[:g.shape {:on-mouse-down on-mouse-down
|
||||
:on-context-menu on-context-menu}
|
||||
[:& rect-shape {:shape (geom/transform-shape frame shape) }]]))
|
||||
[:& rect-shape {:shape (geom/transform-shape frame shape) }]
|
||||
[:& bounding-box {:shape shape :frame frame}]]))
|
||||
|
||||
;; --- Rect Shape
|
||||
|
||||
|
@ -41,11 +43,8 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [id x y width height rotation]} shape
|
||||
center (gpt/center shape)
|
||||
transform (when (pos? rotation)
|
||||
(str (-> (gmt/matrix)
|
||||
(gmt/rotate rotation center))))
|
||||
{:keys [id x y width height]} shape
|
||||
transform (geom/transform-matrix shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(itr/obj-assign!
|
||||
#js {:x x
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.streams :as ms]
|
||||
|
@ -112,19 +112,24 @@
|
|||
(let [shape (get-in state [:workspace-local :drawing])
|
||||
shape (geom/setup shape {:x (:x point)
|
||||
:y (:y point)
|
||||
:width 2
|
||||
:height 2})]
|
||||
:width 10
|
||||
:height 10})]
|
||||
(assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true))))
|
||||
|
||||
(resize-shape [shape point lock?]
|
||||
(resize-shape [{:keys [x y] :as shape} initial point lock?]
|
||||
(let [shape' (geom/shape->rect-shape shape)
|
||||
result (geom/resize-shape :bottom-right shape' point lock?)
|
||||
scale (geom/calculate-scale-ratio shape' result)
|
||||
mtx (geom/generate-resize-matrix :bottom-right shape' scale)]
|
||||
(assoc shape :resize-modifier mtx)))
|
||||
shapev (gpt/point (:width shape') (:height shape'))
|
||||
deltav (gpt/subtract point initial)
|
||||
scalev (gpt/divide (gpt/add shapev deltav) shapev)
|
||||
scalev (if lock? (let [v (max (:x scalev) (:y scalev))] (gpt/point v v)) scalev)]
|
||||
|
||||
(update-drawing [state point lock?]
|
||||
(update-in state [:workspace-local :drawing] resize-shape point lock?))]
|
||||
(-> shape
|
||||
(assoc-in [:modifiers :resize-vector] scalev)
|
||||
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
|
||||
(assoc-in [:modifiers :resize-rotation] 0))))
|
||||
|
||||
(update-drawing [state initial point lock?]
|
||||
(update-in state [:workspace-local :drawing] resize-shape initial point lock?))]
|
||||
|
||||
(ptk/reify ::handle-drawing-generic
|
||||
ptk/WatchEvent
|
||||
|
@ -132,7 +137,7 @@
|
|||
(let [{:keys [zoom flags]} (:workspace-local state)
|
||||
stoper? #(or (ms/mouse-up? %) (= % :interrupt))
|
||||
stoper (rx/filter stoper? stream)
|
||||
|
||||
initial @ms/mouse-position
|
||||
mouse (->> ms/mouse-position
|
||||
(rx/map #(gpt/divide % (gpt/point zoom))))]
|
||||
(rx/concat
|
||||
|
@ -141,7 +146,7 @@
|
|||
(rx/map (fn [pt] #(initialize-drawing % pt))))
|
||||
(->> mouse
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/map (fn [[pt ctrl?]] #(update-drawing % pt ctrl?)))
|
||||
(rx/map (fn [[pt ctrl?]] #(update-drawing % initial pt ctrl?)))
|
||||
(rx/take-until stoper))
|
||||
(rx/of handle-finish-drawing)))))))
|
||||
|
||||
|
@ -268,13 +273,12 @@
|
|||
(rx/concat
|
||||
(rx/of dw/clear-drawing)
|
||||
(when (::initialized? shape)
|
||||
(let [modifier (:resize-modifier shape)
|
||||
shape (if (gmt/matrix? modifier)
|
||||
(geom/transform shape modifier)
|
||||
shape)
|
||||
shape (dissoc shape ::initialized? :resize-modifier)]
|
||||
(let [shape (-> shape
|
||||
(geom/transform-shape)
|
||||
(dissoc shape ::initialized?))]
|
||||
;; Add & select the created shape to the workspace
|
||||
(rx/of dw/deselect-all (dw/add-shape shape)))))))))
|
||||
(rx/of dw/deselect-all
|
||||
(dw/add-shape shape)))))))))
|
||||
|
||||
(def close-drawing-path
|
||||
(ptk/reify ::close-drawing-path
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
;; :x 0 :y 0
|
||||
:height c/viewport-height}
|
||||
|
||||
[:g {:transform (str "translate(0, " translate-y ")")}
|
||||
[:g {:transform (str "translate(0, " (+ translate-y 20) ")")}
|
||||
[:& vertical-rule-ticks {:zoom zoom}]]
|
||||
[:rect {:x 0
|
||||
:y 0
|
||||
|
|
|
@ -14,92 +14,14 @@
|
|||
[potok.core :as ptk]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.streams :as ms]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.matrix :as gmt]))
|
||||
|
||||
(defn- apply-zoom
|
||||
[point]
|
||||
(gpt/divide point (gpt/point @refs/selected-zoom)))
|
||||
|
||||
;; --- Resize & Rotate
|
||||
|
||||
(defn- start-resize
|
||||
[vid ids shape]
|
||||
(letfn [(resize [shape [point lock?]]
|
||||
(let [result (geom/resize-shape vid shape point lock?)
|
||||
scale (geom/calculate-scale-ratio shape result)
|
||||
mtx (geom/generate-resize-matrix vid shape scale)]
|
||||
(rx/of (dw/assoc-resize-modifier-in-bulk ids mtx))))
|
||||
|
||||
;; Unifies the instantaneous proportion lock modifier
|
||||
;; activated by Ctrl key and the shapes own proportion
|
||||
;; lock flag that can be activated on element options.
|
||||
(normalize-proportion-lock [[point ctrl?]]
|
||||
(let [proportion-lock? (:proportion-lock shape)]
|
||||
[point (or proportion-lock? ctrl?)]))
|
||||
|
||||
;; Applies alginment to point if it is currently
|
||||
;; activated on the current workspace
|
||||
;; (apply-grid-alignment [point]
|
||||
;; (if @refs/selected-alignment
|
||||
;; (uwrk/align-point point)
|
||||
;; (rx/of point)))
|
||||
]
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [shape (->> (geom/shape->rect-shape shape)
|
||||
(geom/size))
|
||||
stoper (rx/filter ms/mouse-up? stream)]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/map apply-zoom)
|
||||
;; (rx/mapcat apply-grid-alignment)
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/map normalize-proportion-lock)
|
||||
(rx/mapcat (partial resize shape))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-resize-modifier-in-bulk ids))))))))
|
||||
|
||||
(defn start-rotate
|
||||
[shapes]
|
||||
(ptk/reify ::start-rotate
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter ms/mouse-up? stream)
|
||||
group (geom/selection-rect shapes)
|
||||
group-center (gpt/center group)
|
||||
initial-angle (gpt/angle @ms/mouse-position group-center)
|
||||
calculate-angle (fn [pos ctrl?]
|
||||
(let [angle (- (gpt/angle pos group-center) initial-angle)
|
||||
angle (if (neg? angle) (+ 360 angle) angle)
|
||||
modval (mod angle 90)
|
||||
angle (if ctrl?
|
||||
(if (< 50 modval)
|
||||
(+ angle (- 90 modval))
|
||||
(- angle modval))
|
||||
angle)
|
||||
angle (if (= angle 360)
|
||||
0
|
||||
angle)]
|
||||
angle))]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/map apply-zoom)
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/map (fn [[pos ctrl?]]
|
||||
(let [delta-angle (calculate-angle pos ctrl?)]
|
||||
(dw/apply-rotation delta-angle shapes))))
|
||||
|
||||
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-rotation shapes))
|
||||
)))))
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.debug :refer [debug?]]))
|
||||
|
||||
;; --- Controls (Component)
|
||||
|
||||
|
@ -149,41 +71,44 @@
|
|||
[{:keys [shape zoom on-resize on-rotate] :as props}]
|
||||
(let [{:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape)
|
||||
radius (if (> (max width height) handler-size-threshold) 6.0 4.0)
|
||||
transform (geom/rotation-matrix shape)
|
||||
transform (geom/transform-matrix shape)
|
||||
|
||||
resize-handlers {:top [(+ x (/ width 2 )) (- y 2)]
|
||||
:right [(+ x width 1) (+ y (/ height 2))]
|
||||
:bottom [(+ x (/ width 2)) (+ y height 2)]
|
||||
:left [(- x 3) (+ y (/ height 2))]
|
||||
resize-handlers {:top [(+ x (/ width 2 )) y]
|
||||
:right [(+ x width) (+ y (/ height 2))]
|
||||
:bottom [(+ x (/ width 2)) (+ y height)]
|
||||
:left [x (+ y (/ height 2))]
|
||||
:top-left [x y]
|
||||
:top-right [(+ x width) y]
|
||||
:bottom-left [x (+ y height)]
|
||||
:bottom-right [(+ x width) (+ y height)]}]
|
||||
|
||||
[:g.controls {:transform transform}
|
||||
[:rect.main {:x x :y y
|
||||
|
||||
[:g.controls
|
||||
[:rect.main {:transform transform
|
||||
:x x :y y
|
||||
:width width
|
||||
:height height
|
||||
:stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom))
|
||||
:vector-effect "non-scaling-stroke"
|
||||
:style {:stroke "#1FDEA7"
|
||||
:fill "transparent"
|
||||
:stroke-opacity "1"}}]
|
||||
|
||||
(for [[position [cx cy]] resize-handlers]
|
||||
[:* {:key (str "fragment-" (name position))}
|
||||
[:& rotation-handler {:key (str "rotation-" (name position))
|
||||
:cx cx
|
||||
:cy cy
|
||||
:position position
|
||||
:rotation (:rotation shape)
|
||||
:on-mouse-down on-rotate}]
|
||||
(let [tp (gpt/transform (gpt/point cx cy) transform)]
|
||||
[:* {:key (str "fragment-" (name position))}
|
||||
[:& rotation-handler {:key (str "rotation-" (name position))
|
||||
:cx (:x tp)
|
||||
:cy (:y tp)
|
||||
:position position
|
||||
:rotation (:rotation shape)
|
||||
:on-mouse-down on-rotate}]
|
||||
|
||||
[:& control-item {:key (str "resize-" (name position))
|
||||
:class (name position)
|
||||
:on-click #(on-resize position %)
|
||||
:r (/ radius zoom)
|
||||
:cx cx
|
||||
:cy cy}]])]))
|
||||
[:& control-item {:key (str "resize-" (name position))
|
||||
:class (name position)
|
||||
:on-click #(on-resize position %)
|
||||
:r (/ radius zoom)
|
||||
:cx (:x tp)
|
||||
:cy (:y tp)}]]))]))
|
||||
|
||||
;; --- Selection Handlers (Component)
|
||||
|
||||
|
@ -210,18 +135,20 @@
|
|||
(on-handler-move [delta index]
|
||||
(st/emit! (dw/update-path (:id shape) index delta)))]
|
||||
|
||||
(let [displacement (:displacement modifiers)
|
||||
(let [transform (geom/transform-matrix shape)
|
||||
displacement (:displacement modifiers)
|
||||
segments (cond->> (:segments shape)
|
||||
displacement (map #(gpt/transform % displacement)))]
|
||||
[:g.controls
|
||||
(for [[index {:keys [x y]}] (map-indexed vector segments)]
|
||||
[:circle {:cx x :cy y
|
||||
:r (/ 6.0 zoom)
|
||||
:key index
|
||||
:on-mouse-down #(on-mouse-down % index)
|
||||
:fill "#ffffff"
|
||||
:stroke "#1FDEA7"
|
||||
:style {:cursor "pointer"}}])])))
|
||||
(let [{:keys [x y]} (gpt/transform (gpt/point x y) transform)]
|
||||
[:circle {:cx x :cy y
|
||||
:r (/ 6.0 zoom)
|
||||
:key index
|
||||
:on-mouse-down #(on-mouse-down % index)
|
||||
:fill "#ffffff"
|
||||
:stroke "#1FDEA7"
|
||||
:style {:cursor "pointer"}}]))])))
|
||||
|
||||
;; TODO: add specs for clarity
|
||||
|
||||
|
@ -239,13 +166,13 @@
|
|||
:fill "transparent"}}]]))
|
||||
|
||||
(mf/defc multiple-selection-handlers
|
||||
[{:keys [shapes selected zoom] :as props}]
|
||||
[{:keys [shapes selected zoom objects] :as props}]
|
||||
(let [shape (geom/selection-rect shapes)
|
||||
on-resize #(do (dom/stop-propagation %2)
|
||||
(st/emit! (start-resize %1 selected shape)))
|
||||
(st/emit! (dw/start-resize %1 selected shape objects)))
|
||||
|
||||
on-rotate #(do (dom/stop-propagation %)
|
||||
(st/emit! (start-rotate shapes)))]
|
||||
(st/emit! (dw/start-rotate shapes)))]
|
||||
|
||||
[:& controls {:shape shape
|
||||
:zoom zoom
|
||||
|
@ -254,13 +181,15 @@
|
|||
|
||||
(mf/defc single-selection-handlers
|
||||
[{:keys [shape zoom objects] :as props}]
|
||||
(let [shape (geom/transform-shape shape)
|
||||
(let [shape-id (:id shape)
|
||||
shape (geom/transform-shape shape)
|
||||
shape' (if (debug? :simple-selection) (geom/selection-rect [shape]) shape)
|
||||
on-resize #(do (dom/stop-propagation %2)
|
||||
(st/emit! (start-resize %1 #{(:id shape)} shape)))
|
||||
(st/emit! (dw/start-resize %1 #{shape-id} shape' objects)))
|
||||
on-rotate #(do (dom/stop-propagation %)
|
||||
(st/emit! (start-rotate [shape])))]
|
||||
(st/emit! (dw/start-rotate [shape])))]
|
||||
|
||||
[:& controls {:shape shape
|
||||
[:& controls {:shape shape'
|
||||
:zoom zoom
|
||||
:on-rotate on-rotate
|
||||
:on-resize on-resize}]))
|
||||
|
@ -284,6 +213,7 @@
|
|||
(> num 1)
|
||||
[:& multiple-selection-handlers {:shapes shapes
|
||||
:selected selected
|
||||
:objects objects
|
||||
:zoom zoom}]
|
||||
|
||||
(and (= type :text)
|
||||
|
@ -298,5 +228,5 @@
|
|||
|
||||
:else
|
||||
[:& single-selection-handlers {:shape shape
|
||||
:objects objects
|
||||
:zoom zoom}])))
|
||||
:zoom zoom
|
||||
:objects objects}])))
|
||||
|
|
|
@ -149,7 +149,6 @@
|
|||
on-drop
|
||||
(fn [side {:keys [id name] :as data}]
|
||||
(let [index (if (= :top side) (inc index) index)]
|
||||
;; (println "droping" name "on" side "of" (:name item) "/" index)
|
||||
(st/emit! (dw/relocate-shape id (:id item) index))))
|
||||
|
||||
[dprops dref] (hooks/use-sortable
|
||||
|
|
|
@ -18,6 +18,6 @@
|
|||
[{:keys [shape] :as props}]
|
||||
[:div
|
||||
[:& measures-menu {:shape shape
|
||||
:options #{:circle-size :position :rotation}}]
|
||||
:options #{:size :position :rotation}}]
|
||||
[:& fill-menu {:shape shape}]
|
||||
[:& stroke-menu {:shape shape}]])
|
||||
|
|
|
@ -64,8 +64,10 @@
|
|||
delta (if (= attr :x)
|
||||
(gpt/point (math/neg (- pval cval)) 0)
|
||||
(gpt/point 0 (math/neg (- pval cval))))]
|
||||
(st/emit! (udw/apply-frame-displacement (:id shape) delta)
|
||||
(udw/materialize-frame-displacement (:id shape)))))
|
||||
|
||||
;; TODO: Change so not apply the modifiers until blur
|
||||
(st/emit! (udw/set-modifiers #{(:id shape)} {:displacement delta})
|
||||
(udw/apply-modifiers #{(:id shape)}))))
|
||||
|
||||
on-width-change #(on-size-change % :width)
|
||||
on-height-change #(on-size-change % :height)
|
||||
|
|
|
@ -67,7 +67,8 @@
|
|||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(d/parse-integer 0))]
|
||||
(st/emit! (udw/update-shape (:id shape) {:rotation value}))))
|
||||
(st/emit! (udw/set-rotation (- value (:rotation shape)) [shape])
|
||||
(udw/apply-modifiers #{(:id shape)}))))
|
||||
|
||||
on-radius-change
|
||||
(fn [event]
|
||||
|
|
|
@ -111,6 +111,9 @@
|
|||
not-found))
|
||||
not-found coll)))
|
||||
|
||||
(defn zip [col1 col2]
|
||||
(map vector col1 col2))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Numbers Parsing
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
39
frontend/src/uxbox/util/debug.cljs
Normal file
39
frontend/src/uxbox/util/debug.cljs
Normal file
|
@ -0,0 +1,39 @@
|
|||
(ns uxbox.util.debug
|
||||
"Debugging utils"
|
||||
(:require
|
||||
[uxbox.main.store :as store]))
|
||||
|
||||
(def debug-options #{:bounding-boxes :group :events #_:simple-selection})
|
||||
|
||||
(defonce ^:dynamic *debug* (atom #{}))
|
||||
|
||||
(defn debug-all! [] (reset! *debug* debug-options))
|
||||
(defn debug-none! [] (reset! *debug* #{}))
|
||||
(defn debug! [option] (swap! *debug* conj option))
|
||||
(defn -debug! [option] (swap! *debug* disj option))
|
||||
(defn debug? [option] (@*debug* option))
|
||||
|
||||
(defn tap
|
||||
"Transducer function that can execute a side-effect `effect-fn` per input"
|
||||
[effect-fn]
|
||||
|
||||
(fn [rf]
|
||||
(fn
|
||||
([] (rf))
|
||||
([result] (rf result))
|
||||
([result input]
|
||||
(effect-fn input)
|
||||
(rf result input)))))
|
||||
|
||||
(defn logjs
|
||||
([str] (tap (partial logjs str)))
|
||||
([str val]
|
||||
(js/console.log str (clj->js val))
|
||||
val))
|
||||
|
||||
(defn dump-state []
|
||||
(logjs "state" @store/state))
|
||||
|
||||
(defn dump-objects []
|
||||
(let [page-id (get @store/state :page-id)]
|
||||
(logjs "state" (get-in @store/state [:workspace-data page-id :objects]))))
|
|
@ -33,6 +33,13 @@
|
|||
([m1 m2 & others]
|
||||
(reduce multiply (multiply m1 m2) others)))
|
||||
|
||||
(defn substract
|
||||
[{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f :as m1}
|
||||
{m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f :as m2}]
|
||||
(Matrix.
|
||||
(- m1a m2a) (- m1b m2b) (- m1c m2c)
|
||||
(- m1d m2d) (- m1e m2e) (- m1f m2f)))
|
||||
|
||||
(defn ^boolean matrix?
|
||||
"Return true if `v` is Matrix instance."
|
||||
[v]
|
||||
|
@ -51,44 +58,63 @@
|
|||
(Matrix. 1 0 0 1 x y))
|
||||
|
||||
(defn scale-matrix
|
||||
[{x :x y :y :as pt}]
|
||||
(assert (gpt/point? pt))
|
||||
(Matrix. x 0 0 y 0 0))
|
||||
([pt center]
|
||||
(multiply (translate-matrix center)
|
||||
(scale-matrix pt)
|
||||
(translate-matrix (gpt/negate center))))
|
||||
([{x :x y :y :as pt}]
|
||||
(assert (gpt/point? pt))
|
||||
(Matrix. x 0 0 y 0 0)))
|
||||
|
||||
(defn rotate-matrix
|
||||
[a]
|
||||
(let [a (mth/radians a)]
|
||||
(Matrix. (mth/cos a)
|
||||
(mth/sin a)
|
||||
(- (mth/sin a))
|
||||
(mth/cos a)
|
||||
0
|
||||
0)))
|
||||
([angle point] (multiply (translate-matrix point)
|
||||
(rotate-matrix angle)
|
||||
(translate-matrix (gpt/negate point))))
|
||||
([angle]
|
||||
(let [a (mth/radians angle)]
|
||||
(Matrix. (mth/cos a)
|
||||
(mth/sin a)
|
||||
(- (mth/sin a))
|
||||
(mth/cos a)
|
||||
0
|
||||
0))))
|
||||
|
||||
(defn skew-matrix
|
||||
([angle-x angle-y point]
|
||||
(multiply (translate-matrix point)
|
||||
(skew-matrix angle-y angle-y)
|
||||
(translate-matrix (gpt/negate point))))
|
||||
([angle-x angle-y]
|
||||
(let [m1 (mth/tan (mth/radians angle-x))
|
||||
m2 (mth/tan (mth/radians angle-y))]
|
||||
(Matrix. 1 m2 m1 1 0 0))))
|
||||
|
||||
(defn rotate
|
||||
"Apply rotation transformation to the matrix."
|
||||
([m angle]
|
||||
(multiply m (rotate-matrix angle)))
|
||||
([m angle center]
|
||||
(multiply m
|
||||
(translate-matrix center)
|
||||
(rotate-matrix angle)
|
||||
(translate-matrix (gpt/negate center)))))
|
||||
(multiply m (rotate-matrix angle center))))
|
||||
|
||||
(defn scale
|
||||
"Apply scale transformation to the matrix."
|
||||
([m scale] (multiply m (scale-matrix scale)))
|
||||
([m scale]
|
||||
(multiply m (scale-matrix scale)))
|
||||
([m scale center]
|
||||
(multiply m
|
||||
(translate-matrix center)
|
||||
(scale-matrix scale)
|
||||
(translate-matrix (gpt/negate center)))))
|
||||
(multiply m (scale-matrix scale center))))
|
||||
|
||||
(defn translate
|
||||
"Apply translate transformation to the matrix."
|
||||
[m pt]
|
||||
(multiply m (translate-matrix pt)))
|
||||
|
||||
(defn skew
|
||||
"Apply translate transformation to the matrix."
|
||||
([m angle-x angle-y]
|
||||
(multiply m (skew-matrix angle-x angle-y)))
|
||||
([m angle-x angle-y p]
|
||||
(multiply m (skew-matrix angle-x angle-y p))))
|
||||
|
||||
;; --- Transit Adapter
|
||||
|
||||
(def matrix-write-handler
|
||||
|
@ -100,22 +126,3 @@
|
|||
(t/read-handler
|
||||
(fn [value]
|
||||
(map->Matrix value))))
|
||||
|
||||
;; Calculates the delta vector to move the figure when scaling after rotation
|
||||
;; https://math.stackexchange.com/questions/1449672/determine-shift-between-scaled-rotated-object-and-additional-scale-step
|
||||
(defn correct-rotation [handler lx ly kx ky angle]
|
||||
(let [[s1 s2 s3]
|
||||
;; Different sign configurations change the anchor corner
|
||||
(cond
|
||||
(#{:right :bottom :bottom-right} handler) [-1 1 1]
|
||||
(#{:left :top :top-left} handler) [1 -1 1]
|
||||
(#{:bottom-left} handler) [-1 -1 -1]
|
||||
(#{:top-right} handler) [1 1 -1])
|
||||
rad (* (or angle 0) (/ Math/PI 180))
|
||||
kx' (* (/ (- kx 1.) 2.) lx)
|
||||
ky' (* (/ (- ky 1.) 2.) ly)
|
||||
dx (+ (* s3 (* kx' (- 1 (Math/cos rad))))
|
||||
(* ky' (Math/sin rad)))
|
||||
dy (+ (* (- s3) (* ky' (- 1 (Math/cos rad))))
|
||||
(* kx' (Math/sin rad)))]
|
||||
(gpt/point (* s1 dx) (* s2 dy))))
|
||||
|
|
|
@ -9,13 +9,17 @@
|
|||
|
||||
(ns uxbox.util.geom.point
|
||||
(:refer-clojure :exclude [divide])
|
||||
(:require [uxbox.util.math :as mth]
|
||||
[cognitect.transit :as t]))
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.math :as mth]
|
||||
[cognitect.transit :as t]))
|
||||
|
||||
;; --- Point Impl
|
||||
|
||||
(defrecord Point [x y])
|
||||
|
||||
(defn s [{:keys [x y]}] (str "(" x "," y ")"))
|
||||
|
||||
(defn ^boolean point?
|
||||
"Return true if `v` is Point instance."
|
||||
[v]
|
||||
|
@ -71,6 +75,11 @@
|
|||
(assert (point? other))
|
||||
(Point. (/ x ox) (/ y oy)))
|
||||
|
||||
(defn inverse
|
||||
[{:keys [x y] :as p}]
|
||||
(assert (point? p))
|
||||
(Point. (/ 1 x) (/ 1 y)))
|
||||
|
||||
(defn negate
|
||||
[{x :x y :y :as p}]
|
||||
(assert (point? p))
|
||||
|
@ -161,3 +170,23 @@
|
|||
(t/read-handler
|
||||
(fn [value]
|
||||
(map->Point value))))
|
||||
|
||||
|
||||
;; Vector functions
|
||||
(defn to-vec [p1 p2]
|
||||
(subtract p2 p1))
|
||||
|
||||
(defn dot [{x1 :x y1 :y} {x2 :x y2 :y}]
|
||||
(+ (* x1 x2) (* y1 y2)))
|
||||
|
||||
(defn unit [v]
|
||||
(let [v-length (length v)]
|
||||
(divide v (point v-length v-length))))
|
||||
|
||||
(defn project [v1 v2]
|
||||
(let [v2-unit (unit v2)
|
||||
scalar-projection (dot v1 (unit v2))]
|
||||
(multiply
|
||||
v2-unit
|
||||
(point scalar-projection scalar-projection))))
|
||||
|
||||
|
|
|
@ -2,54 +2,46 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.geom
|
||||
(ns uxbox.util.geom.shapes
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.data :as d]
|
||||
[uxbox.util.debug :as debug]
|
||||
[uxbox.main.data.helpers :as helpers]))
|
||||
|
||||
;; --- Relative Movement
|
||||
|
||||
(declare move-rect)
|
||||
(declare move-path)
|
||||
(declare move-circle)
|
||||
|
||||
(defn- _chk
|
||||
"Function that checks if a number is nil or nan. Will return 0 when not
|
||||
valid and the number otherwise."
|
||||
[v]
|
||||
(if (or (not v) (mth/nan? v)) 0 v))
|
||||
|
||||
(defn move
|
||||
"Move the shape relativelly to its current
|
||||
position applying the provided delta."
|
||||
[shape dpoint]
|
||||
(case (:type shape)
|
||||
:icon (move-rect shape dpoint)
|
||||
:image (move-rect shape dpoint)
|
||||
:rect (move-rect shape dpoint)
|
||||
:frame (move-rect shape dpoint)
|
||||
:text (move-rect shape dpoint)
|
||||
:curve (move-path shape dpoint)
|
||||
:path (move-path shape dpoint)
|
||||
:circle (move-circle shape dpoint)
|
||||
:group (move-rect shape dpoint)
|
||||
shape))
|
||||
(move-rect shape dpoint)))
|
||||
|
||||
(defn- move-rect
|
||||
"A specialized function for relative movement
|
||||
for rect-like shapes."
|
||||
[shape {dx :x dy :y}]
|
||||
(assoc shape
|
||||
:x (mth/round (+ (:x shape) dx))
|
||||
:y (mth/round (+ (:y shape) dy))))
|
||||
|
||||
(defn- move-circle
|
||||
"A specialized function for relative movement
|
||||
for circle shapes."
|
||||
[shape {dx :x dy :y}]
|
||||
(assoc shape
|
||||
:cx (mth/round (+ (:cx shape) dx))
|
||||
:cy (mth/round (+ (:cy shape) dy))))
|
||||
:x (mth/round (+ (_chk (:x shape)) (_chk dx)))
|
||||
:y (mth/round (+ (_chk (:y shape)) (_chk dy)))))
|
||||
|
||||
(defn- move-path
|
||||
"A specialized function for relative movement
|
||||
|
@ -71,35 +63,21 @@
|
|||
;; --- Absolute Movement
|
||||
|
||||
(declare absolute-move-rect)
|
||||
(declare absolute-move-circle)
|
||||
|
||||
(defn absolute-move
|
||||
"Move the shape to the exactly specified position."
|
||||
[shape position]
|
||||
(case (:type shape)
|
||||
:icon (absolute-move-rect shape position)
|
||||
:frame (absolute-move-rect shape position)
|
||||
:image (absolute-move-rect shape position)
|
||||
:rect (absolute-move-rect shape position)
|
||||
:text (absolute-move-rect shape position)
|
||||
:group (absolute-move-rect shape position)
|
||||
:circle (absolute-move-circle shape position)
|
||||
shape))
|
||||
:path shape
|
||||
:curve shape
|
||||
(absolute-move-rect shape position)))
|
||||
|
||||
(defn- absolute-move-rect
|
||||
"A specialized function for absolute moviment
|
||||
for rect-like shapes."
|
||||
[shape {:keys [x y] :as pos}]
|
||||
(let [dx (if x (- x (:x shape)) 0)
|
||||
dy (if y (- y (:y shape)) 0)]
|
||||
(move shape (gpt/point dx dy))))
|
||||
|
||||
(defn- absolute-move-circle
|
||||
"A specialized function for absolute moviment
|
||||
for rect-like shapes."
|
||||
[shape {:keys [x y] :as pos}]
|
||||
(let [dx (if x (- x (:cx shape)) 0)
|
||||
dy (if y (- y (:cy shape)) 0)]
|
||||
(let [dx (if x (- (_chk x) (_chk (:x shape))) 0)
|
||||
dy (if y (- (_chk y) (_chk (:y shape))) 0)]
|
||||
(move shape (gpt/point dx dy))))
|
||||
|
||||
;; --- Rotation
|
||||
|
@ -113,16 +91,23 @@
|
|||
[shape rotation]
|
||||
(assoc shape :rotation rotation))
|
||||
|
||||
;; --- Corner points
|
||||
|
||||
(defn corner-points [points]
|
||||
(let [minx (apply min (map :x points))
|
||||
miny (apply min (map :y points))
|
||||
maxx (apply max (map :x points))
|
||||
maxy (apply max (map :y points))]
|
||||
{:x1 minx :y1 miny :x2 maxx :y2 maxy}))
|
||||
|
||||
;; --- Size
|
||||
|
||||
(declare size-circle)
|
||||
(declare size-path)
|
||||
|
||||
(defn size
|
||||
"Calculate the size of the shape."
|
||||
[shape]
|
||||
(case (:type shape)
|
||||
:circle (size-circle shape)
|
||||
:curve (size-path shape)
|
||||
:path (size-path shape)
|
||||
shape))
|
||||
|
@ -141,24 +126,15 @@
|
|||
:width (- maxx minx)
|
||||
:height (- maxy miny)))))
|
||||
|
||||
(defn- size-circle
|
||||
"A specialized function for calculate size
|
||||
for circle shape."
|
||||
[{:keys [rx ry] :as shape}]
|
||||
(merge shape {:width (* rx 2)
|
||||
:height (* ry 2)}))
|
||||
|
||||
;; --- Center
|
||||
|
||||
(declare center-rect)
|
||||
(declare center-circle)
|
||||
(declare center-path)
|
||||
|
||||
(defn center
|
||||
"Calculate the center of the shape."
|
||||
[shape]
|
||||
(case (:type shape)
|
||||
:circle (center-circle shape)
|
||||
:curve (center-path shape)
|
||||
:path (center-path shape)
|
||||
(center-rect shape)))
|
||||
|
@ -167,30 +143,30 @@
|
|||
[{:keys [x y width height] :as shape}]
|
||||
(gpt/point (+ x (/ width 2)) (+ y (/ height 2))))
|
||||
|
||||
(defn- center-circle
|
||||
[{:keys [cx cy] :as shape}]
|
||||
(gpt/point cx cy))
|
||||
|
||||
(defn- center-path
|
||||
[{:keys [segments x1 y1 x2 y2] :as shape}]
|
||||
(if (and x1 y1 x2 y2)
|
||||
(gpt/point (/ (+ x1 x2) 2) (/ (+ y1 y2) 2))
|
||||
(let [minx (apply min (map :x segments))
|
||||
miny (apply min (map :y segments))
|
||||
maxx (apply max (map :x segments))
|
||||
maxy (apply max (map :y segments))]
|
||||
(gpt/point (/ (+ minx maxx) 2) (/ (+ miny maxy) 2)))))
|
||||
[{:keys [segments] :as shape}]
|
||||
(let [minx (apply min (map :x segments))
|
||||
miny (apply min (map :y segments))
|
||||
maxx (apply max (map :x segments))
|
||||
maxy (apply max (map :y segments))]
|
||||
(gpt/point (/ (+ minx maxx) 2) (/ (+ miny maxy) 2))))
|
||||
|
||||
(defn center->rect
|
||||
"Creates a rect given a center and a width and height"
|
||||
[center width height]
|
||||
{:x (- (:x center) (/ width 2))
|
||||
:y (- (:y center) (/ height 2))
|
||||
:width width
|
||||
:height height})
|
||||
|
||||
;; --- Proportions
|
||||
|
||||
(declare assign-proportions-path)
|
||||
(declare assign-proportions-circle)
|
||||
(declare assign-proportions-rect)
|
||||
|
||||
(defn assign-proportions
|
||||
[{:keys [type] :as shape}]
|
||||
(case type
|
||||
:circle (assign-proportions-circle shape)
|
||||
:path (assign-proportions-path shape)
|
||||
(assign-proportions-rect shape)))
|
||||
|
||||
|
@ -198,10 +174,6 @@
|
|||
[{:keys [width height] :as shape}]
|
||||
(assoc shape :proportion (/ width height)))
|
||||
|
||||
(defn- assign-proportions-circle
|
||||
[{:as shape}]
|
||||
(assoc shape :proportion 1))
|
||||
|
||||
;; TODO: implement the rest of shapes
|
||||
|
||||
;; --- Paths
|
||||
|
@ -259,22 +231,6 @@
|
|||
(assoc :height value)
|
||||
(assoc :width (* value proportion)))))))
|
||||
|
||||
(defn resize-circle
|
||||
[shape attr value]
|
||||
(us/assert map? shape)
|
||||
(us/assert #{:rx :ry} attr)
|
||||
(us/assert number? value)
|
||||
(let [{:keys [proportion proportion-lock]} shape]
|
||||
(if-not proportion-lock
|
||||
(assoc shape attr value)
|
||||
(if (= attr :rx)
|
||||
(-> shape
|
||||
(assoc :rx value)
|
||||
(assoc :ry (/ value proportion)))
|
||||
(-> shape
|
||||
(assoc :ry value)
|
||||
(assoc :rx (* value proportion)))))))
|
||||
|
||||
;; --- Resize
|
||||
|
||||
(defn calculate-scale-ratio
|
||||
|
@ -297,26 +253,6 @@
|
|||
:right [:x1 :y1]
|
||||
:left [:x2 :y1]))
|
||||
|
||||
(defn generate-resize-matrix
|
||||
"Generate the resize transformation matrix given a corner-id, shape
|
||||
and the scale factor vector. The shape should be of rect-like type.
|
||||
|
||||
Mainly used by drawarea and shape resize on workspace."
|
||||
[vid shape [scalex scaley]]
|
||||
(let [[cor-x cor-y] (get-vid-coords vid)
|
||||
{:keys [x y width height rotation]} shape
|
||||
cx (+ x (/ width 2))
|
||||
cy (+ y (/ height 2))
|
||||
center (gpt/point cx cy)
|
||||
]
|
||||
(-> (gmt/matrix)
|
||||
;; Correction first otherwise the scale is going to deform the correction
|
||||
(gmt/translate (gmt/correct-rotation
|
||||
vid width height scalex scaley rotation))
|
||||
(gmt/scale (gpt/point scalex scaley)
|
||||
(gpt/point (cor-x shape)
|
||||
(cor-y shape))))))
|
||||
|
||||
(defn resize-shape
|
||||
"Apply a resize transformation to a rect-like shape. The shape
|
||||
should have the `width` and `height` attrs, because these attrs
|
||||
|
@ -325,9 +261,10 @@
|
|||
Mainly used in drawarea and interactive resize on workspace
|
||||
with the main objective that on the end of resize have a way
|
||||
a calculte the resize ratio with `calculate-scale-ratio`."
|
||||
[vid shape {:keys [x y] :as point} lock?]
|
||||
[vid shape initial target lock?]
|
||||
|
||||
(let [[cor-x cor-y] (get-vid-coords vid)]
|
||||
(let [{:keys [x y]} (gpt/subtract target initial)
|
||||
[cor-x cor-y] (get-vid-coords vid)]
|
||||
(let [final-x (if (#{:top :bottom} vid) (:x2 shape) x)
|
||||
final-y (if (#{:right :left} vid) (:y2 shape) y)
|
||||
width (Math/abs (- final-x (cor-x shape)))
|
||||
|
@ -341,7 +278,6 @@
|
|||
|
||||
(declare setup-rect)
|
||||
(declare setup-image)
|
||||
(declare setup-circle)
|
||||
|
||||
(defn setup
|
||||
"A function that initializes the first coordinates for
|
||||
|
@ -349,7 +285,6 @@
|
|||
[shape props]
|
||||
(case (:type shape)
|
||||
:image (setup-image shape props)
|
||||
:circle (setup-circle shape props)
|
||||
(setup-rect shape props)))
|
||||
|
||||
(defn- setup-rect
|
||||
|
@ -361,15 +296,6 @@
|
|||
:width width
|
||||
:height height))
|
||||
|
||||
(defn- setup-circle
|
||||
"A specialized function for setup circle shapes."
|
||||
[shape {:keys [x y width height]}]
|
||||
(assoc shape
|
||||
:cx x
|
||||
:cy y
|
||||
:rx (mth/abs width)
|
||||
:ry (mth/abs height)))
|
||||
|
||||
(defn- setup-image
|
||||
[{:keys [metadata] :as shape} {:keys [x y width height] :as props}]
|
||||
(assoc shape
|
||||
|
@ -383,7 +309,6 @@
|
|||
|
||||
;; --- Coerce to Rect-like shape.
|
||||
|
||||
(declare circle->rect-shape)
|
||||
(declare path->rect-shape)
|
||||
(declare group->rect-shape)
|
||||
(declare rect->rect-shape)
|
||||
|
@ -392,7 +317,6 @@
|
|||
"Coerce shape to rect like shape."
|
||||
[{:keys [type] :as shape}]
|
||||
(case type
|
||||
:circle (circle->rect-shape shape)
|
||||
:path (path->rect-shape shape)
|
||||
:curve (path->rect-shape shape)
|
||||
(rect->rect-shape shape)))
|
||||
|
@ -414,6 +338,29 @@
|
|||
:height (- maxy miny)
|
||||
:type :rect}))
|
||||
|
||||
(declare rect->path)
|
||||
|
||||
(defn shape->path
|
||||
[shape]
|
||||
(case (:type shape)
|
||||
:path shape
|
||||
:curve shape
|
||||
(rect->path shape)))
|
||||
|
||||
(defn rect->path
|
||||
[{:keys [x y width height] :as shape}]
|
||||
|
||||
(let [points [(gpt/point x y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point x (+ y height))
|
||||
(gpt/point x y)]]
|
||||
(-> shape
|
||||
(assoc :type :path)
|
||||
(assoc :segments points))))
|
||||
|
||||
;; --- SHAPE -> RECT
|
||||
|
||||
(defn- rect->rect-shape
|
||||
[{:keys [x y width height] :as shape}]
|
||||
(assoc shape
|
||||
|
@ -438,22 +385,6 @@
|
|||
:width (- maxx minx)
|
||||
:height (- maxy miny))))
|
||||
|
||||
(defn- circle->rect-shape
|
||||
[{:keys [cx cy rx ry] :as shape}]
|
||||
(let [width (* rx 2)
|
||||
height (* ry 2)
|
||||
x1 (- cx rx)
|
||||
y1 (- cy ry)]
|
||||
(assoc shape
|
||||
:x1 x1
|
||||
:y1 y1
|
||||
:x2 (+ x1 width)
|
||||
:y2 (+ y1 height)
|
||||
:x x1
|
||||
:y y1
|
||||
:width width
|
||||
:height height)))
|
||||
|
||||
;; --- Resolve Shape
|
||||
|
||||
(declare resolve-rect-shape)
|
||||
|
@ -477,7 +408,6 @@
|
|||
;; --- Transform Shape
|
||||
|
||||
(declare transform-rect)
|
||||
(declare transform-circle)
|
||||
(declare transform-path)
|
||||
|
||||
(defn transform
|
||||
|
@ -485,18 +415,20 @@
|
|||
[{:keys [type] :as shape} xfmt]
|
||||
(if (gmt/matrix? xfmt)
|
||||
(case type
|
||||
:frame (transform-rect shape xfmt)
|
||||
:group (transform-rect shape xfmt)
|
||||
:rect (transform-rect shape xfmt)
|
||||
:icon (transform-rect shape xfmt)
|
||||
:text (transform-rect shape xfmt)
|
||||
:image (transform-rect shape xfmt)
|
||||
:path (transform-path shape xfmt)
|
||||
:curve (transform-path shape xfmt)
|
||||
:circle (transform-circle shape xfmt)
|
||||
shape)
|
||||
(transform-rect shape xfmt))
|
||||
shape))
|
||||
|
||||
(defn center-transform [shape matrix]
|
||||
(let [shape-center (center shape)]
|
||||
(-> shape
|
||||
(transform
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate shape-center)
|
||||
(gmt/multiply matrix)
|
||||
(gmt/translate (gpt/negate shape-center)))))))
|
||||
|
||||
(defn- transform-rect
|
||||
[{:keys [x y width height] :as shape} mx]
|
||||
(let [tl (gpt/transform (gpt/point x y) mx)
|
||||
|
@ -514,27 +446,6 @@
|
|||
:width (- maxx minx)
|
||||
:height (- maxy miny))))
|
||||
|
||||
(defn- transform-circle
|
||||
[{:keys [cx cy rx ry] :as shape} xfmt]
|
||||
(let [{:keys [x1 y1 x2 y2]} (shape->rect-shape shape)
|
||||
tl (gpt/transform (gpt/point x1 y1) xfmt)
|
||||
tr (gpt/transform (gpt/point x2 y1) xfmt)
|
||||
bl (gpt/transform (gpt/point x1 y2) xfmt)
|
||||
br (gpt/transform (gpt/point x2 y2) xfmt)
|
||||
|
||||
;; TODO: replace apply with transduce (performance)
|
||||
x (apply min (map :x [tl tr bl br]))
|
||||
y (apply min (map :y [tl tr bl br]))
|
||||
maxx (apply max (map :x [tl tr bl br]))
|
||||
maxy (apply max (map :y [tl tr bl br]))
|
||||
width (- maxx x)
|
||||
height (- maxy y)
|
||||
cx (+ x (/ width 2))
|
||||
cy (+ y (/ height 2))
|
||||
rx (/ width 2)
|
||||
ry (/ height 2)]
|
||||
(assoc shape :cx cx :cy cy :rx rx :ry ry)))
|
||||
|
||||
(defn- transform-path
|
||||
[{:keys [segments] :as shape} xfmt]
|
||||
(let [segments (mapv #(gpt/transform % xfmt) segments)]
|
||||
|
@ -551,39 +462,19 @@
|
|||
(and rotation (pos? rotation))
|
||||
(gmt/rotate rotation (gpt/point cx cy)))))
|
||||
|
||||
(defn resolve-rotation
|
||||
[shape]
|
||||
(transform shape (rotation-matrix shape)))
|
||||
(declare transform-apply-modifiers)
|
||||
|
||||
(defn resolve-modifier
|
||||
[{:keys [resize-modifier displacement-modifier rotation-modifier] :as shape}]
|
||||
(cond-> shape
|
||||
(gmt/matrix? resize-modifier)
|
||||
(transform resize-modifier)
|
||||
|
||||
(gmt/matrix? displacement-modifier)
|
||||
(transform displacement-modifier)
|
||||
|
||||
rotation-modifier
|
||||
(update :rotation #(+ (or % 0) rotation-modifier))))
|
||||
|
||||
;; NOTE: we need apply `shape->rect-shape` 3 times because we need to
|
||||
;; update the x1 x2 y1 y2 attributes on each step; this is because
|
||||
;; some transform functions still uses that attributes. WE NEED TO
|
||||
;; REFACTOR this, and remove any usage of the old xN yN attributes.
|
||||
|
||||
(def ^:private xf-resolve-shape
|
||||
(comp (map shape->rect-shape)
|
||||
(map resolve-modifier)
|
||||
(map shape->rect-shape)
|
||||
(map resolve-rotation)
|
||||
(map shape->rect-shape)))
|
||||
(defn selection-rect-shape [shape]
|
||||
(-> shape
|
||||
(transform-apply-modifiers)
|
||||
(shape->rect-shape)))
|
||||
|
||||
(defn selection-rect
|
||||
"Returns a rect that contains all the shapes and is aware of the
|
||||
rotation of each shape. Mainly used for multiple selection."
|
||||
[shapes]
|
||||
(let [shapes (into [] xf-resolve-shape shapes)
|
||||
(let [xf-resolve-shape (map selection-rect-shape)
|
||||
shapes (into [] xf-resolve-shape shapes)
|
||||
minx (transduce (map :x1) min shapes)
|
||||
miny (transduce (map :y1) min shapes)
|
||||
maxx (transduce (map :x2) max shapes)
|
||||
|
@ -744,16 +635,200 @@
|
|||
:type :rect}]
|
||||
(overlaps? shape selrect)))
|
||||
|
||||
|
||||
(defn calculate-rec-path-skew-angle
|
||||
[path-shape]
|
||||
(let [p1 (get-in path-shape [:segments 2])
|
||||
p2 (get-in path-shape [:segments 3])
|
||||
p3 (get-in path-shape [:segments 4])
|
||||
v1 (gpt/to-vec p1 p2)
|
||||
v2 (gpt/to-vec p2 p3)]
|
||||
(- 90 (gpt/angle-with-other v1 v2))))
|
||||
|
||||
(defn calculate-rec-path-height
|
||||
"Calculates the height of a paralelogram given by the path"
|
||||
[path-shape]
|
||||
|
||||
(let [p1 (get-in path-shape [:segments 2])
|
||||
p2 (get-in path-shape [:segments 3])
|
||||
p3 (get-in path-shape [:segments 4])
|
||||
v1 (gpt/to-vec p1 p2)
|
||||
v2 (gpt/to-vec p2 p3)
|
||||
angle (gpt/angle-with-other v1 v2)]
|
||||
(* (gpt/length v2) (mth/sin (mth/radians angle)))))
|
||||
|
||||
(defn calculate-rec-path-rotation
|
||||
[path-shape1 path-shape2 resize-vector]
|
||||
|
||||
(let [idx-1 0
|
||||
idx-2 (cond (and (neg? (:x resize-vector)) (pos? (:y resize-vector))) 1
|
||||
(and (neg? (:x resize-vector)) (neg? (:y resize-vector))) 2
|
||||
(and (pos? (:x resize-vector)) (neg? (:y resize-vector))) 3
|
||||
:else 0)
|
||||
p1 (get-in path-shape1 [:segments idx-1])
|
||||
p2 (get-in path-shape2 [:segments idx-2])
|
||||
v1 (gpt/to-vec (center path-shape1) p1)
|
||||
v2 (gpt/to-vec (center path-shape2) p2)
|
||||
|
||||
rot-angle (gpt/angle-with-other v1 v2)
|
||||
rot-sign (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)]
|
||||
(* rot-sign rot-angle)))
|
||||
|
||||
(defn transform-shape-point
|
||||
"Transform a point around the shape center"
|
||||
[point shape transform]
|
||||
(let [shape-center (center shape)]
|
||||
(gpt/transform
|
||||
point
|
||||
(-> (gmt/multiply
|
||||
(gmt/translate-matrix shape-center)
|
||||
transform
|
||||
(gmt/translate-matrix (gpt/negate shape-center)))))))
|
||||
|
||||
(defn- transform-apply-modifiers
|
||||
[shape]
|
||||
(let [modifiers (:modifiers shape)
|
||||
ds-modifier (:displacement modifiers (gmt/matrix))
|
||||
resize (:resize-vector modifiers (gpt/point 1 1))
|
||||
origin (:resize-origin modifiers (gpt/point 0 0))
|
||||
resize-transform (:resize-transform modifiers (gmt/matrix))
|
||||
resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix))
|
||||
rt-modif (:rotation modifiers 0)
|
||||
|
||||
shape (-> shape
|
||||
(transform ds-modifier))
|
||||
|
||||
shape-center (center shape)]
|
||||
|
||||
(-> (shape->path shape)
|
||||
(transform (-> (gmt/matrix)
|
||||
|
||||
;; Applies the current resize transformation
|
||||
(gmt/translate origin)
|
||||
(gmt/multiply resize-transform)
|
||||
(gmt/scale resize)
|
||||
(gmt/multiply resize-transform-inverse)
|
||||
(gmt/translate (gpt/negate origin))
|
||||
|
||||
;; Applies the stacked transformations
|
||||
(gmt/translate shape-center)
|
||||
(gmt/multiply (gmt/rotate-matrix rt-modif))
|
||||
(gmt/multiply (:transform shape (gmt/matrix)))
|
||||
(gmt/translate (gpt/negate shape-center)))))))
|
||||
|
||||
(defn rect-path-dimensions [rect-path]
|
||||
(let [seg (:segments rect-path)
|
||||
[width height] (mapv (fn [[c1 c2]] (gpt/distance c1 c2)) (take 2 (d/zip seg (rest seg))))]
|
||||
{:width width
|
||||
:height height}))
|
||||
|
||||
(defn calculate-stretch [shape-path transform-inverse]
|
||||
(let [shape-center (center shape-path)
|
||||
shape-path-temp (transform
|
||||
shape-path
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate shape-center)
|
||||
(gmt/multiply transform-inverse)
|
||||
(gmt/translate (gpt/negate shape-center))))
|
||||
|
||||
shape-path-temp-rec (shape->rect-shape shape-path-temp)
|
||||
shape-path-temp-dim (rect-path-dimensions shape-path-temp)]
|
||||
(gpt/divide (gpt/point (:width shape-path-temp-rec) (:height shape-path-temp-rec))
|
||||
(gpt/point (:width shape-path-temp-dim) (:height shape-path-temp-dim)))))
|
||||
|
||||
(defn transform-selrect
|
||||
[frame shape]
|
||||
(-> shape
|
||||
(transform-apply-modifiers)
|
||||
(translate-to-frame frame)
|
||||
(shape->rect-shape)))
|
||||
|
||||
(defn transform-rect-shape
|
||||
[shape]
|
||||
(let [;; Apply modifiers to the rect as a path so we have the end shape expected
|
||||
shape-path (transform-apply-modifiers shape)
|
||||
shape-center (center shape-path)
|
||||
resize-vector (get-in shape [:modifiers :resize-vector] (gpt/point 1 1))
|
||||
|
||||
;; Reverse the current transformation stack to get the base rectangle
|
||||
shape-path-temp (center-transform shape-path (:transform-inverse shape (gmt/matrix)))
|
||||
shape-path-temp-dim (rect-path-dimensions shape-path-temp)
|
||||
shape-path-temp-rec (shape->rect-shape shape-path-temp)
|
||||
|
||||
;; This rectangle is the new data for the current rectangle. We want to change our rectangle
|
||||
;; to have this width, height, x, y
|
||||
rec (center->rect shape-center (:width shape-path-temp-dim) (:height shape-path-temp-dim))
|
||||
rec-path (rect->path rec)
|
||||
|
||||
;; The next matrix is a series of transformations we have to do to the previous rec so that
|
||||
;; after applying them the end result is the `shape-path-temp`
|
||||
;; This is compose of three transformations: skew, resize and rotation
|
||||
stretch-matrix (gmt/matrix)
|
||||
|
||||
skew-angle (calculate-rec-path-skew-angle shape-path-temp)
|
||||
|
||||
;; When one of the axis is flipped we have to reverse the skew
|
||||
skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle )
|
||||
|
||||
stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
|
||||
|
||||
h1 (calculate-rec-path-height shape-path-temp)
|
||||
h2 (calculate-rec-path-height (center-transform rec-path stretch-matrix))
|
||||
stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point 1 (/ h1 h2))))
|
||||
|
||||
rotation-angle (calculate-rec-path-rotation (center-transform rec-path stretch-matrix) shape-path-temp resize-vector)
|
||||
|
||||
stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
|
||||
|
||||
;; This is the inverse to be able to remove the transformation
|
||||
stretch-matrix-inverse (-> (gmt/matrix)
|
||||
(gmt/scale (gpt/point 1 (/ h2 h1)))
|
||||
(gmt/skew (- skew-angle) 0)
|
||||
(gmt/rotate (- rotation-angle)))
|
||||
|
||||
new-shape (-> shape
|
||||
(merge rec)
|
||||
(update :transform #(gmt/multiply (or % (gmt/matrix)) stretch-matrix))
|
||||
(update :transform-inverse #(gmt/multiply stretch-matrix-inverse (or % (gmt/matrix)))))]
|
||||
|
||||
new-shape))
|
||||
|
||||
(defn transform-path-shape
|
||||
[shape]
|
||||
(transform-apply-modifiers shape)
|
||||
;; TODO: Addapt for paths is not working
|
||||
#_(let [shape-path (transform-apply-modifiers shape)
|
||||
shape-path-center (center shape-path)
|
||||
|
||||
shape-transform-inverse' (-> (gmt/matrix)
|
||||
(gmt/translate shape-path-center)
|
||||
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
|
||||
(gmt/multiply (gmt/rotate-matrix (- (:rotation-modifier shape 0))))
|
||||
(gmt/translate (gpt/negate shape-path-center)))]
|
||||
(-> shape-path
|
||||
(transform shape-transform-inverse')
|
||||
(add-rotate-transform (:rotation-modifier shape 0)))))
|
||||
|
||||
(defn transform-shape
|
||||
"Transform the shape properties given the modifiers"
|
||||
([shape] (transform-shape nil shape))
|
||||
([frame shape]
|
||||
(let [ds-modifier (:displacement-modifier shape)
|
||||
rz-modifier (:resize-modifier shape)
|
||||
frame-ds-modifier (:displacement-modifier frame)
|
||||
rt-modifier (:rotation-modifier shape)]
|
||||
(cond-> shape
|
||||
(gmt/matrix? rz-modifier) (transform rz-modifier)
|
||||
frame (move (gpt/point (- (:x frame)) (- (:y frame))))
|
||||
(gmt/matrix? frame-ds-modifier) (transform frame-ds-modifier)
|
||||
(gmt/matrix? ds-modifier) (transform ds-modifier)
|
||||
rt-modifier (update :rotation #(+ (or % 0) rt-modifier))))))
|
||||
(let [new-shape (case (:type shape)
|
||||
:path (transform-path-shape shape)
|
||||
:curve (transform-path-shape shape)
|
||||
(transform-rect-shape shape))]
|
||||
(-> new-shape
|
||||
(translate-to-frame frame)
|
||||
(update :rotation #(mod (+ % (get-in shape [:modifiers :rotation] 0)) 360))
|
||||
(dissoc :modifiers)))))
|
||||
|
||||
|
||||
(defn transform-matrix
|
||||
"Returns a transformation matrix without changing the shape properties.
|
||||
The result should be used in a `transform` attribute in svg"
|
||||
([{:keys [x y] :as shape}]
|
||||
(let [shape-center (center shape)]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate shape-center)
|
||||
(gmt/multiply (:transform shape (gmt/matrix)))
|
||||
(gmt/translate (gpt/negate shape-center))))))
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
(defn- load
|
||||
[alias]
|
||||
(if (= *target* "nodejs")
|
||||
(if (or (= *target* "nodejs") (not (exists? js/window)))
|
||||
{}
|
||||
(let [data (.getItem js/localStorage (name alias))]
|
||||
(if data
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.worker.impl :as impl]
|
||||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.util.quadtree :as qdt]))
|
||||
|
||||
(defonce state (l/atom {}))
|
||||
|
@ -92,10 +92,8 @@
|
|||
|
||||
(defn- resolve-object
|
||||
[state {:keys [id] :as item}]
|
||||
(let [item (-> item
|
||||
(geom/shape->rect-shape)
|
||||
(geom/resolve-rotation)
|
||||
(geom/shape->rect-shape))
|
||||
(let [selection-rect (geom/selection-rect-shape item)
|
||||
item (merge item (select-keys selection-rect [:x :y :width :height]))
|
||||
width (+ (:x item 0) (:width item 0))
|
||||
height (+ (:y item 0) (:height item 0))
|
||||
max (fnil max 0)]
|
||||
|
|
Loading…
Add table
Reference in a new issue