1
.gitignore
vendored
|
@ -30,3 +30,4 @@ node_modules
|
|||
/media
|
||||
/deploy
|
||||
/web
|
||||
/_dump
|
||||
|
|
71
common/app/common/attrs.cljc
Normal file
|
@ -0,0 +1,71 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.common.attrs)
|
||||
|
||||
(defn get-attrs-multi
|
||||
[shapes attrs]
|
||||
;; Extract some attributes of a list of shapes.
|
||||
;; For each attribute, if the value is the same in all shapes,
|
||||
;; wll take this value. If there is any shape that is different,
|
||||
;; the value of the attribute will be the keyword :multiple.
|
||||
;;
|
||||
;; If some shape has the value nil in any attribute, it's
|
||||
;; considered a different value. If the shape does not contain
|
||||
;; the attribute, it's ignored in the final result.
|
||||
;;
|
||||
;; Example:
|
||||
;; (def shapes [{:stroke-color "#ff0000"
|
||||
;; :stroke-width 3
|
||||
;; :fill-color "#0000ff"
|
||||
;; :x 1000 :y 2000 :rx nil}
|
||||
;; {:stroke-width "#ff0000"
|
||||
;; :stroke-width 5
|
||||
;; :x 1500 :y 2000}])
|
||||
;;
|
||||
;; (get-attrs-multi shapes [:stroke-color
|
||||
;; :stroke-width
|
||||
;; :fill-color
|
||||
;; :rx
|
||||
;; :ry])
|
||||
;; >>> {:stroke-color "#ff0000"
|
||||
;; :stroke-width :multiple
|
||||
;; :fill-color "#0000ff"
|
||||
;; :rx nil
|
||||
;; :ry nil}
|
||||
;;
|
||||
(let [defined-shapes (filter some? shapes)
|
||||
|
||||
combine-value (fn [v1 v2] (cond
|
||||
(= v1 v2) v1
|
||||
(= v1 :undefined) v2
|
||||
(= v2 :undefined) v1
|
||||
:else :multiple))
|
||||
|
||||
combine-values (fn [attrs shape values]
|
||||
(map #(combine-value (get shape % :undefined)
|
||||
(get values % :undefined)) attrs))
|
||||
|
||||
select-attrs (fn [shape attrs]
|
||||
(zipmap attrs (map #(get shape % :undefined) attrs)))
|
||||
|
||||
reducer (fn [result shape]
|
||||
(zipmap attrs (combine-values attrs shape result)))
|
||||
|
||||
combined (reduce reducer
|
||||
(select-attrs (first defined-shapes) attrs)
|
||||
(rest defined-shapes))
|
||||
|
||||
cleanup-value (fn [value]
|
||||
(if (= value :undefined) nil value))
|
||||
|
||||
cleanup (fn [result]
|
||||
(zipmap attrs (map #(cleanup-value (get result %)) attrs)))]
|
||||
|
||||
(cleanup combined)))
|
|
@ -7,12 +7,14 @@
|
|||
(ns app.common.data
|
||||
"Data manipulation and query helper functions."
|
||||
(:refer-clojure :exclude [concat read-string hash-map])
|
||||
(:require [clojure.set :as set]
|
||||
[linked.set :as lks]
|
||||
#?(:cljs [cljs.reader :as r]
|
||||
:clj [clojure.edn :as r])
|
||||
#?(:cljs [cljs.core :as core]
|
||||
:clj [clojure.core :as core]))
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[linked.set :as lks]
|
||||
[app.common.math :as mth]
|
||||
#?(:cljs [cljs.reader :as r]
|
||||
:clj [clojure.edn :as r])
|
||||
#?(:cljs [cljs.core :as core]
|
||||
:clj [clojure.core :as core]))
|
||||
#?(:clj
|
||||
(:import linked.set.LinkedSet)))
|
||||
|
||||
|
@ -261,3 +263,21 @@
|
|||
(defn coalesce
|
||||
[val default]
|
||||
(or val default))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Parsing / Conversion
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defn nilf
|
||||
"Returns a new function that if you pass nil as any argument will
|
||||
return nil"
|
||||
[f]
|
||||
(fn [& args]
|
||||
(if (some nil? args)
|
||||
nil
|
||||
(apply f args))))
|
||||
|
||||
(defn check-num
|
||||
"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))
|
||||
|
|
151
common/app/common/geom/align.cljc
Normal file
|
@ -0,0 +1,151 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.common.geom.align
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.data :as d]))
|
||||
|
||||
;; --- Alignment
|
||||
|
||||
(s/def ::align-axis #{:hleft :hcenter :hright :vtop :vcenter :vbottom})
|
||||
|
||||
(declare calc-align-pos)
|
||||
|
||||
;; Duplicated from pages-helpers to remove cyclic dependencies
|
||||
(defn- get-children [id objects]
|
||||
(let [shapes (vec (get-in objects [id :shapes]))]
|
||||
(if shapes
|
||||
(d/concat shapes (mapcat #(get-children % objects) shapes))
|
||||
[])))
|
||||
|
||||
(defn- recursive-move
|
||||
"Move the shape and all its recursive children."
|
||||
[shape dpoint objects]
|
||||
(let [children-ids (get-children (:id shape) objects)
|
||||
children (map #(get objects %) children-ids)]
|
||||
(map #(gsh/move % dpoint) (cons shape children))))
|
||||
|
||||
(defn align-to-rect
|
||||
"Move the shape so that it is aligned with the given rectangle
|
||||
in the given axis. Take account the form of the shape and the
|
||||
possible rotation. What is aligned is the rectangle that wraps
|
||||
the shape with the given rectangle. If the shape is a group,
|
||||
move also all of its recursive children."
|
||||
[shape rect axis objects]
|
||||
(let [wrapper-rect (gsh/selection-rect [shape])
|
||||
align-pos (calc-align-pos wrapper-rect rect axis)
|
||||
delta {:x (- (:x align-pos) (:x wrapper-rect))
|
||||
:y (- (:y align-pos) (:y wrapper-rect))}]
|
||||
(recursive-move shape delta objects)))
|
||||
|
||||
(defn calc-align-pos
|
||||
[wrapper-rect rect axis]
|
||||
(case axis
|
||||
:hleft (let [left (:x rect)]
|
||||
{:x left
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:hcenter (let [center (+ (:x rect) (/ (:width rect) 2))]
|
||||
{:x (- center (/ (:width wrapper-rect) 2))
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:hright (let [right (+ (:x rect) (:width rect))]
|
||||
{:x (- right (:width wrapper-rect))
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:vtop (let [top (:y rect)]
|
||||
{:x (:x wrapper-rect)
|
||||
:y top})
|
||||
|
||||
:vcenter (let [center (+ (:y rect) (/ (:height rect) 2))]
|
||||
{:x (:x wrapper-rect)
|
||||
:y (- center (/ (:height wrapper-rect) 2))})
|
||||
|
||||
:vbottom (let [bottom (+ (:y rect) (:height rect))]
|
||||
{:x (:x wrapper-rect)
|
||||
:y (- bottom (:height wrapper-rect))})))
|
||||
|
||||
;; --- Distribute
|
||||
|
||||
(s/def ::dist-axis #{:horizontal :vertical})
|
||||
|
||||
(defn distribute-space
|
||||
"Distribute equally the space between shapes in the given axis. If
|
||||
there is no space enough, it does nothing. It takes into account
|
||||
the form of the shape and the rotation, what is distributed is
|
||||
the wrapping recangles of the shapes. If any shape is a group,
|
||||
move also all of its recursive children."
|
||||
[shapes axis objects]
|
||||
(let [coord (if (= axis :horizontal) :x :y)
|
||||
other-coord (if (= axis :horizontal) :y :x)
|
||||
size (if (= axis :horizontal) :width :height)
|
||||
; The rectangle that wraps the whole selection
|
||||
wrapper-rect (gsh/selection-rect shapes)
|
||||
; Sort shapes by the center point in the given axis
|
||||
sorted-shapes (sort-by #(coord (gsh/center-shape %)) shapes)
|
||||
; Each shape wrapped in its own rectangle
|
||||
wrapped-shapes (map #(gsh/selection-rect [%]) sorted-shapes)
|
||||
; The total space between shapes
|
||||
space (reduce - (size wrapper-rect) (map size wrapped-shapes))]
|
||||
|
||||
(if (<= space 0)
|
||||
shapes
|
||||
(let [unit-space (/ space (- (count wrapped-shapes) 1))
|
||||
; Calculate the distance we need to move each shape.
|
||||
; The new position of each one is the position of the
|
||||
; previous one plus its size plus the unit space.
|
||||
deltas (loop [shapes' wrapped-shapes
|
||||
start-pos (coord wrapper-rect)
|
||||
deltas []]
|
||||
|
||||
(let [first-shape (first shapes')
|
||||
delta (- start-pos (coord first-shape))
|
||||
new-pos (+ start-pos (size first-shape) unit-space)]
|
||||
|
||||
(if (= (count shapes') 1)
|
||||
(conj deltas delta)
|
||||
(recur (rest shapes')
|
||||
new-pos
|
||||
(conj deltas delta)))))]
|
||||
|
||||
(mapcat #(recursive-move %1 {coord %2 other-coord 0} objects)
|
||||
sorted-shapes deltas)))))
|
||||
|
||||
;; Adjusto to viewport
|
||||
|
||||
(defn adjust-to-viewport
|
||||
([viewport srect] (adjust-to-viewport viewport srect nil))
|
||||
([viewport srect {:keys [padding] :or {padding 0}}]
|
||||
(let [gprop (/ (:width viewport) (:height viewport))
|
||||
srect (-> srect
|
||||
(update :x #(- % padding))
|
||||
(update :y #(- % padding))
|
||||
(update :width #(+ % padding padding))
|
||||
(update :height #(+ % padding padding)))
|
||||
width (:width srect)
|
||||
height (:height srect)
|
||||
lprop (/ width height)]
|
||||
(cond
|
||||
(> gprop lprop)
|
||||
(let [width' (* (/ width lprop) gprop)
|
||||
padding (/ (- width' width) 2)]
|
||||
(-> srect
|
||||
(update :x #(- % padding))
|
||||
(assoc :width width')))
|
||||
|
||||
(< gprop lprop)
|
||||
(let [height' (/ (* height lprop) gprop)
|
||||
padding (/ (- height' height) 2)]
|
||||
(-> srect
|
||||
(update :y #(- % padding))
|
||||
(assoc :height height')))
|
||||
|
||||
:else srect))))
|
|
@ -121,3 +121,13 @@
|
|||
([m angle-x angle-y p]
|
||||
(multiply m (skew-matrix angle-x angle-y p))))
|
||||
|
||||
(defn m-equal [m1 m2 threshold]
|
||||
(let [th-eq (fn [a b] (<= (mth/abs (- a b)) threshold))
|
||||
{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f} m1
|
||||
{m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f} m2]
|
||||
(and (th-eq m1a m2a)
|
||||
(th-eq m1b m2b)
|
||||
(th-eq m1c m2c)
|
||||
(th-eq m1d m2d)
|
||||
(th-eq m1e m2e)
|
||||
(th-eq m1f m2f))))
|
||||
|
|
|
@ -26,6 +26,14 @@
|
|||
[v]
|
||||
(instance? Point v))
|
||||
|
||||
(defn ^boolean point-like?
|
||||
[{:keys [x y] :as v}]
|
||||
(and (map? v)
|
||||
(not (nil? x))
|
||||
(not (nil? y))
|
||||
(number? x)
|
||||
(number? y)))
|
||||
|
||||
(defn point
|
||||
"Create a Point instance."
|
||||
([] (Point. 0 0))
|
||||
|
@ -37,9 +45,15 @@
|
|||
(number? v)
|
||||
(Point. v v)
|
||||
|
||||
(point-like? v)
|
||||
(Point. (:x v) (:y v))
|
||||
|
||||
:else
|
||||
(throw (ex-info "Invalid arguments" {:v v}))))
|
||||
([x y] (Point. x y)))
|
||||
([x y]
|
||||
;;(assert (not (nil? x)))
|
||||
;;(assert (not (nil? y)))
|
||||
(Point. x y)))
|
||||
|
||||
(defn add
|
||||
"Returns the addition of the supplied value to both
|
||||
|
|
62
common/app/common/geom/proportions.cljc
Normal file
|
@ -0,0 +1,62 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.common.geom.proportions
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[app.common.spec :as us]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.geom.shapes.transforms :as gtr]
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.math :as mth]
|
||||
[app.common.data :as d]))
|
||||
|
||||
;; --- Proportions
|
||||
|
||||
(declare assign-proportions-path)
|
||||
(declare assign-proportions-rect)
|
||||
|
||||
(defn assign-proportions
|
||||
[{:keys [type] :as shape}]
|
||||
(case type
|
||||
:path (assign-proportions-path shape)
|
||||
(assign-proportions-rect shape)))
|
||||
|
||||
(defn- assign-proportions-rect
|
||||
[{:keys [width height] :as shape}]
|
||||
(assoc shape :proportion (/ width height)))
|
||||
|
||||
|
||||
;; --- Setup Proportions
|
||||
|
||||
(declare setup-proportions-const)
|
||||
(declare setup-proportions-image)
|
||||
|
||||
(defn setup-proportions
|
||||
[shape]
|
||||
(case (:type shape)
|
||||
:icon (setup-proportions-image shape)
|
||||
:image (setup-proportions-image shape)
|
||||
:text shape
|
||||
(setup-proportions-const shape)))
|
||||
|
||||
(defn setup-proportions-image
|
||||
[{:keys [metadata] :as shape}]
|
||||
(let [{:keys [width height]} metadata]
|
||||
(assoc shape
|
||||
:proportion (/ width height)
|
||||
:proportion-lock false)))
|
||||
|
||||
(defn setup-proportions-const
|
||||
[shape]
|
||||
(assoc shape
|
||||
:proportion 1
|
||||
:proportion-lock false))
|
|
@ -13,63 +13,24 @@
|
|||
[app.common.spec :as us]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.geom.shapes.transforms :as gtr]
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.math :as mth]
|
||||
[app.common.data :as d]))
|
||||
|
||||
(defn- nilf
|
||||
"Returns a new function that if you pass nil as any argument will
|
||||
return nil"
|
||||
[f]
|
||||
(fn [& args]
|
||||
(if (some nil? args)
|
||||
nil
|
||||
(apply f args))))
|
||||
|
||||
;; --- Relative Movement
|
||||
|
||||
(declare move-rect)
|
||||
(declare move-path)
|
||||
|
||||
(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 {dx :x dy :y}]
|
||||
(let [inc-x (nilf (fn [x] (+ (-chk x) (-chk dx))))
|
||||
inc-y (nilf (fn [y] (+ (-chk y) (-chk dy))))
|
||||
inc-point (nilf (fn [p] (-> p
|
||||
(update :x inc-x)
|
||||
(update :y inc-y))))]
|
||||
(let [dx (d/check-num dx)
|
||||
dy (d/check-num dy)]
|
||||
(-> shape
|
||||
(update :x inc-x)
|
||||
(update :y inc-y)
|
||||
(update-in [:selrect :x] inc-x)
|
||||
(update-in [:selrect :x1] inc-x)
|
||||
(update-in [:selrect :x2] inc-x)
|
||||
(update-in [:selrect :y] inc-y)
|
||||
(update-in [:selrect :y1] inc-y)
|
||||
(update-in [:selrect :y2] inc-y)
|
||||
(update :points #(mapv inc-point %))
|
||||
(update :segments #(mapv inc-point %)))))
|
||||
|
||||
;; Duplicated from pages-helpers to remove cyclic dependencies
|
||||
(defn get-children [id objects]
|
||||
(let [shapes (vec (get-in objects [id :shapes]))]
|
||||
(if shapes
|
||||
(d/concat shapes (mapcat #(get-children % objects) shapes))
|
||||
[])))
|
||||
|
||||
(defn recursive-move
|
||||
"Move the shape and all its recursive children."
|
||||
[shape dpoint objects]
|
||||
(let [children-ids (get-children (:id shape) objects)
|
||||
children (map #(get objects %) children-ids)]
|
||||
(map #(move % dpoint) (cons shape children))))
|
||||
(assoc-in [:modifiers :displacement] (gmt/translate-matrix (gpt/point dx dy)))
|
||||
(gtr/transform-shape))))
|
||||
|
||||
;; --- Absolute Movement
|
||||
|
||||
|
@ -77,116 +38,32 @@
|
|||
|
||||
(defn absolute-move
|
||||
"Move the shape to the exactly specified position."
|
||||
[shape position]
|
||||
(case (:type shape)
|
||||
(:curve :path) 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 (- (-chk x) (-chk (:x shape))) 0)
|
||||
dy (if y (- (-chk y) (-chk (:y shape))) 0)]
|
||||
[shape {:keys [x y]}]
|
||||
(let [dx (- (d/check-num x) (-> shape :selrect :x))
|
||||
dy (- (d/check-num y) (-> shape :selrect :y))]
|
||||
(move shape (gpt/point dx dy))))
|
||||
|
||||
;; --- Center
|
||||
|
||||
(declare center-rect)
|
||||
(declare center-path)
|
||||
|
||||
(defn center
|
||||
"Calculate the center of the shape."
|
||||
[shape]
|
||||
(case (:type shape)
|
||||
:curve (center-path shape)
|
||||
:path (center-path shape)
|
||||
(center-rect shape)))
|
||||
|
||||
(defn- center-rect
|
||||
[{:keys [x y width height] :as shape}]
|
||||
(gpt/point (+ x (/ width 2)) (+ y (/ height 2))))
|
||||
|
||||
(defn- center-path
|
||||
[{: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-rect)
|
||||
|
||||
(defn assign-proportions
|
||||
[{:keys [type] :as shape}]
|
||||
(case type
|
||||
:path (assign-proportions-path shape)
|
||||
(assign-proportions-rect shape)))
|
||||
|
||||
(defn- assign-proportions-rect
|
||||
[{:keys [width height] :as shape}]
|
||||
(assoc shape :proportion (/ width height)))
|
||||
|
||||
;; --- Paths
|
||||
|
||||
(defn update-path-point
|
||||
"Update a concrete point in the path.
|
||||
|
||||
The point should exists before, this function
|
||||
does not adds it automatically."
|
||||
[shape index point]
|
||||
(assoc-in shape [:segments index] point))
|
||||
|
||||
;; --- Setup Proportions
|
||||
|
||||
(declare setup-proportions-const)
|
||||
(declare setup-proportions-image)
|
||||
|
||||
(defn setup-proportions
|
||||
[shape]
|
||||
(case (:type shape)
|
||||
:icon (setup-proportions-image shape)
|
||||
:image (setup-proportions-image shape)
|
||||
:text shape
|
||||
(setup-proportions-const shape)))
|
||||
|
||||
(defn setup-proportions-image
|
||||
[{:keys [metadata] :as shape}]
|
||||
(let [{:keys [width height]} metadata]
|
||||
(assoc shape
|
||||
:proportion (/ width height)
|
||||
:proportion-lock false)))
|
||||
|
||||
(defn setup-proportions-const
|
||||
[shape]
|
||||
(assoc shape
|
||||
:proportion 1
|
||||
:proportion-lock false))
|
||||
|
||||
;; --- Resize (Dimensions)
|
||||
|
||||
;; Fixme: Improve using modifiers instead of calculating the selrect/points
|
||||
(defn resize
|
||||
[shape width height]
|
||||
(us/assert map? shape)
|
||||
(us/assert number? width)
|
||||
(us/assert number? height)
|
||||
(-> shape
|
||||
(assoc :width width :height height)
|
||||
(update :selrect (fn [selrect]
|
||||
(assoc selrect
|
||||
:x2 (+ (:x1 selrect) width)
|
||||
:y2 (+ (:y1 selrect) height))))))
|
||||
(let [selrect (-> (:selrect shape)
|
||||
(assoc :width width)
|
||||
(assoc :height height)
|
||||
(assoc :x2 (+ (-> shape :selrect :x1) width))
|
||||
(assoc :y2 (+ (-> shape :selrect :y1) height)))
|
||||
|
||||
center (gco/center-selrect selrect)
|
||||
points (-> selrect gpr/rect->points (gtr/transform-points center (:transform shape)))]
|
||||
|
||||
(-> shape
|
||||
(assoc :width width)
|
||||
(assoc :height height)
|
||||
(assoc :selrect selrect)
|
||||
(assoc :points points))))
|
||||
|
||||
(defn resize-rect
|
||||
[shape attr value]
|
||||
|
@ -207,31 +84,21 @@
|
|||
(resize shape (:width new-size) (:height new-size))))
|
||||
|
||||
;; --- Setup (Initialize)
|
||||
|
||||
(declare setup-rect)
|
||||
(declare setup-image)
|
||||
|
||||
(defn setup
|
||||
"A function that initializes the first coordinates for
|
||||
the shape. Used mainly for draw operations."
|
||||
[shape props]
|
||||
(case (:type shape)
|
||||
:image (setup-image shape props)
|
||||
(setup-rect shape props)))
|
||||
|
||||
(declare shape->points)
|
||||
(declare points->selrect)
|
||||
;; FIXME: Is this the correct place for these functions?
|
||||
|
||||
(defn- setup-rect
|
||||
"A specialized function for setup rect-like shapes."
|
||||
[shape {:keys [x y width height]}]
|
||||
(as-> shape $
|
||||
(assoc $ :x x
|
||||
:y y
|
||||
:width width
|
||||
:height height)
|
||||
(assoc $ :points (shape->points $))
|
||||
(assoc $ :selrect (points->selrect (:points $)))))
|
||||
(let [rect {:x x :y y :width width :height height}
|
||||
points (gpr/rect->points rect)
|
||||
selrect (gpr/points->selrect points)]
|
||||
(assoc shape
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:points points
|
||||
:selrect selrect)))
|
||||
|
||||
(defn- setup-image
|
||||
[{:keys [metadata] :as shape} {:keys [x y width height] :as props}]
|
||||
|
@ -241,157 +108,13 @@
|
|||
(:height metadata))
|
||||
:proportion-lock true)))
|
||||
|
||||
;; --- Coerce to Rect-like shape.
|
||||
|
||||
(declare path->rect-shape)
|
||||
(declare group->rect-shape)
|
||||
(declare rect->rect-shape)
|
||||
|
||||
;; TODO: completly remove
|
||||
|
||||
(defn shape->rect-shape
|
||||
"Coerce shape to rect like shape."
|
||||
|
||||
[{:keys [type] :as shape}]
|
||||
(case type
|
||||
(:curve :path) (path->rect-shape shape)
|
||||
(rect->rect-shape shape)))
|
||||
|
||||
;; -- Points
|
||||
|
||||
(declare transform-shape-point)
|
||||
|
||||
(defn shape->points [shape]
|
||||
(let [points (case (:type shape)
|
||||
(:curve :path) (:segments shape)
|
||||
(let [{:keys [x y width height]} shape]
|
||||
[(gpt/point x y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point x (+ y height))]))]
|
||||
(->> points
|
||||
(map #(transform-shape-point % shape (:transform shape (gmt/matrix))))
|
||||
(map gpt/round)
|
||||
(vec))))
|
||||
|
||||
(defn points->selrect [points]
|
||||
(let [minx (transduce (map :x) min ##Inf points)
|
||||
miny (transduce (map :y) min ##Inf points)
|
||||
maxx (transduce (map :x) max ##-Inf points)
|
||||
maxy (transduce (map :y) max ##-Inf points)]
|
||||
{:x1 minx
|
||||
:y1 miny
|
||||
:x2 maxx
|
||||
:y2 maxy
|
||||
:x minx
|
||||
:y miny
|
||||
:width (- maxx minx)
|
||||
:height (- maxy miny)
|
||||
:type :rect}))
|
||||
|
||||
;; Shape->PATH
|
||||
|
||||
(declare rect->path)
|
||||
|
||||
(defn shape->path
|
||||
[shape]
|
||||
(defn setup
|
||||
"A function that initializes the first coordinates for
|
||||
the shape. Used mainly for draw operations."
|
||||
[shape props]
|
||||
(case (:type shape)
|
||||
(:curve :path) 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
|
||||
:x1 x
|
||||
:y1 y
|
||||
:x2 (+ x width)
|
||||
:y2 (+ y height)))
|
||||
|
||||
(defn- path->rect-shape
|
||||
[{:keys [segments] :as shape}]
|
||||
(merge shape
|
||||
{:type :rect}
|
||||
(:selrect shape)))
|
||||
|
||||
;; --- Resolve Shape
|
||||
|
||||
(declare resolve-rect-shape)
|
||||
(declare translate-from-frame)
|
||||
(declare translate-to-frame)
|
||||
|
||||
(defn resolve-shape
|
||||
[objects shape]
|
||||
(case (:type shape)
|
||||
:rect (resolve-rect-shape objects shape)
|
||||
:group (resolve-rect-shape objects shape)
|
||||
:frame (resolve-rect-shape objects shape)))
|
||||
|
||||
(defn- resolve-rect-shape
|
||||
[objects {:keys [parent] :as shape}]
|
||||
(loop [pobj (get objects parent)]
|
||||
(if (= :frame (:type pobj))
|
||||
(translate-from-frame shape pobj)
|
||||
(recur (get objects (:parent pobj))))))
|
||||
|
||||
;; --- Transform Shape
|
||||
|
||||
(declare transform-rect)
|
||||
(declare transform-path)
|
||||
|
||||
(defn transform
|
||||
"Apply the matrix transformation to shape."
|
||||
[{:keys [type] :as shape} xfmt]
|
||||
(if (gmt/matrix? xfmt)
|
||||
(case type
|
||||
:path (transform-path shape xfmt)
|
||||
:curve (transform-path shape xfmt)
|
||||
(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)
|
||||
tr (gpt/transform (gpt/point (+ x width) y) mx)
|
||||
bl (gpt/transform (gpt/point x (+ y height)) mx)
|
||||
br (gpt/transform (gpt/point (+ x width) (+ y height)) mx)
|
||||
;; TODO: replace apply with transduce (performance)
|
||||
minx (apply min (map :x [tl tr bl br]))
|
||||
maxx (apply max (map :x [tl tr bl br]))
|
||||
miny (apply min (map :y [tl tr bl br]))
|
||||
maxy (apply max (map :y [tl tr bl br]))]
|
||||
(assoc shape
|
||||
:x minx
|
||||
:y miny
|
||||
:width (- maxx minx)
|
||||
:height (- maxy miny))))
|
||||
|
||||
(defn- transform-path
|
||||
[{:keys [segments] :as shape} xfmt]
|
||||
(let [segments (mapv #(gpt/transform % xfmt) segments)]
|
||||
(assoc shape :segments segments)))
|
||||
:image (setup-image shape props)
|
||||
(setup-rect shape props)))
|
||||
|
||||
;; --- Outer Rect
|
||||
|
||||
|
@ -399,24 +122,10 @@
|
|||
"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 (map :selrect shapes)
|
||||
minx (transduce (map :x1) min ##Inf shapes)
|
||||
miny (transduce (map :y1) min ##Inf shapes)
|
||||
maxx (transduce (map :x2) max ##-Inf shapes)
|
||||
maxy (transduce (map :y2) max ##-Inf shapes)]
|
||||
{:x1 minx
|
||||
:y1 miny
|
||||
:x2 maxx
|
||||
:y2 maxy
|
||||
:x minx
|
||||
:y miny
|
||||
:width (- maxx minx)
|
||||
:height (- maxy miny)
|
||||
:points [(gpt/point minx miny)
|
||||
(gpt/point maxx miny)
|
||||
(gpt/point maxx maxy)
|
||||
(gpt/point minx maxy)]
|
||||
:type :rect}))
|
||||
(->> shapes
|
||||
(gtr/transform-shape)
|
||||
(map (comp gpr/points->selrect :points))
|
||||
(gpr/join-selrects)))
|
||||
|
||||
(defn translate-to-frame
|
||||
[shape {:keys [x y] :as frame}]
|
||||
|
@ -426,117 +135,26 @@
|
|||
[shape {:keys [x y] :as frame}]
|
||||
(move shape (gpt/point x y)))
|
||||
|
||||
;; --- Alignment
|
||||
|
||||
(s/def ::align-axis #{:hleft :hcenter :hright :vtop :vcenter :vbottom})
|
||||
|
||||
(declare calc-align-pos)
|
||||
|
||||
(defn align-to-rect
|
||||
"Move the shape so that it is aligned with the given rectangle
|
||||
in the given axis. Take account the form of the shape and the
|
||||
possible rotation. What is aligned is the rectangle that wraps
|
||||
the shape with the given rectangle. If the shape is a group,
|
||||
move also all of its recursive children."
|
||||
[shape rect axis objects]
|
||||
(let [wrapper-rect (selection-rect [shape])
|
||||
align-pos (calc-align-pos wrapper-rect rect axis)
|
||||
delta {:x (- (:x align-pos) (:x wrapper-rect))
|
||||
:y (- (:y align-pos) (:y wrapper-rect))}]
|
||||
(recursive-move shape delta objects)))
|
||||
|
||||
(defn calc-align-pos
|
||||
[wrapper-rect rect axis]
|
||||
(case axis
|
||||
:hleft (let [left (:x rect)]
|
||||
{:x left
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:hcenter (let [center (+ (:x rect) (/ (:width rect) 2))]
|
||||
{:x (- center (/ (:width wrapper-rect) 2))
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:hright (let [right (+ (:x rect) (:width rect))]
|
||||
{:x (- right (:width wrapper-rect))
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:vtop (let [top (:y rect)]
|
||||
{:x (:x wrapper-rect)
|
||||
:y top})
|
||||
|
||||
:vcenter (let [center (+ (:y rect) (/ (:height rect) 2))]
|
||||
{:x (:x wrapper-rect)
|
||||
:y (- center (/ (:height wrapper-rect) 2))})
|
||||
|
||||
:vbottom (let [bottom (+ (:y rect) (:height rect))]
|
||||
{:x (:x wrapper-rect)
|
||||
:y (- bottom (:height wrapper-rect))})))
|
||||
|
||||
;; --- Distribute
|
||||
|
||||
(s/def ::dist-axis #{:horizontal :vertical})
|
||||
|
||||
(defn distribute-space
|
||||
"Distribute equally the space between shapes in the given axis. If
|
||||
there is no space enough, it does nothing. It takes into account
|
||||
the form of the shape and the rotation, what is distributed is
|
||||
the wrapping recangles of the shapes. If any shape is a group,
|
||||
move also all of its recursive children."
|
||||
[shapes axis objects]
|
||||
(let [coord (if (= axis :horizontal) :x :y)
|
||||
other-coord (if (= axis :horizontal) :y :x)
|
||||
size (if (= axis :horizontal) :width :height)
|
||||
; The rectangle that wraps the whole selection
|
||||
wrapper-rect (selection-rect shapes)
|
||||
; Sort shapes by the center point in the given axis
|
||||
sorted-shapes (sort-by #(coord (center %)) shapes)
|
||||
; Each shape wrapped in its own rectangle
|
||||
wrapped-shapes (map #(selection-rect [%]) sorted-shapes)
|
||||
; The total space between shapes
|
||||
space (reduce - (size wrapper-rect) (map size wrapped-shapes))]
|
||||
|
||||
(if (<= space 0)
|
||||
shapes
|
||||
(let [unit-space (/ space (- (count wrapped-shapes) 1))
|
||||
; Calculate the distance we need to move each shape.
|
||||
; The new position of each one is the position of the
|
||||
; previous one plus its size plus the unit space.
|
||||
deltas (loop [shapes' wrapped-shapes
|
||||
start-pos (coord wrapper-rect)
|
||||
deltas []]
|
||||
|
||||
(let [first-shape (first shapes')
|
||||
delta (- start-pos (coord first-shape))
|
||||
new-pos (+ start-pos (size first-shape) unit-space)]
|
||||
|
||||
(if (= (count shapes') 1)
|
||||
(conj deltas delta)
|
||||
(recur (rest shapes')
|
||||
new-pos
|
||||
(conj deltas delta)))))]
|
||||
|
||||
(mapcat #(recursive-move %1 {coord %2 other-coord 0} objects)
|
||||
sorted-shapes deltas)))))
|
||||
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn contained-in?
|
||||
"Check if a shape is contained in the
|
||||
provided selection rect."
|
||||
[shape selrect]
|
||||
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (shape->rect-shape selrect)
|
||||
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (shape->rect-shape shape)]
|
||||
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} selrect
|
||||
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (:selrect shape)]
|
||||
(and (neg? (- sy1 ry1))
|
||||
(neg? (- sx1 rx1))
|
||||
(pos? (- sy2 ry2))
|
||||
(pos? (- sx2 rx2)))))
|
||||
|
||||
;; TODO: This not will work for rotated shapes
|
||||
(defn overlaps?
|
||||
"Check if a shape overlaps with provided selection rect."
|
||||
[shape selrect]
|
||||
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (shape->rect-shape selrect)
|
||||
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (shape->rect-shape shape)]
|
||||
[shape rect]
|
||||
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (gpr/rect->selrect rect)
|
||||
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (gpr/points->selrect (:points shape))]
|
||||
|
||||
(and (< rx1 sx2)
|
||||
(> rx2 sx1)
|
||||
(< ry1 sy2)
|
||||
|
@ -564,43 +182,6 @@
|
|||
: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 pad-selrec
|
||||
([selrect] (pad-selrec selrect 1))
|
||||
([selrect size]
|
||||
|
@ -658,310 +239,29 @@
|
|||
(and (>= s1c1 s2c1) (<= s1c1 s2c2))
|
||||
(and (>= s1c2 s2c1) (<= s1c2 s2c2)))))
|
||||
|
||||
(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))
|
||||
{res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1))
|
||||
|
||||
;; Normalize x/y vector coordinates because scale by 0 is infinite
|
||||
res-x (cond
|
||||
(and (< res-x 0) (> res-x -0.01)) -0.01
|
||||
(and (>= res-x 0) (< res-x 0.01)) 0.01
|
||||
:else res-x)
|
||||
|
||||
res-y (cond
|
||||
(and (< res-y 0) (> res-y -0.01)) -0.01
|
||||
(and (>= res-y 0) (< res-y 0.01)) 0.01
|
||||
:else res-y)
|
||||
|
||||
resize (gpt/point res-x res-y)
|
||||
|
||||
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 (or (: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 fix-invalid-rect-values
|
||||
[rect-shape]
|
||||
(letfn [(check [num]
|
||||
(if (or (nil? num) (mth/nan? num) (= ##Inf num) (= ##-Inf num)) 0 num))
|
||||
(to-positive [num] (if (< num 1) 1 num))]
|
||||
(-> rect-shape
|
||||
(update :x check)
|
||||
(update :y check)
|
||||
(update :width (comp to-positive check))
|
||||
(update :height (comp to-positive check)))))
|
||||
|
||||
(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))
|
||||
(update :x #(if (zero? %) 1 %))
|
||||
(update :y #(if (zero? %) 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 (fix-invalid-rect-values rec)
|
||||
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 )
|
||||
skew-angle (if (mth/nan? skew-angle) 0 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))
|
||||
h3 (/ h1 h2)
|
||||
h3 (if (mth/nan? h3) 1 h3)
|
||||
|
||||
stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point 1 h3)))
|
||||
|
||||
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 h3))
|
||||
(gmt/skew (- skew-angle) 0)
|
||||
(gmt/rotate (- rotation-angle)))
|
||||
|
||||
|
||||
new-shape (as-> shape $
|
||||
(merge $ rec)
|
||||
(update $ :x #(mth/precision % 0))
|
||||
(update $ :y #(mth/precision % 0))
|
||||
(update $ :width #(mth/precision % 0))
|
||||
(update $ :height #(mth/precision % 0))
|
||||
(update $ :transform #(gmt/multiply (or % (gmt/matrix)) stretch-matrix))
|
||||
(update $ :transform-inverse #(gmt/multiply stretch-matrix-inverse (or % (gmt/matrix))))
|
||||
(assoc $ :points (shape->points $))
|
||||
(assoc $ :selrect (points->selrect (:points $)))
|
||||
(update $ :selrect fix-invalid-rect-values)
|
||||
(update $ :rotation #(mod (+ (or % 0)
|
||||
(or (get-in $ [:modifiers :rotation]) 0)) 360)))]
|
||||
new-shape))
|
||||
|
||||
(declare update-path-selrect)
|
||||
(defn transform-path-shape
|
||||
[shape]
|
||||
(-> shape
|
||||
transform-apply-modifiers
|
||||
update-path-selrect)
|
||||
;; 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 [new-shape
|
||||
(if (:modifiers shape)
|
||||
(-> (case (:type shape)
|
||||
(:curve :path) (transform-path-shape shape)
|
||||
(transform-rect-shape shape))
|
||||
(dissoc :modifiers))
|
||||
shape)]
|
||||
(cond-> new-shape
|
||||
frame (translate-to-frame frame)))))
|
||||
|
||||
|
||||
(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))))))
|
||||
|
||||
(defn update-path-selrect [shape]
|
||||
(as-> shape $
|
||||
(assoc $ :points (shape->points $))
|
||||
(assoc $ :selrect (points->selrect (:points $)))
|
||||
(assoc $ :x (get-in $ [:selrect :x]))
|
||||
(assoc $ :y (get-in $ [:selrect :y]))
|
||||
(assoc $ :width (get-in $ [:selrect :width]))
|
||||
(assoc $ :height (get-in $ [:selrect :height]))))
|
||||
|
||||
(defn adjust-to-viewport
|
||||
([viewport srect] (adjust-to-viewport viewport srect nil))
|
||||
([viewport srect {:keys [padding] :or {padding 0}}]
|
||||
(let [gprop (/ (:width viewport) (:height viewport))
|
||||
srect (-> srect
|
||||
(update :x #(- % padding))
|
||||
(update :y #(- % padding))
|
||||
(update :width #(+ % padding padding))
|
||||
(update :height #(+ % padding padding)))
|
||||
width (:width srect)
|
||||
height (:height srect)
|
||||
lprop (/ width height)]
|
||||
(cond
|
||||
(> gprop lprop)
|
||||
(let [width' (* (/ width lprop) gprop)
|
||||
padding (/ (- width' width) 2)]
|
||||
(-> srect
|
||||
(update :x #(- % padding))
|
||||
(assoc :width width')))
|
||||
|
||||
(< gprop lprop)
|
||||
(let [height' (/ (* height lprop) gprop)
|
||||
padding (/ (- height' height) 2)]
|
||||
(-> srect
|
||||
(update :y #(- % padding))
|
||||
(assoc :height height')))
|
||||
|
||||
:else srect))))
|
||||
|
||||
(defn get-attrs-multi
|
||||
[shapes attrs]
|
||||
;; Extract some attributes of a list of shapes.
|
||||
;; For each attribute, if the value is the same in all shapes,
|
||||
;; wll take this value. If there is any shape that is different,
|
||||
;; the value of the attribute will be the keyword :multiple.
|
||||
;;
|
||||
;; If some shape has the value nil in any attribute, it's
|
||||
;; considered a different value. If the shape does not contain
|
||||
;; the attribute, it's ignored in the final result.
|
||||
;;
|
||||
;; Example:
|
||||
;; (def shapes [{:stroke-color "#ff0000"
|
||||
;; :stroke-width 3
|
||||
;; :fill-color "#0000ff"
|
||||
;; :x 1000 :y 2000 :rx nil}
|
||||
;; {:stroke-width "#ff0000"
|
||||
;; :stroke-width 5
|
||||
;; :x 1500 :y 2000}])
|
||||
;;
|
||||
;; (get-attrs-multi shapes [:stroke-color
|
||||
;; :stroke-width
|
||||
;; :fill-color
|
||||
;; :rx
|
||||
;; :ry])
|
||||
;; >>> {:stroke-color "#ff0000"
|
||||
;; :stroke-width :multiple
|
||||
;; :fill-color "#0000ff"
|
||||
;; :rx nil
|
||||
;; :ry nil}
|
||||
;;
|
||||
(let [defined-shapes (filter some? shapes)
|
||||
|
||||
combine-value (fn [v1 v2] (cond
|
||||
(= v1 v2) v1
|
||||
(= v1 :undefined) v2
|
||||
(= v2 :undefined) v1
|
||||
:else :multiple))
|
||||
|
||||
combine-values (fn [attrs shape values]
|
||||
(map #(combine-value (get shape % :undefined)
|
||||
(get values % :undefined)) attrs))
|
||||
|
||||
select-attrs (fn [shape attrs]
|
||||
(zipmap attrs (map #(get shape % :undefined) attrs)))
|
||||
|
||||
reducer (fn [result shape]
|
||||
(zipmap attrs (combine-values attrs shape result)))
|
||||
|
||||
combined (reduce reducer
|
||||
(select-attrs (first defined-shapes) attrs)
|
||||
(rest defined-shapes))
|
||||
|
||||
cleanup-value (fn [value]
|
||||
(if (= value :undefined) nil value))
|
||||
|
||||
cleanup (fn [result]
|
||||
(zipmap attrs (map #(cleanup-value (get result %)) attrs)))]
|
||||
|
||||
(cleanup combined)))
|
||||
|
||||
|
||||
(defn setup-selrect [{:keys [x y width height] :as shape}]
|
||||
(-> shape
|
||||
(assoc :selrect {:x x :y y
|
||||
:width width :height height
|
||||
:x1 x :y1 y
|
||||
:x2 (+ x width) :y2 (+ y height)})))
|
||||
(let [selrect (gpr/rect->selrect shape)
|
||||
points (gpr/rect->points shape)]
|
||||
(-> shape
|
||||
(assoc :selrect selrect
|
||||
:points points))))
|
||||
|
||||
|
||||
;; EXPORTS
|
||||
(defn center-shape [shape] (gco/center-shape shape))
|
||||
(defn center-selrect [selrect] (gco/center-selrect selrect))
|
||||
(defn center-rect [rect] (gco/center-rect rect))
|
||||
|
||||
(defn rect->selrect [rect] (gpr/rect->selrect rect))
|
||||
(defn rect->points [rect] (gpr/rect->points rect))
|
||||
(defn points->selrect [points] (gpr/points->selrect points))
|
||||
|
||||
(defn transform-shape [shape] (gtr/transform-shape shape))
|
||||
(defn transform-matrix [shape] (gtr/transform-matrix shape))
|
||||
(defn transform-point-center [point center transform] (gtr/transform-point-center point center transform))
|
||||
(defn transform-rect [rect mtx] (gtr/transform-rect rect mtx))
|
||||
|
||||
;; PATHS
|
||||
(defn content->points [content] (gsp/content->points content))
|
||||
(defn content->selrect [content] (gsp/content->selrect content))
|
||||
|
|
52
common/app/common/geom/shapes/common.cljc
Normal file
|
@ -0,0 +1,52 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.common.geom.shapes.common
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[app.common.spec :as us]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.math :as mth]
|
||||
[app.common.data :as d]))
|
||||
|
||||
(defn center-rect
|
||||
[{:keys [x y width height]}]
|
||||
(when (and (mth/finite? x)
|
||||
(mth/finite? y)
|
||||
(mth/finite? width)
|
||||
(mth/finite? height))
|
||||
(gpt/point (+ x (/ width 2))
|
||||
(+ y (/ height 2)))))
|
||||
|
||||
(defn center-selrect
|
||||
"Calculate the center of the shape."
|
||||
[selrect]
|
||||
(center-rect selrect))
|
||||
|
||||
(defn center-points [points]
|
||||
(let [minx (transduce (map :x) min ##Inf points)
|
||||
miny (transduce (map :y) min ##Inf points)
|
||||
maxx (transduce (map :x) max ##-Inf points)
|
||||
maxy (transduce (map :y) max ##-Inf points)]
|
||||
(gpt/point (/ (+ minx maxx) 2)
|
||||
(/ (+ miny maxy) 2))))
|
||||
|
||||
(defn center-shape
|
||||
"Calculate the center of the shape."
|
||||
[shape]
|
||||
(center-rect (:selrect shape)))
|
||||
|
||||
(defn make-centered-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})
|
162
common/app/common/geom/shapes/path.cljc
Normal file
|
@ -0,0 +1,162 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.common.geom.shapes.path
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[app.common.spec :as us]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.math :as mth]
|
||||
[app.common.data :as d]))
|
||||
|
||||
(defn content->points [content]
|
||||
(->> content
|
||||
(map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y))))
|
||||
(remove nil?)
|
||||
(into [])))
|
||||
|
||||
;; https://medium.com/@Acegikmo/the-ever-so-lovely-b%C3%A9zier-curve-eb27514da3bf
|
||||
;; https://en.wikipedia.org/wiki/Bernstein_polynomial
|
||||
(defn curve-values
|
||||
"Parametric equation for cubic beziers. Given a start and end and
|
||||
two intermediate points returns points for values of t.
|
||||
If you draw t on a plane you got the bezier cube"
|
||||
[start end h1 h2 t]
|
||||
|
||||
(let [t2 (* t t) ;; t square
|
||||
t3 (* t2 t) ;; t cube
|
||||
|
||||
start-v (+ (- t3) (* 3 t2) (* -3 t) 1)
|
||||
h1-v (+ (* 3 t3) (* -6 t2) (* 3 t))
|
||||
h2-v (+ (* -3 t3) (* 3 t2))
|
||||
end-v t3
|
||||
|
||||
coord-v (fn [coord]
|
||||
(+ (* (coord start) start-v)
|
||||
(* (coord h1) h1-v)
|
||||
(* (coord h2) h2-v)
|
||||
(* (coord end) end-v)))]
|
||||
|
||||
(gpt/point (coord-v :x) (coord-v :y))))
|
||||
|
||||
;; https://pomax.github.io/bezierinfo/#extremities
|
||||
(defn curve-extremities
|
||||
"Given a cubic bezier cube finds its roots in t. This are the extremities
|
||||
if we calculate its values for x, y we can find a bounding box for the curve."
|
||||
[start end h1 h2]
|
||||
|
||||
(let [coords [[(:x start) (:x h1) (:x h2) (:x end)]
|
||||
[(:y start) (:y h1) (:y h2) (:y end)]]
|
||||
|
||||
coord->tvalue
|
||||
(fn [[c0 c1 c2 c3]]
|
||||
|
||||
(let [a (+ (* -3 c0) (* 9 c1) (* -9 c2) (* 3 c3))
|
||||
b (+ (* 6 c0) (* -12 c1) (* 6 c2))
|
||||
c (+ (* 3 c1) (* -3 c0))
|
||||
|
||||
sqrt-b2-4ac (mth/sqrt (- (* b b) (* 4 a c)))]
|
||||
|
||||
(cond
|
||||
(and (mth/almost-zero? a)
|
||||
(not (mth/almost-zero? b)))
|
||||
;; When the term a is close to zero we have a linear equation
|
||||
[(/ (- c) b)]
|
||||
|
||||
;; If a is not close to zero return the two roots for a cuadratic
|
||||
(not (mth/almost-zero? a))
|
||||
[(/ (+ (- b) sqrt-b2-4ac)
|
||||
(* 2 a))
|
||||
(/ (- (- b) sqrt-b2-4ac)
|
||||
(* 2 a))]
|
||||
|
||||
;; If a and b close to zero we can't find a root for a constant term
|
||||
:else
|
||||
[])))]
|
||||
(->> coords
|
||||
(mapcat coord->tvalue)
|
||||
|
||||
;; Only values in the range [0, 1] are valid
|
||||
(filter #(and (>= % 0) (<= % 1)))
|
||||
|
||||
;; Pass t-values to actual points
|
||||
(map #(curve-values start end h1 h2 %)))
|
||||
))
|
||||
|
||||
(defn command->point
|
||||
([command] (command->point command nil))
|
||||
([{params :params} coord]
|
||||
(let [prefix (if coord (name coord) "")
|
||||
xkey (keyword (str prefix "x"))
|
||||
ykey (keyword (str prefix "y"))
|
||||
x (get params xkey)
|
||||
y (get params ykey)]
|
||||
(gpt/point x y))))
|
||||
|
||||
(defn content->selrect [content]
|
||||
(let [calc-extremities
|
||||
(fn [command prev]
|
||||
(case (:command command)
|
||||
:close-path []
|
||||
:move-to [(command->point command)]
|
||||
|
||||
;; If it's a line we add the beginning point and endpoint
|
||||
:line-to [(command->point prev)
|
||||
(command->point command)]
|
||||
|
||||
;; We return the bezier extremities
|
||||
:curve-to (d/concat
|
||||
[(command->point prev)
|
||||
(command->point command)]
|
||||
(curve-extremities (command->point prev)
|
||||
(command->point command)
|
||||
(command->point command :c1)
|
||||
(command->point command :c2)))))
|
||||
|
||||
extremities (mapcat calc-extremities
|
||||
content
|
||||
(d/concat [nil] content))]
|
||||
|
||||
(gpr/points->selrect extremities)))
|
||||
|
||||
(defn transform-content [content transform]
|
||||
(let [set-tr (fn [params px py]
|
||||
(let [tr-point (-> (gpt/point (get params px) (get params py))
|
||||
(gpt/transform transform))]
|
||||
(assoc params
|
||||
px (:x tr-point)
|
||||
py (:y tr-point))))
|
||||
|
||||
transform-params
|
||||
(fn [{:keys [x y c1x c1y c2x c2y] :as params}]
|
||||
(cond-> params
|
||||
(not (nil? x)) (set-tr :x :y)
|
||||
(not (nil? c1x)) (set-tr :c1x :c1y)
|
||||
(not (nil? c2x)) (set-tr :c2x :c2y)))]
|
||||
|
||||
(mapv #(update % :params transform-params) content)))
|
||||
|
||||
(defn segments->content
|
||||
([segments]
|
||||
(segments->content segments false))
|
||||
|
||||
([segments closed?]
|
||||
(let [initial (first segments)
|
||||
lines (rest segments)]
|
||||
|
||||
(d/concat [{:command :move-to
|
||||
:params (select-keys initial [:x :y])}]
|
||||
(->> lines
|
||||
(mapv #(hash-map :command :line-to
|
||||
:params (select-keys % [:x :y]))))
|
||||
|
||||
(when closed?
|
||||
[{:command :close-path}])))))
|
60
common/app/common/geom/shapes/rect.cljc
Normal file
|
@ -0,0 +1,60 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.common.geom.shapes.rect
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[app.common.spec :as us]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.math :as mth]
|
||||
[app.common.data :as d]))
|
||||
|
||||
(defn rect->points [{:keys [x y width height]}]
|
||||
[(gpt/point x y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point x (+ y height))])
|
||||
|
||||
(defn points->rect [points]
|
||||
(let [minx (transduce (comp (map :x) (remove nil?)) min ##Inf points)
|
||||
miny (transduce (comp (map :y) (remove nil?)) min ##Inf points)
|
||||
maxx (transduce (comp (map :x) (remove nil?)) max ##-Inf points)
|
||||
maxy (transduce (comp (map :y) (remove nil?)) max ##-Inf points)]
|
||||
{:x minx
|
||||
:y miny
|
||||
:width (- maxx minx)
|
||||
:height (- maxy miny)}))
|
||||
|
||||
(defn points->selrect [points]
|
||||
(let [{:keys [x y width height] :as rect} (points->rect points)]
|
||||
(assoc rect
|
||||
:x1 x
|
||||
:x2 (+ x width)
|
||||
:y1 y
|
||||
:y2 (+ y height))))
|
||||
|
||||
(defn rect->selrect [rect]
|
||||
(-> rect rect->points points->selrect))
|
||||
|
||||
(defn join-selrects [selrects]
|
||||
(let [minx (transduce (comp (map :x1) (remove nil?)) min ##Inf selrects)
|
||||
miny (transduce (comp (map :y1) (remove nil?)) min ##Inf selrects)
|
||||
maxx (transduce (comp (map :x2) (remove nil?)) max ##-Inf selrects)
|
||||
maxy (transduce (comp (map :y2) (remove nil?)) max ##-Inf selrects)]
|
||||
{:x minx
|
||||
:y miny
|
||||
:x1 minx
|
||||
:y1 miny
|
||||
:x2 maxx
|
||||
:y2 maxy
|
||||
:width (- maxx minx)
|
||||
:height (- maxy miny)}))
|
||||
|
263
common/app/common/geom/shapes/transforms.cljc
Normal file
|
@ -0,0 +1,263 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.common.geom.shapes.transforms
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[app.common.spec :as us]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.geom.shapes.path :as gpa]
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.math :as mth]
|
||||
[app.common.data :as d]))
|
||||
|
||||
(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 (or (gco/center-shape shape)
|
||||
(gpt/point 0 0))]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate shape-center)
|
||||
(gmt/multiply (:transform shape (gmt/matrix)))
|
||||
(gmt/translate (gpt/negate shape-center))))))
|
||||
|
||||
(defn transform-point-center
|
||||
"Transform a point around the shape center"
|
||||
[point center matrix]
|
||||
(gpt/transform
|
||||
point
|
||||
(gmt/multiply (gmt/translate-matrix center)
|
||||
matrix
|
||||
(gmt/translate-matrix (gpt/negate center)))))
|
||||
|
||||
(defn transform-points
|
||||
([points matrix]
|
||||
(transform-points points nil matrix))
|
||||
|
||||
([points center matrix]
|
||||
|
||||
(let [prev (if center (gmt/translate-matrix center) (gmt/matrix))
|
||||
post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix))
|
||||
|
||||
tr-point (fn [point]
|
||||
(gpt/transform point (gmt/multiply prev matrix post)))]
|
||||
(mapv tr-point points))))
|
||||
|
||||
(defn transform-rect
|
||||
"Transform a rectangles and changes its attributes"
|
||||
[{:keys [x y width height] :as rect} matrix]
|
||||
|
||||
(let [points (-> (gpr/rect->points rect)
|
||||
(transform-points matrix))]
|
||||
(gpr/points->rect points)))
|
||||
|
||||
(defn normalize-scale
|
||||
"We normalize the scale so it's not too close to 0"
|
||||
[scale]
|
||||
(cond
|
||||
(and (< scale 0) (> scale -0.01)) -0.01
|
||||
(and (>= scale 0) (< scale 0.01)) 0.01
|
||||
:else scale))
|
||||
|
||||
(defn modifiers->transform
|
||||
([center modifiers]
|
||||
(modifiers->transform (gmt/matrix) center modifiers))
|
||||
|
||||
([current-transform center modifiers]
|
||||
(let [ds-modifier (:displacement modifiers (gmt/matrix))
|
||||
{res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1))
|
||||
|
||||
;; Normalize x/y vector coordinates because scale by 0 is infinite
|
||||
res-x (normalize-scale res-x)
|
||||
res-y (normalize-scale res-y)
|
||||
resize (gpt/point res-x res-y)
|
||||
|
||||
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 (or (:rotation modifiers) 0)
|
||||
|
||||
center (gpt/transform center ds-modifier)
|
||||
|
||||
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 center)
|
||||
(gmt/multiply (gmt/rotate-matrix rt-modif))
|
||||
(gmt/translate (gpt/negate center))
|
||||
|
||||
;; Displacement
|
||||
(gmt/multiply ds-modifier))]
|
||||
transform)))
|
||||
|
||||
(defn- calculate-skew-angle
|
||||
"Calculates the skew angle of the paralelogram given by the points"
|
||||
[[p1 p2 p3 p4]]
|
||||
(let [v1 (gpt/to-vec p3 p4)
|
||||
v2 (gpt/to-vec p4 p1)]
|
||||
(- 90 (gpt/angle-with-other v1 v2))))
|
||||
|
||||
(defn- calculate-height
|
||||
"Calculates the height of a paralelogram given by the points"
|
||||
[[p1 p2 p3 p4]]
|
||||
(let [v1 (gpt/to-vec p3 p4)
|
||||
v2 (gpt/to-vec p4 p1)
|
||||
angle (gpt/angle-with-other v1 v2)]
|
||||
(* (gpt/length v2) (mth/sin (mth/radians angle)))))
|
||||
|
||||
(defn- calculate-rotation
|
||||
"Calculates the rotation between two shapes given the resize vector direction"
|
||||
[points-shape1 points-shape2 flip-x flip-y]
|
||||
|
||||
(let [idx-1 0
|
||||
idx-2 (cond (and flip-x (not flip-y)) 1
|
||||
(and flip-x flip-y) 2
|
||||
(and (not flip-x) flip-y) 3
|
||||
:else 0)
|
||||
p1 (nth points-shape1 idx-1)
|
||||
p2 (nth points-shape2 idx-2)
|
||||
v1 (gpt/to-vec (gco/center-points points-shape1) p1)
|
||||
v2 (gpt/to-vec (gco/center-points points-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- calculate-dimensions
|
||||
[[p1 p2 p3 p4]]
|
||||
(let [width (gpt/distance p1 p2)
|
||||
height (gpt/distance p2 p3)]
|
||||
{:width width :height height}))
|
||||
|
||||
(defn calculate-adjust-matrix
|
||||
"Calculates a matrix that is a series of transformations we have to do to the transformed rectangle so that
|
||||
after applying them the end result is the `shape-pathn-temp`.
|
||||
This is compose of three transformations: skew, resize and rotation"
|
||||
[points-temp points-rec flip-x flip-y]
|
||||
(let [center (gco/center-points points-temp)
|
||||
|
||||
stretch-matrix (gmt/matrix)
|
||||
|
||||
skew-angle (calculate-skew-angle points-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 )
|
||||
skew-angle (if (and (or flip-x flip-y)
|
||||
(not (and flip-x flip-y))) (- skew-angle) skew-angle )
|
||||
skew-angle (if (mth/nan? skew-angle) 0 skew-angle)
|
||||
|
||||
stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
|
||||
|
||||
h1 (calculate-height points-temp)
|
||||
h2 (calculate-height (transform-points points-rec center stretch-matrix))
|
||||
h3 (/ h1 h2)
|
||||
h3 (if (mth/nan? h3) 1 h3)
|
||||
|
||||
stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point 1 h3)))
|
||||
|
||||
rotation-angle (calculate-rotation
|
||||
(transform-points points-rec (gco/center-points points-rec) stretch-matrix)
|
||||
points-temp
|
||||
flip-x
|
||||
flip-y)
|
||||
|
||||
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 (/ 1 h3)))
|
||||
(gmt/skew (- skew-angle) 0)
|
||||
(gmt/rotate (- rotation-angle)))]
|
||||
[stretch-matrix stretch-matrix-inverse]))
|
||||
|
||||
|
||||
(defn apply-transform-path
|
||||
[shape transform]
|
||||
(let [content (gpa/transform-content (:content shape) transform)
|
||||
selrect (gpa/content->selrect content)
|
||||
points (gpr/rect->points selrect)
|
||||
rotation (mod (+ (:rotation shape 0)
|
||||
(or (get-in shape [:modifiers :rotation]) 0))
|
||||
360)]
|
||||
(assoc shape
|
||||
:content content
|
||||
:points points
|
||||
:selrect selrect)))
|
||||
|
||||
(defn apply-transform-rect
|
||||
"Given a new set of points transformed, set up the rectangle so it keeps
|
||||
its properties. We adjust de x,y,width,height and create a custom transform"
|
||||
[shape transform]
|
||||
;;
|
||||
(let [points (-> shape :points (transform-points transform))
|
||||
center (gco/center-points points)
|
||||
|
||||
;; Reverse the current transformation stack to get the base rectangle
|
||||
tr-inverse (:transform-inverse shape (gmt/matrix))
|
||||
modifiers (:modifiers shape)
|
||||
|
||||
points-temp (transform-points points center tr-inverse)
|
||||
points-temp-dim (calculate-dimensions points-temp)
|
||||
|
||||
;; This rectangle is the new data for the current rectangle. We want to change our rectangle
|
||||
;; to have this width, height, x, y
|
||||
rect-shape (gco/make-centered-rect center
|
||||
(:width points-temp-dim)
|
||||
(:height points-temp-dim))
|
||||
rect-points (gpr/rect->points rect-shape)
|
||||
|
||||
[matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points (:flip-x shape) (:flip-y shape))]
|
||||
(as-> shape $
|
||||
(merge $ rect-shape)
|
||||
(update $ :x #(mth/precision % 0))
|
||||
(update $ :y #(mth/precision % 0))
|
||||
(update $ :width #(mth/precision % 0))
|
||||
(update $ :height #(mth/precision % 0))
|
||||
(update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix))
|
||||
(update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix))))
|
||||
(assoc $ :points (into [] points))
|
||||
(assoc $ :selrect (gpr/rect->selrect rect-shape))
|
||||
(update $ :rotation #(mod (+ (or % 0)
|
||||
(or (get-in $ [:modifiers :rotation]) 0)) 360)))))
|
||||
|
||||
(defn apply-transform [shape transform]
|
||||
(let [apply-transform-fn
|
||||
(case (:type shape)
|
||||
:path apply-transform-path
|
||||
apply-transform-rect)]
|
||||
(apply-transform-fn shape transform)))
|
||||
|
||||
(defn set-flip [shape modifiers]
|
||||
(let [rx (get-in modifiers [:resize-vector :x])
|
||||
ry (get-in modifiers [:resize-vector :y])]
|
||||
(cond-> shape
|
||||
(and rx (< rx 0)) (update :flip-x not)
|
||||
(and ry (< ry 0)) (update :flip-y not))))
|
||||
|
||||
(defn transform-shape [shape]
|
||||
(let [center (gco/center-shape shape)]
|
||||
(if (and (:modifiers shape) center)
|
||||
(let [transform (modifiers->transform (:transform shape (gmt/matrix)) center (:modifiers shape))]
|
||||
(-> shape
|
||||
(set-flip (:modifiers shape))
|
||||
(apply-transform transform)
|
||||
(dissoc :modifiers)))
|
||||
shape)))
|
|
@ -23,8 +23,8 @@
|
|||
|
||||
(defn finite?
|
||||
[v]
|
||||
#?(:cljs (js/isFinite v)
|
||||
:clj (Double/isFinite v)))
|
||||
#?(:cljs (and (not (nil? v)) (js/isFinite v))
|
||||
:clj (and (not (nil? v)) (Double/isFinite v))))
|
||||
|
||||
(defn abs
|
||||
[v]
|
||||
|
@ -135,3 +135,6 @@
|
|||
(if (< num from)
|
||||
from
|
||||
(if (> num to) to num)))
|
||||
|
||||
(defn almost-zero? [num]
|
||||
(< (abs num) 1e-8))
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def file-version 2)
|
||||
(def file-version 3)
|
||||
(def max-safe-int 9007199254740991)
|
||||
(def min-safe-int -9007199254740991)
|
||||
|
||||
|
@ -273,7 +273,9 @@
|
|||
(s/every uuid? :kind vector?))
|
||||
|
||||
(s/def ::shape-attrs
|
||||
(s/keys :opt-un [:internal.shape/blocked
|
||||
(s/keys :req-un [:internal.shape/selrect
|
||||
:internal.shape/points]
|
||||
:opt-un [:internal.shape/blocked
|
||||
:internal.shape/collapsed
|
||||
:internal.shape/content
|
||||
:internal.shape/fill-color
|
||||
|
@ -309,8 +311,6 @@
|
|||
:internal.shape/width
|
||||
:internal.shape/height
|
||||
:internal.shape/interactions
|
||||
:internal.shape/selrect
|
||||
:internal.shape/points
|
||||
:internal.shape/masked-group?
|
||||
:internal.shape/shadow
|
||||
:internal.shape/blur]))
|
||||
|
@ -611,8 +611,7 @@
|
|||
:stroke-alignment :center
|
||||
:stroke-width 2
|
||||
:stroke-color "#000000"
|
||||
:stroke-opacity 1
|
||||
:segments []}
|
||||
:stroke-opacity 1}
|
||||
|
||||
{:type :frame
|
||||
:name "Artboard"
|
||||
|
@ -624,44 +623,37 @@
|
|||
:stroke-color "#000000"
|
||||
:stroke-opacity 0}
|
||||
|
||||
{:type :curve
|
||||
:name "Path"
|
||||
:fill-color "#000000"
|
||||
:fill-opacity 0
|
||||
:stroke-style :solid
|
||||
:stroke-alignment :center
|
||||
:stroke-width 2
|
||||
:stroke-color "#000000"
|
||||
:stroke-opacity 1
|
||||
:segments []}
|
||||
|
||||
{:type :text
|
||||
:name "Text"
|
||||
:content nil}])
|
||||
|
||||
(defn make-minimal-shape
|
||||
[type]
|
||||
(let [shape (d/seek #(= type (:type %)) minimal-shapes)]
|
||||
(let [type (cond (= type :curve) :path
|
||||
:else type)
|
||||
shape (d/seek #(= type (:type %)) minimal-shapes)]
|
||||
(when-not shape
|
||||
(ex/raise :type :assertion
|
||||
:code :shape-type-not-implemented
|
||||
:context {:type type}))
|
||||
(assoc shape
|
||||
:id (uuid/next)
|
||||
:x 0
|
||||
:y 0
|
||||
:width 1
|
||||
:height 1
|
||||
:selrect {:x 0
|
||||
:x1 0
|
||||
:x2 1
|
||||
:y 0
|
||||
:y1 0
|
||||
:y2 1
|
||||
:width 1
|
||||
:height 1}
|
||||
:points []
|
||||
:segments [])))
|
||||
|
||||
(cond-> shape
|
||||
:always
|
||||
(assoc :id (uuid/next))
|
||||
|
||||
(not= :path (:type shape))
|
||||
(assoc :x 0
|
||||
:y 0
|
||||
:width 1
|
||||
:height 1
|
||||
:selrect {:x 0
|
||||
:y 0
|
||||
:x1 0
|
||||
:y1 0
|
||||
:x2 1
|
||||
:y2 1
|
||||
:width 1
|
||||
:height 1}))))
|
||||
|
||||
(defn make-minimal-group
|
||||
[frame-id selection-rect group-name]
|
||||
|
@ -764,13 +756,14 @@
|
|||
|
||||
(defn rotation-modifiers
|
||||
[center shape angle]
|
||||
(let [displacement (let [shape-center (geom/center shape)]
|
||||
(let [displacement (let [shape-center (geom/center-shape shape)]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/rotate angle center)
|
||||
(gmt/rotate (- angle) shape-center)))]
|
||||
{:rotation angle
|
||||
:displacement displacement}))
|
||||
|
||||
;; reg-objects operation "regenerates" the values for the parent groups
|
||||
(defmethod process-change :reg-objects
|
||||
[data {:keys [page-id shapes]}]
|
||||
(letfn [(reg-objects [objects]
|
||||
|
@ -783,7 +776,7 @@
|
|||
(distinct))
|
||||
shapes)))
|
||||
(update-group [group objects]
|
||||
(let [gcenter (geom/center group)
|
||||
(let [gcenter (geom/center-shape group)
|
||||
gxfm (comp
|
||||
(map #(get objects %))
|
||||
(map #(-> %
|
||||
|
@ -798,10 +791,10 @@
|
|||
|
||||
;; Rotate the group shape change the data and rotate back again
|
||||
(-> group
|
||||
(assoc-in [:modifiers :rotation] (- (:rotation group 0)))
|
||||
(geom/transform-shape)
|
||||
(assoc :selrect selrect)
|
||||
(assoc :points (geom/rect->points selrect))
|
||||
(merge (select-keys selrect [:x :y :width :height]))
|
||||
(assoc-in [:modifiers :rotation] (:rotation group))
|
||||
(assoc-in [:modifiers :rotation] (:rotation group 0))
|
||||
(geom/transform-shape))))]
|
||||
|
||||
(d/update-in-when data [:pages-index page-id :objects] reg-objects)))
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(:require
|
||||
[app.common.pages :as cp]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.spec :as us]
|
||||
|
@ -35,7 +36,6 @@
|
|||
;; -- MIGRATIONS --
|
||||
|
||||
;; Ensure that all :shape attributes on shapes are vectors.
|
||||
|
||||
(defmethod migrate 2
|
||||
[data]
|
||||
(letfn [(update-object [id object]
|
||||
|
@ -49,3 +49,63 @@
|
|||
(update page :objects #(d/mapm update-object %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
|
||||
;; Changes paths formats
|
||||
(defmethod migrate 3
|
||||
[data]
|
||||
(letfn [(migrate-path [shape]
|
||||
(if-not (contains? shape :content)
|
||||
(let [content (gsp/segments->content (:segments shape) (:close? shape))
|
||||
selrect (gsh/content->selrect content)
|
||||
points (gsh/rect->points selrect)]
|
||||
(-> shape
|
||||
(dissoc :segments)
|
||||
(dissoc :close?)
|
||||
(assoc :content content)
|
||||
(assoc :selrect selrect)
|
||||
(assoc :points points)))
|
||||
;; If the shape contains :content is already in the new format
|
||||
shape))
|
||||
|
||||
(fix-frames-selrects [frame]
|
||||
(if (= (:id frame) uuid/zero)
|
||||
frame
|
||||
(let [frame-rect (select-keys frame [:x :y :width :height])]
|
||||
(-> frame
|
||||
(assoc :selrect (gsh/rect->selrect frame-rect))
|
||||
(assoc :points (gsh/rect->points frame-rect))))))
|
||||
|
||||
(fix-empty-points [shape]
|
||||
(let [shape (cond-> shape
|
||||
(empty? (:selrect shape)) (gsh/setup-selrect))]
|
||||
(cond-> shape
|
||||
(empty? (:points shape))
|
||||
(assoc :points (gsh/rect->points (:selrect shape))))))
|
||||
|
||||
(update-object [id object]
|
||||
(cond-> object
|
||||
(= :curve (:type object))
|
||||
(assoc :type :path)
|
||||
|
||||
(or (#{:curve :path} (:type object)))
|
||||
(migrate-path)
|
||||
|
||||
(= :frame (:type object))
|
||||
(fix-frames-selrects)
|
||||
|
||||
(and (empty? (:points object)) (not= (:id object) uuid/zero))
|
||||
(fix-empty-points)
|
||||
|
||||
:always
|
||||
(->
|
||||
;; Setup an empty transformation to re-calculate selrects
|
||||
;; and points data
|
||||
(assoc :modifiers {:displacement (gmt/matrix)})
|
||||
(gsh/transform-shape))
|
||||
|
||||
))
|
||||
|
||||
(update-page [id page]
|
||||
(update page :objects #(d/mapm update-object %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
|
|
4
frontend/resources/images/cursors/comments.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="24px" height="24px" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6.9 12.519-2.5349 2.0222-0.17039-2.8021c-0.94178-0.55996-2.933-2.2948-3.3621-4.7515-0.42907-2.4551 2.6642-4.7755 4.2636-5.6282 3.7036-0.49567 10.862-0.12159 9.8685 5.3362-0.99444 5.4578-5.7908 6.1564-8.0655 5.8234z" fill="#fff"/>
|
||||
<path d="m7.8642 0.38698c-2.26 0.027882-4.5687 0.88679-6.1014 2.586-1.0146 1.1052-1.6148 2.6201-1.4816 4.1296 0.10533 1.7488 1.1292 3.3396 2.4939 4.3929 0.25093 0.19827 0.51271 0.3826 0.77991 0.55841 0.029431 0.98902-0.026333 1.9858 0.072802 2.9694 0.16342 0.39731 0.73731 0.49567 1.0432 0.20369 0.81244-0.66993 1.6334-1.3306 2.4466-2.0013 0.76055 0.07745 1.5319 0.03486 2.2878-0.06815 2.1283-0.3462 4.1807-1.4994 5.3749-3.3257 0.81089-1.2214 1.1772-2.7618 0.85581-4.2055-0.31212-1.4847-1.2764-2.7711-2.4962-3.6447-1.5164-1.1036-3.4101-1.6303-5.2758-1.5947zm0.17426 1.3089c1.8696 0.00697 3.8027 0.65677 5.1217 2.0307 0.80392 0.82251 1.3282 1.9533 1.2686 3.1072-0.02478 1.3089-0.72647 2.5364-1.6961 3.3892-1.3848 1.2314-3.2792 1.8216-5.1101 1.7209-0.31599 0.01084-0.63973-0.08829-0.94642-0.04957-0.59868 0.48715-1.1966 0.97431-1.7937 1.4622-0.01549-0.6893-0.012392-1.3794-0.020137-2.0694-1.3677-0.68929-2.6093-1.8046-3.0871-3.2939-0.41203-1.2547-0.17503-2.7068 0.64515-3.7563 1.2911-1.7372 3.51-2.5519 5.6181-2.5403z" color="#000000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
4
frontend/resources/images/cursors/pen-node.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16.621 21.506">
|
||||
<path fill="#fff" d="M.719.719l.5 1.8v.1a45.985 45.985 0 011 4.7c.2 1.7.9 3.2 1.9 4.2 1 1.1 2.4 1.7 4.1 1.7.7 0-.001-.101.699-.301l3 3 4-4.1-3-3 .3-.6c0-1.7-.6-3.099-1.8-4.099-1-1.1-2.5-1.7-4.2-1.9l-3.4-.6c-.438-.116-.87-.25-1.298-.4L.719.719zm3.629 13.808a3.49 3.49 0 000 6.979 3.49 3.49 0 000-6.979zm0 1.866a1.624 1.624 0 11.002 3.248 1.624 1.624 0 01-.002-3.248z" color="#000" font-family="sans-serif" font-weight="400" overflow="visible" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||
<path fill="#000" fill-rule="evenodd" d="M.719.719l.5 1.8v.1a45.985 45.985 0 011 4.7c.2 1.7.9 3.2 1.9 4.2 1 1.1 2.4 1.7 4.1 1.7.333 0 .639-.028.937-.063l2.762 2.762 4-4.1-2.729-2.728c.018-.28.03-.562.03-.871 0-1.7-.601-3.1-1.801-4.1-1-1.1-2.5-1.7-4.2-1.9l-3.4-.6c-.438-.116-.87-.25-1.298-.4L.719.719zm2.537 1.736c1.31.306 2.63.573 3.963.764 3 .4 5 2.1 5 5l-.1 1.3.1.1.699.7 1.602 1.599-2.602 2.602-1.6-1.602-.004-.004-.695-.695-.047.047-.052-.047H8.219c-2.9 0-4.6-2-5-5-.2-1.7-.5-2.999-.7-3.899L6.33 7.13c-.34.884.12 2.089 1.389 2.089 2 0 2-3 0-3-.224 0-.419.04-.592.107l-3.871-3.87zM4.348 14.95a3.067 3.067 0 100 6.135 3.067 3.067 0 100-6.135zm0 1.024a2.045 2.045 0 110 4.09 2.045 2.045 0 010-4.09z" clip-rule="evenodd" color="#000" font-family="sans-serif" font-weight="400" overflow="visible" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
6
frontend/resources/images/cursors/pointer-move.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 26" width="14.516" height="26.17">
|
||||
<path d="M1 13.6V2.4l8.2 8.2H4z" fill="#fff"/>
|
||||
<path d="M0 16V0l11.6 11.6H4.4zm4-5.4h5.2L1 2.4v11.2z" clip-rule="evenodd" fill="#231f20" fill-rule="evenodd"/>
|
||||
<path d="M8.937 14.17l-2.842 2.842 1.263 1.263.526-.526v1.263h-.947l.526-.527L6.2 17.222l-2.842 2.842L6.2 22.907l1.263-1.264-.526-.526h.947v1.474l-.526-.632-1.263 1.369 2.842 2.842 2.842-2.842-1.263-1.264-.527.527v-1.474h.948l-.527.526 1.264 1.264 2.842-2.843-2.842-2.842-1.264 1.263.527.527h-.948v-1.263l.527.526 1.263-1.263z" fill="#fff"/>
|
||||
<path d="M8.937 14.907l-2.105 2.105.526.526 1.052-1.053v3.053H5.674l1.052-1.053-.526-.526-2.105 2.105L6.2 22.17l.526-.527-1.052-1.052H8.41v3.263L7.358 22.8l-.526.527 2.105 2.105 2.105-2.105-.526-.527-1.053 1.053V20.59H12.2l-1.053 1.052.527.527 2.105-2.106-2.105-2.105-.527.526 1.053 1.053H9.463v-3.053l1.053 1.053.526-.526z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 946 B |
1
frontend/resources/images/cursors/pointer-node.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12.632" height="21.293" fill="none"><path d="M1 13.6V2.4l8.2 8.2H4z" fill="#fff"/><path d="M0 16V0l11.6 11.6H4.4zm4-5.4h5.2L1 2.4v11.2z" clip-rule="evenodd" fill="#231f20" fill-rule="evenodd"/><path d="M9.143 19.429a1.624 1.624 0 100-3.248 1.624 1.624 0 000 3.248zm3.489-1.624a3.489 3.489 0 11-6.978 0 3.489 3.489 0 016.978 0z" clip-rule="evenodd" fill="#fff" fill-rule="evenodd"/><path d="M12.21 17.805a3.068 3.068 0 11-6.135 0 3.068 3.068 0 016.135 0zm-1.022 0a2.045 2.045 0 11-4.09 0 2.045 2.045 0 014.09 0z" clip-rule="evenodd" fill="#000" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 618 B |
3
frontend/resources/images/icons/nodes-add.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M369.205 7.23c-17.208.104-47.593 2.472-64.201 5.287-20.773 3.52-52.639 11.881-69.555 18.25l-14.548 5.476-7.122-5.436c-30.798-23.51-75.188-27.152-111.754-9.173-39.687 19.513-61.526 54.611-61.526 98.88 0 18.37 2.388 29.96 9.19 44.577l4.4 9.453-7.692 12.354c-16.232 26.07-30.749 58.736-37.94 85.375-4.02 14.893-8.44 39.23-8.45 46.528L0 323.085h17.745c19.1 0 19.638-.241 19.638-8.762 0-11.84 10.285-47.998 19.87-69.847 5.487-12.511 20.226-39.771 21.502-39.771.372 0 4.006 2.368 8.077 5.266 33.324 23.72 76.736 26.357 114.547 6.957 18.167-9.32 36.82-28.2 45.929-46.491 9.176-18.427 11.88-29.97 11.852-50.623-.024-18.36-2.286-29.476-9.157-45.013l-3.925-8.875 5.188-2.169c8.812-3.682 39.855-11.478 59.482-14.94 11.955-2.11 28.655-3.6 46.34-4.135 15.206-.46 27.713-.914 27.793-1.013.08-.099.979-8.028 1.996-17.617l1.85-17.436L378.68 7.54c-2.236-.239-5.506-.334-9.477-.31zM153.993 47.255c5.439.115 10.535.69 14.48 1.752 24.16 6.506 43.998 25.871 51.372 50.149 2.71 8.92 3.163 13.509 2.531 25.582-.695 13.29-1.381 15.947-7.112 27.478-10.13 20.38-27.473 34.334-48.661 39.153-5.872 1.336-14.045 2.37-18.16 2.297-32.296-.571-61.53-23.72-69.734-55.217-9.66-37.085 12.4-76.769 48.947-88.057 7.251-2.24 17.272-3.328 26.337-3.137zm194.549 141.863c-4.948 0-7.23 1.055-11.473 5.297l-5.293 5.296v123.363l-58.022.016c-34.96.005-60.316.647-63.793 1.612-12.302 3.417-16.2 17.153-7.6 26.781l4.546 5.09 62.043.779 62.047.779.779 61.79.779 61.792 3.894 4.19c2.141 2.303 5.296 4.766 7.009 5.475 1.713.709 3.662 1.333 4.332 1.388 2.784.225 11.463-4.39 14.36-7.637 2.987-3.348 3.134-6.05 3.586-65.636l.472-62.141h123.199l5.296-5.297c4.242-4.242 5.297-6.525 5.297-11.472 0-7.85-4.523-13.764-12.145-15.88-3.477-.966-28.829-1.613-63.79-1.613l-58.021-.015-.01-58.022c-.004-34.961-.646-60.313-1.612-63.79-2.117-7.622-8.03-12.145-15.88-12.145z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
3
frontend/resources/images/icons/nodes-corner.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M240.326 76.886c-7.638-.006-15.27.99-21.109 2.996-22.013 7.56-39.34 26.373-44.518 48.333-4.5 19.09-.083 40.81 11.353 55.816l3.04 3.988-43.821 54.644c-34.23 42.682-44.261 54.454-45.828 53.78-24.235-10.42-43.288-10.479-63.935-.196-14.994 7.466-28.572 24.15-33.606 41.289-2.234 7.604-2.571 25.97-.633 34.384 5.389 23.385 27.168 44.357 51.567 49.65 22.91 4.972 45.72-2.198 62.206-19.553 13.156-13.85 18.529-27.186 18.555-46.053.017-12.292-2.3-22.187-7.466-31.88l-3.726-6.988 44.836-55.895 44.837-55.897 7.058 2.59c5.629 2.063 9.92 2.594 21.163 2.62 12.469.028 15.087-.353 22.59-3.289 4.669-1.826 9.252-3.954 10.187-4.73 1.33-1.104 2.311-.766 4.517 1.557 2.064 2.173 81.858 93.81 97.285 111.724 1.585 1.84 1.306 3.028-2.505 10.647-17.899 35.777.792 80.37 39.04 93.145 16.958 5.664 33.836 4.499 50.65-3.499 36.251-17.243 49.065-62.73 27.251-96.724-5.382-8.387-17.147-19.038-25.927-23.47-18.635-9.407-42.082-9.637-59.938-.589l-5.64 2.858-42.274-48.494c-23.25-26.672-46.063-52.846-50.7-58.166l-8.43-9.673 3.884-7.892c7-14.22 8.867-28.714 5.65-43.896-4.87-22.983-22.14-42.445-44.467-50.11-5.866-2.014-13.509-3.022-21.147-3.027zm.929 28.928c5.153.09 10.157 1.013 14.05 2.795 14.35 6.57 21.879 17.418 22.734 32.755.893 16.027-6.018 28.847-19.518 36.202-5.483 2.988-8.298 3.655-16.996 4.022-9.491.4-11.086.119-17.831-3.14-28.557-13.797-29.16-54.64-1.018-68.915 5.08-2.577 11.953-3.836 18.579-3.72zM431.067 317.35c3.412-.105 6.882.28 10.354 1.169 12.355 3.165 20.235 9.339 25.942 20.327 2.416 4.65 2.867 7.35 2.867 17.168 0 10.626-.32 12.227-3.643 18.227-4.347 7.847-11.71 14.195-20.215 17.427-3.837 1.458-9.14 2.336-13.865 2.297-21.382-.179-37.694-16.329-37.694-37.316 0-11.152 2.56-18.641 8.797-25.726 7.532-8.557 17.223-13.258 27.457-13.573zm-364.48.122c15.176.033 28.573 8.515 35.241 22.31 3.905 8.078 4.064 22.996.333 31.096-3.607 7.831-10.761 15.386-18.138 19.154-6.632 3.387-21.483 4.956-27.668 2.922-33.797-11.112-38.072-55.336-6.889-71.243 7.245-3.695 9.505-4.256 17.12-4.239z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2 KiB |
3
frontend/resources/images/icons/nodes-curve.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M244.577 79.225a61.421 61.421 0 00-15.436 1.605c-21.988 5.173-41.986 23.143-48.381 43.474l-2.134 6.78H74.925v-20.543H0V185.466H74.925v-25.378h31.66c17.415 0 31.146.455 30.514 1.012-.631.558-4.41 3.48-8.398 6.496-10.714 8.103-26.93 25.517-34.648 37.205-15.054 22.799-25.433 49.97-30.521 79.91-.689 4.05-1.132 4.442-5.856 5.197-13.48 2.154-31.265 14.57-40.243 28.097-7.4 11.147-10.57 21.889-10.638 36.041-.09 18.774 6.174 34.098 19.329 47.293 8.576 8.603 23.71 16.734 34.3 18.43 22.872 3.661 42.976-2.674 58.645-18.482 13.554-13.673 19.846-28.637 19.886-47.29.054-26.118-17.983-52.66-41.525-61.103-2.576-.924-4.944-2.356-5.261-3.182-.785-2.046 2.847-17.999 6.989-30.702 12.624-38.72 37.846-68.227 71.689-83.864 4.98-2.3 9.596-4.182 10.257-4.182.662 0 2.466 2.568 4.008 5.71 4.408 8.979 17.8 22.3 27.592 27.445 9.31 4.892 21.229 7.888 31.406 7.893 23.718.01 48.035-15.184 58.641-36.644 2.511-5.08 5.162-9.234 5.891-9.228.73.006 7.094 2.368 14.14 5.247 36.538 14.927 63.7 43.54 79.59 83.841 4.193 10.636 11.574 35.522 10.635 35.863-.174.063-4.469 2.174-9.543 4.692-15.007 7.447-27.018 20.919-32.555 36.511-3.566 10.043-4.46 27.758-1.905 37.734 8.049 31.416 38.098 53.27 69.477 50.531 32.832-2.865 57.406-27.371 61.24-61.074 1.837-16.142-5.55-36.943-17.82-50.187-9.88-10.664-21.227-16.86-37.328-20.386-2.781-.609-3.341-1.606-4.647-8.263-2.366-12.061-10.426-35.322-17.03-49.146-11.797-24.697-30.196-48.268-49.313-63.175-4.413-3.441-8.54-6.703-9.172-7.249-.631-.545 10.925-.991 25.68-.991h26.83v25.378h76.132V110.54H416.92v20.544H308.54l-1.536-5.136c-8.14-27.222-34.67-46.113-62.427-46.724zm.248 28.594c20.076.653 38.59 17.524 36.445 41.178-1.165 12.848-9.105 24.698-20.494 30.592-6.773 3.504-23.247 4.32-30.837 1.529-7.329-2.695-16.54-11.483-20.483-19.54-2.762-5.646-3.354-8.461-3.354-15.956 0-12.011 3.38-19.722 12.17-27.755 7.978-7.291 17.427-10.344 26.553-10.048zM73.66 316.54c22.939.427 41.895 22.783 35.85 46.377-3.136 12.24-9.355 20.058-20.549 25.836-6.296 3.25-22.152 4.16-28.694 1.647-35.199-13.525-32.897-63.035 3.378-72.64a36.407 36.407 0 0110.015-1.22zm359.468.18c7.514-.066 9.858.475 16.756 3.87 14.635 7.205 22.116 19.953 21.124 36-.883 14.288-8.57 25.912-21.226 32.104-6.893 3.372-21.443 4.308-28.172 1.81-11.325-4.203-20.124-12.775-23.648-23.034-8.6-25.039 9.058-50.521 35.166-50.75zM561.389 720.95c-6.416-.046-13.085.744-17.701 2.318-17.355 5.916-31.094 19.83-36.004 36.462l-1.254 4.25h-78.684v23h78.684l1.271 4.25c5.172 17.284 20.647 32.463 38.045 37.317 6.08 1.696 22.323 1.724 28.614.049 18.293-4.871 34.106-20.609 38.933-38.746 1.939-7.284 1.856-22.146-.16-28.938-5.54-18.663-19.656-32.778-38.318-38.318-3.599-1.069-8.436-1.61-13.426-1.645zm164.267.081c-5.306.012-10.518.528-13.978 1.545-20.853 6.13-35.946 22.898-39.584 43.979-2.39 13.854.716 28.39 8.478 39.673 7.049 10.245 20.848 20.071 31.793 22.641 6.605 1.55 22.482 1.27 28.45-.502 17.81-5.287 32.264-19.166 37.634-36.137l1.663-5.25h78.634v-23H780.063l-1.254-4.25c-5.059-17.133-21.186-32.682-38.604-37.218-3.84-1-9.242-1.493-14.549-1.48zm.3 23.324c4.762-.047 9.503.84 13.29 2.666 9.532 4.598 15.446 12.427 17.776 23.536.971 4.63.886 6.578-.52 11.937-.924 3.523-2.807 8.191-4.185 10.375-3.33 5.274-10.7 10.775-16.905 12.617-13.61 4.042-27.272-.938-35.388-12.9-3.856-5.683-5.262-10.31-5.272-17.318-.012-11.357 7.273-22.822 17.78-27.98 3.875-1.904 8.66-2.885 13.423-2.933zm-165.71.174c6.784-.041 8.86.373 13.66 2.73 6.529 3.207 12.148 8.977 15.188 15.598 2.973 6.476 2.973 18.77 0 25.246-3.013 6.563-8.648 12.388-14.977 15.485-10.64 5.206-24.218 3.858-33.166-3.291-18.457-14.748-14.793-43.673 6.795-53.64 3.325-1.536 6.59-2.092 12.5-2.128zm.5 150.451v36h39v-36h-19.5zm117 0v36h40v-36h-20zm-117 57l.016 20.25c.01 14.304.447 22.087 1.488 26.504 4.069 17.256 17.425 33.307 34.617 41.602 9.678 4.669 17.897 7.183 28.352 8.674 9.227 1.315 28.846.274 38.527-2.045 4.675-1.12 12.775-4.145 18-6.723 17.883-8.823 30.709-24.722 34.496-42.762.995-4.738 1.475-13.22 1.487-26.25l.017-19.25h-40v18.635c0 11.707-.452 20.164-1.216 22.75-1.754 5.934-9.862 13.753-17.053 16.444-16.938 6.337-39.908 3.58-50.35-6.045-7.89-7.273-8.802-10.487-9.224-32.534l-.37-19.25h-19.392z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
3
frontend/resources/images/icons/nodes-join.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M346.165 49.901c-16.103 0-32.178 6.191-44.549 18.575-12.774 12.787-18.57 26.914-18.545 45.198.025 17.844 5.108 30.457 17.546 43.552 10.039 10.568 22.881 17.471 36.377 19.553 11.454 1.766 28.106-1.306 40.022-7.386 11.49-5.862 25.093-22.04 29.546-35.14l2.247-6.614h91.154V99.8h-91.267l-1.454-4.93c-2.681-9.08-8.7-18.786-16.362-26.388-12.483-12.386-28.613-18.58-44.715-18.582zm-192.833.023c-25.936 0-51.304 18.185-59.43 42.602L91.483 99.8H.037v27.839h91.455l1.389 4.35c6.9 21.58 25.856 39.02 47.507 43.71 15.063 3.261 26.846 1.762 41.53-5.286 39.44-18.931 47.91-71.2 16.531-102.023-12.554-12.332-27.54-18.466-45.116-18.466zm.293 27.39c7.232-.064 9.457.454 16.182 3.765 27.18 13.38 27.066 51.98-.195 65.318-5.817 2.846-20.076 4.093-25.745 2.251-10.381-3.371-19.176-11.16-23.56-20.867-3.633-8.042-3.4-20.744.534-29.14 6.2-13.232 18.452-21.202 32.784-21.328zm192.737.054c13.171.05 26.326 7.058 33.064 20.998 3.82 7.903 3.83 21.67.02 30.165-3.68 8.21-13.364 17.033-21.27 19.381-7.653 2.273-18.634 2.12-25.158-.35-7.3-2.765-16.618-11.672-19.909-19.03-3.906-8.736-3.864-22.34.093-30.422 6.801-13.887 19.988-20.793 33.16-20.742zM241.3 161.276v106.11l-22.34-22.317c-20.314-20.294-22.7-22.317-26.314-22.317-5.177 0-9.342 3.83-9.342 8.588 0 3.093 4.032 7.578 31.63 35.176 29.126 29.126 31.949 31.63 35.618 31.63 3.66 0 6.431-2.435 33.989-29.867 16.502-16.428 30.775-31.328 31.718-33.115 3.133-5.934-.815-12.412-7.564-12.412-3.854 0-5.796 1.637-26.497 22.317l-22.34 22.317v-106.11h-9.279zm104.972 162.391c-18.015 0-32.164 5.828-44.777 18.441-7.684 7.685-13.285 16.865-16.17 26.508l-1.475 4.93-33.745-.014-33.744-.011-2.186-7.012c-5.88-18.86-24-35.894-43.782-41.159-8.405-2.237-25.676-2.152-33.565.166-20.361 5.98-38.903 24.167-43.937 43.096l-1.31 4.93H.038V400.22H91.628l.734 3.19c1.88 8.15 8.983 20.39 15.915 27.423 8.186 8.305 15.494 12.922 26.311 16.624 9.042 3.094 26.063 3.546 35.806.953 19.77-5.262 38.566-22.993 44.009-41.517l1.96-6.67 33.743-.001h33.745l1.474 4.93c2.886 9.642 8.487 18.823 16.171 26.508 12.613 12.612 26.762 18.438 44.777 18.438 18.016 0 32.165-5.826 44.777-18.438 7.685-7.685 13.286-16.866 16.171-26.508l1.475-4.93h91.267V373.544h-91.267l-1.475-4.93c-2.885-9.643-8.486-18.823-16.17-26.508-12.613-12.612-26.762-18.439-44.778-18.439zm0 27.256c7.8 0 10.327.514 15.845 3.224 7.572 3.718 14.09 10.412 17.616 18.092 3.45 7.511 3.45 21.772 0 29.283-3.495 7.613-10.03 14.37-17.371 17.96-8.399 4.11-19.516 4.851-27.884 1.86-30.644-10.952-33.172-52.894-4.05-67.195 5.518-2.71 8.045-3.224 15.844-3.224zm-192.546.03c7.834-.025 10.3.472 15.844 3.194 7.217 3.544 13.363 9.644 17.444 17.313 2.198 4.128 2.653 6.769 2.651 15.42-.002 9.06-.412 11.217-3.092 16.314-3.725 7.082-10.937 13.843-18.187 17.05-15.315 6.775-34.753 1.303-44.211-12.444-4.534-6.59-7.441-17.776-6.459-24.848 1.07-7.698 6.966-19.164 11.993-23.318 7.897-6.526 13.774-8.65 24.017-8.681z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
3
frontend/resources/images/icons/nodes-merge.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M153.658 49.909c-9.284-.067-18.58 2.134-28 6.604-14.775 7.01-27.865 21.741-32.482 36.555l-2.122 6.813-45.527.011L0 99.902v27.838h91.267l1.456 4.929c4.692 15.893 17.009 30.16 32.684 37.858 5.348 2.627 12.594 5.236 16.103 5.798v-.002c14.512 2.323 27.764.414 40.597-5.852 9.498-4.637 22.415-17.376 27.548-27.168 16.568-31.6 3.864-70.693-28.069-86.384-9.372-4.606-18.644-6.942-27.928-7.01zm191.998.15c-40.132 0-71.217 38.53-62.144 77.025 5.775 24.503 24.788 43.62 48.515 48.774 30.871 6.706 62.672-10.702 73.33-40.144l2.676-7.395 45.656-.58 45.656-.58.326-13.615.329-13.616-45.944-.303-45.943-.304-2.048-5.8c-3.738-10.572-7.3-16.152-15.628-24.485-12.693-12.7-27.501-18.977-44.781-18.977zm-192.248 27.19c18.884-.143 35.996 15.864 36.123 35.411.095 14.556-6.684 26.231-19.177 33.03-8.38 4.562-21.758 5.304-30.835 1.711-23.34-9.239-29.882-41.303-12.05-59.063 6.167-6.142 11.358-8.866 20.038-10.512a32.999 32.999 0 015.901-.577zm192.792.179c4.012.054 8.122.746 12.204 2.13 8.695 2.946 18.563 12.748 21.386 21.247 5.898 17.758-1.087 36.285-16.93 44.908-5.048 2.748-7.826 3.415-15.448 3.716-15.35.605-26.662-5.572-33.622-18.36-2.73-5.015-3.47-8-3.87-15.622-.441-8.41-.15-10.197 2.726-16.604 6.134-13.67 19.223-21.61 33.554-21.415zm-104.937 83.949v52.776c0 29.027-.396 52.776-.881 52.776s-10.789-9.917-22.897-22.038c-18.922-18.942-22.51-22.038-25.539-22.038-5.519 0-8.679 2.872-8.679 7.888 0 4.05 1.673 5.949 31.601 35.892 27.256 27.27 32.075 31.614 35.063 31.614 2.988 0 7.816-4.35 35.097-31.63 26.934-26.935 31.63-32.134 31.63-35.013 0-4.827-4.09-8.751-9.123-8.751-3.748 0-5.902 1.813-26.477 22.29l-22.396 22.29V161.376h-8.7zm8.167 162.402c-17.663.025-30.355 5.073-43.182 17.172-8.831 8.331-12.776 14.25-16.676 25.022l-2.567 7.09-93.502.296L0 373.656v26.651l93.552.296 93.555.297 1.976 5.813c6.953 20.456 23.444 36.374 43.193 41.692 8.445 2.274 26.75 2.245 34.585-.057 20.168-5.924 36.966-22.166 43.372-41.938l1.971-6.09h187.721v-26.678H312.328l-2.62-7.25c-3.843-10.623-7.807-16.832-15.752-24.675-12.44-12.279-26.547-17.963-44.526-17.938zm-.022 27.28c7.558-.03 9.757.474 16.463 3.775 12.855 6.329 19.843 17.664 19.852 32.202.008 14.208-7.952 26.899-20.6 32.844-10.227 4.808-23.042 4.302-33.857-1.336-5.449-2.84-13.91-12.902-15.994-19.017-2.432-7.134-2.353-18.112.179-25.228 2.752-7.739 11.453-17.055 19.12-20.475 4.62-2.061 8.243-2.737 14.837-2.764z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
3
frontend/resources/images/icons/nodes-remove.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M382.316 7.667c-15.872-.235-47.714 1.104-61.212 2.962-32.005 4.406-69.244 13.896-91.83 23.404-5.342 2.248-5.66 2.17-12.837-3.097-19.326-14.187-46.622-22.07-71.126-20.543-28.405 1.77-51.076 12.049-71.622 32.469C62.492 53.99 58.965 58.777 52.85 71.166c-4.049 8.201-8.392 19.858-9.654 25.903-4.806 23.036-2.052 48.437 7.618 70.27l4.002 9.037-8.038 12.944C22.47 228.47 5.202 276.39.954 316.487L0 325.514h37.706v-4.857c0-8.476 6.08-35.068 11.862-51.898 5.203-15.145 23.164-53.485 28.07-59.924 2.055-2.695 2.593-2.546 9.982 2.763 18.245 13.108 39.461 19.651 63.818 19.688 19.01.028 32.058-2.936 48.77-11.083 20.733-10.106 37.148-25.702 48.62-46.2 16.004-28.594 17.686-68.546 4.11-97.557-2.555-5.46-4.16-10.218-3.566-10.57 4.07-2.412 34.919-11.042 50.904-14.241 25.404-5.084 45.493-7.163 69.148-7.163h19.396l1.837-17.56c1.01-9.656 1.43-17.958.935-18.448-.472-.467-3.986-.719-9.276-.797zm-232 39.589c31.396-.398 60.367 18.651 70.892 49.243 14.655 42.593-12.027 88.727-56.154 97.096-40.307 7.644-79.611-20.22-86.55-61.359-5.849-34.673 16.49-71.17 49.886-81.5 7.307-2.26 14.68-3.388 21.926-3.48zm248.571 276.676c-14.803 0-31.883.013-51.597.013H205.39l-5.339 5.339c-4.147 4.147-5.339 6.633-5.339 11.146 0 4.514 1.192 7 5.34 11.147l5.338 5.34h142.037c140.026 0 142.093-.046 146.06-3.165 5.961-4.69 8.104-12.502 5.28-19.261-4.135-9.896 3.743-10.55-99.88-10.559z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
3
frontend/resources/images/icons/nodes-separate.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M152.054 49.928a62.098 62.098 0 00-11.937 1.287c-20.8 4.306-41.307 22.573-47.077 41.934l-1.986 6.667-45.527.002H0v27.838h45.817c44.105 0 45.817.082 45.817 2.186 0 1.202 2.162 6.507 4.805 11.787 8.528 17.04 25.217 30.27 43.07 34.15v.002c31.335 6.808 63.86-11.457 74.061-41.592l2.016-5.951h67.693l2.677 7.392c8.759 24.188 33.964 41.864 59.7 41.864 26.285 0 54.078-20.373 60.488-44.34l1.463-5.471 45.87-.304 45.868-.301.326-13.615.329-13.618-45.984-.302-45.983-.303-2.591-7.161c-11.009-30.405-43.206-47.787-74.745-40.353-20.388 4.806-38.75 21.616-45.547 41.698l-1.969 5.816-33.622.306-33.621.308-1.398-4.368c-8.83-27.617-34.783-45.852-62.486-45.559zm193.93 27.322c8.762.005 17.597 3.217 24.615 10.276 8.755 8.806 10.435 13.027 10.435 26.211 0 10.05-.303 11.568-3.446 17.243-3.541 6.392-9.705 12.199-16.853 15.876-6.239 3.21-21.047 3.48-28.506.523-15.69-6.22-25.068-22.733-22.422-39.475 3-18.972 19.452-30.662 36.177-30.654zm-192.682.1c21.682.255 41.471 20.325 35.316 44.35-3.002 11.72-8.978 19.23-19.714 24.77-3.396 1.754-7.455 2.58-14.055 2.855-11.988.502-19.297-2.217-27.199-10.12-15.693-15.693-13.671-41.92 4.232-54.919 6.757-4.906 14.193-7.022 21.42-6.937zm86.801 83.944v106.11l-22.34-22.317c-18.392-18.375-22.912-22.317-25.592-22.317-7.412 0-11.254 5.433-8.448 11.945.88 2.043 15.142 17.154 31.693 33.581 27.498 27.292 30.434 29.868 34.037 29.868 3.614 0 6.58-2.635 35.575-31.63 26.934-26.935 31.63-32.134 31.63-35.013 0-4.827-4.09-8.751-9.123-8.751-3.748 0-5.902 1.813-26.477 22.29l-22.396 22.29V161.293h-9.28zm-86.946 162.402c-17.286.024-30.241 5.04-42.398 16.411-8.6 8.044-13.832 15.762-17.435 25.713l-2.592 7.159-45.366.304L0 373.586v26.624l45.366.304 45.366.303 2.567 7.091c3.905 10.787 7.848 16.69 16.737 25.077 8.172 7.709 18.697 13.76 27.553 15.84 7.612 1.787 26.073 1.472 32.998-.562 10.323-3.032 19.754-8.704 27.78-16.708 24.805-24.738 24.797-64.496-.02-89.314-12.771-12.77-26.903-18.57-45.19-18.545zm189.792.01c-4.188.032-8.127.367-11.285 1.042-27.91 5.971-49.77 33.268-49.77 62.151 0 27.744 20.105 54.286 46.495 61.383 9.722 2.615 27.46 2.33 36.152-.582 20.148-6.75 36.39-23.049 42.038-42.181l1.389-4.7 45.98-.304 45.977-.302V373.587l-45.977-.303-45.98-.304-1.39-4.7c-5.19-17.586-19.866-33.307-38.104-40.822-5.902-2.432-16.312-3.823-25.525-3.752zm-189.815 27.26c7.545-.02 9.77.49 16.463 3.786 12.843 6.323 19.783 17.585 19.918 32.32.067 7.257-.425 9.275-3.955 16.195-4.678 9.171-12.244 15.706-21.352 18.448-14.165 4.263-28 .237-38.212-11.121-12.826-14.266-11.536-37.323 2.814-50.287 7.517-6.791 14.083-9.313 24.324-9.34zm192.522.02c11.51.038 18.931 3.3 26.603 11.695 7.279 7.964 9.754 15.076 9.162 26.336-.724 13.78-7.532 24.283-19.76 30.48-15.68 7.945-36.509 2.49-46.167-12.091-12.337-18.625-5.23-43.961 14.981-53.416 5.212-2.437 8.187-3.027 15.18-3.004z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
3
frontend/resources/images/icons/nodes-snap.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M155.038 59.376c-7.443-.052-15.18.864-20.535 2.69-20.133 6.863-36.071 23.005-41.768 42.3l-1.455 4.93H0V135.979h91.28l1.475 4.931c6 20.05 23.953 37.66 44.136 43.29 7.054 1.969 25.896 2 33.194.057 21.222-5.65 39.566-23.908 45.167-44.949 2.249-8.45 2.153-25.69-.186-33.57-6.428-21.65-22.802-38.026-44.453-44.453-4.174-1.24-9.786-1.867-15.575-1.908zm190.565.095c-6.156.014-12.202.613-16.216 1.793-24.192 7.11-41.7 26.563-45.921 51.019-2.774 16.072.83 32.936 9.836 46.025 8.177 11.885 24.185 23.284 36.883 26.265 7.662 1.8 26.08 1.473 33.003-.582 20.662-6.134 37.43-22.234 43.66-41.922l1.928-6.09H500v-26.683h-91.28l-1.455-4.93c-5.868-19.876-24.577-37.914-44.784-43.177-4.455-1.16-10.721-1.732-16.878-1.718zm.347 27.059c5.526-.055 11.025.973 15.419 3.092 11.058 5.335 17.918 14.416 20.621 27.303 1.127 5.372 1.028 7.631-.603 13.85-1.072 4.085-3.256 9.502-4.855 12.035-3.862 6.118-12.413 12.5-19.61 14.637-15.79 4.69-31.64-1.088-41.055-14.966-4.473-6.592-6.104-11.96-6.115-20.09-.015-13.175 8.437-26.475 20.625-32.46 4.496-2.208 10.048-3.346 15.573-3.401zm-192.238.201c7.87-.048 10.279.433 15.847 3.168 7.574 3.72 14.092 10.413 17.62 18.095 3.449 7.512 3.449 21.775 0 29.287-3.496 7.614-10.033 14.372-17.375 17.964-12.344 6.04-28.096 4.476-38.476-3.818-21.412-17.11-17.161-50.665 7.883-62.228 3.857-1.781 7.645-2.426 14.501-2.468zm.58 174.538V303.032h45.244V261.269h-22.622zm135.731 0V303.032h46.404V261.269h-23.202zm-135.73 66.125l.017 23.492c.013 16.594.519 25.622 1.727 30.747 4.72 20.018 20.215 38.639 40.16 48.261 11.226 5.417 20.761 8.334 32.89 10.063 10.704 1.526 33.464.318 44.695-2.372 5.423-1.3 14.82-4.809 20.882-7.8 20.746-10.235 35.624-28.679 40.018-49.607 1.154-5.497 1.71-15.336 1.724-30.452l.02-22.332H290.024v21.618c0 13.581-.525 23.392-1.411 26.392-2.034 6.884-11.44 15.955-19.783 19.076-19.65 7.352-46.297 4.152-58.41-7.013-9.153-8.437-10.211-12.165-10.702-37.741l-.428-22.332h-22.497z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
3
frontend/resources/images/icons/pen.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="500" height="500" version="1.1" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m1.75e-5 -0.034425 1.8524 6.6668c19.855 70.353 37.923 148.23 48.145 211.81-5e-3 -0.0421-0.01033-0.0839-0.01528-0.12604l0.02101 0.1566c-0.0017-0.0103-0.0041-0.0203-0.0058-0.0305 6.5425 55.391 29.322 104.35 62.178 137.23 32.904 36.138 79.123 55.863 134.67 55.863 10.162 0 19.793-0.54966 28.999-1.545l90.041 90.041 134.11-137.47-88.933-88.935c0.46511-8.606 0.50799-17.513 0.50799-26.818 0-55.505-19.76-101.81-59.111-134.71-32.95-36.112-81.987-55.605-137.1-62.126-77.669-8.773-142.22-29.696-215.36-50.013zm99.234 67.97c37.995 8.7741 76.301 16.145 114.87 21.925l0.0325 0.0039 0.0305 0.0059c47.638 6.3518 87.018 22.988 114.41 49.203 27.369 26.196 42.991 61.95 43.014 107.64l-3.3401 43.402 75.699 75.699-78.093 78.093-73.976-73.976-1.6786 1.6786-0.0669-0.0573h-43.283c-45.738 0-81.521-15.631-107.74-43.02-26.214-27.389-42.852-66.766-49.203-114.4-5.5829-47.428-13.468-84.629-19.683-112.68l111.63 111.63c-12.123 33.534 12.706 69.76 48.904 69.805h6e-3c28.759 0 52.156-23.399 52.156-52.158 0-28.759-23.397-52.156-52.156-52.156h-2e-3c-6.0543 3e-3 -12.035 1.1211-17.714 3.1777z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
3
frontend/resources/images/icons/pointer-inner.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M108.957 0v500l8.142-8.142 129.769-129.77h224.175zm39.35 94.576l228.162 228.163H230.994l-1.398 1.395-81.29 81.29z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 198 B |
|
@ -206,6 +206,12 @@
|
|||
|
||||
&.menu {
|
||||
margin-right: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-60;
|
||||
|
|
|
@ -225,3 +225,88 @@
|
|||
padding: $x-small;
|
||||
}
|
||||
}
|
||||
|
||||
.viewport-actions {
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
width: 100%;
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.path-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid $color-gray-20;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.viewport-actions-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-right: 1px solid $color-gray-20;
|
||||
}
|
||||
|
||||
.viewport-actions-entry {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0 0.25rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.3;
|
||||
|
||||
&:hover svg {
|
||||
fill: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-toggled {
|
||||
background: $color-black;
|
||||
|
||||
svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.viewport-actions-entry-wide {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
|
||||
svg {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.path-actions > :first-child .viewport-actions-entry {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.path-actions > :last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.path-actions > :last-child .viewport-actions-entry {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.proportions :as gpr]
|
||||
[app.common.geom.align :as gal]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
|
@ -29,6 +31,8 @@
|
|||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.texts :as dwtxt]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[app.main.data.workspace.drawing :as dwd]
|
||||
[app.main.data.workspace.drawing.path :as dwdp]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
|
@ -339,7 +343,7 @@
|
|||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
shapes (cph/select-toplevel-shapes objects {:include-frames? true})
|
||||
srect (geom/selection-rect shapes)
|
||||
srect (gsh/selection-rect shapes)
|
||||
local (assoc local :vport size :zoom 1)]
|
||||
(cond
|
||||
(or (not (mth/finite? (:width srect)))
|
||||
|
@ -348,7 +352,7 @@
|
|||
|
||||
(or (> (:width srect) width)
|
||||
(> (:height srect) height))
|
||||
(let [srect (geom/adjust-to-viewport size srect {:padding 40})
|
||||
(let [srect (gal/adjust-to-viewport size srect {:padding 40})
|
||||
zoom (/ (:width size) (:width srect))]
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
|
@ -471,10 +475,10 @@
|
|||
(let [vbox (update vbox :x + (:left-offset vbox))
|
||||
new-zoom (if (fn? zoom) (zoom (:zoom local)) zoom)
|
||||
old-zoom (:zoom local)
|
||||
center (if center center (geom/center vbox))
|
||||
center (if center center (gsh/center-rect vbox))
|
||||
scale (/ old-zoom new-zoom)
|
||||
mtx (gmt/scale-matrix (gpt/point scale) center)
|
||||
vbox' (geom/transform vbox mtx)
|
||||
vbox' (gsh/transform-rect vbox mtx)
|
||||
vbox' (update vbox' :x - (:left-offset vbox))]
|
||||
(-> local
|
||||
(assoc :zoom new-zoom)
|
||||
|
@ -510,14 +514,14 @@
|
|||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
shapes (cph/select-toplevel-shapes objects {:include-frames? true})
|
||||
srect (geom/selection-rect shapes)]
|
||||
srect (gsh/selection-rect shapes)]
|
||||
|
||||
(if (or (mth/nan? (:width srect))
|
||||
(mth/nan? (:height srect)))
|
||||
state
|
||||
(update state :workspace-local
|
||||
(fn [{:keys [vbox vport] :as local}]
|
||||
(let [srect (geom/adjust-to-viewport vport srect {:padding 40})
|
||||
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
|
||||
zoom (/ (:width vport) (:width srect))]
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
|
@ -534,10 +538,10 @@
|
|||
objects (dwc/lookup-page-objects state page-id)
|
||||
srect (->> selected
|
||||
(map #(get objects %))
|
||||
(geom/selection-rect))]
|
||||
(gsh/selection-rect))]
|
||||
(update state :workspace-local
|
||||
(fn [{:keys [vbox vport] :as local}]
|
||||
(let [srect (geom/adjust-to-viewport vport srect {:padding 40})
|
||||
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
|
||||
zoom (/ (:width vport) (:width srect))]
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
|
@ -545,50 +549,6 @@
|
|||
|
||||
;; --- Add shape to Workspace
|
||||
|
||||
(declare start-edition-mode)
|
||||
|
||||
(defn add-shape
|
||||
[attrs]
|
||||
(us/verify ::shape-attrs attrs)
|
||||
(ptk/reify ::add-shape
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
|
||||
id (uuid/next)
|
||||
shape (geom/setup-proportions attrs)
|
||||
|
||||
unames (dwc/retrieve-used-names objects)
|
||||
name (dwc/generate-unique-name unames (:name shape))
|
||||
|
||||
frame-id (or (:frame-id attrs)
|
||||
(cph/frame-id-by-position objects attrs))
|
||||
|
||||
shape (merge
|
||||
(if (= :frame (:type shape))
|
||||
cp/default-frame-attrs
|
||||
cp/default-shape-attrs)
|
||||
(assoc shape
|
||||
:id id
|
||||
:name name))
|
||||
|
||||
rchange {:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:obj shape}
|
||||
uchange {:type :del-obj
|
||||
:page-id page-id
|
||||
:id id}]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dwc/commit-changes [rchange] [uchange] {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set id)))
|
||||
(when (= :text (:type attrs))
|
||||
(->> (rx/of (start-edition-mode id))
|
||||
(rx/observe-on :async))))))))
|
||||
|
||||
(defn- viewport-center
|
||||
[state]
|
||||
(let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])]
|
||||
|
@ -614,8 +574,8 @@
|
|||
(merge data)
|
||||
(merge {:x x :y y})
|
||||
(assoc :frame-id frame-id)
|
||||
(geom/setup-selrect))]
|
||||
(rx/of (add-shape shape))))))
|
||||
(gsh/setup-selrect))]
|
||||
(rx/of (dwc/add-shape shape))))))
|
||||
|
||||
;; --- Update Shape Attrs
|
||||
|
||||
|
@ -953,7 +913,7 @@
|
|||
|
||||
(defn align-objects
|
||||
[axis]
|
||||
(us/verify ::geom/align-axis axis)
|
||||
(us/verify ::gal/align-axis axis)
|
||||
(ptk/reify :align-objects
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -991,17 +951,17 @@
|
|||
[objects object-id axis]
|
||||
(let [object (get objects object-id)
|
||||
frame (get objects (:frame-id object))]
|
||||
(geom/align-to-rect object frame axis objects)))
|
||||
(gal/align-to-rect object frame axis objects)))
|
||||
|
||||
(defn align-objects-list
|
||||
[objects selected axis]
|
||||
(let [selected-objs (map #(get objects %) selected)
|
||||
rect (geom/selection-rect selected-objs)]
|
||||
(mapcat #(geom/align-to-rect % rect axis objects) selected-objs)))
|
||||
rect (gsh/selection-rect selected-objs)]
|
||||
(mapcat #(gal/align-to-rect % rect axis objects) selected-objs)))
|
||||
|
||||
(defn distribute-objects
|
||||
[axis]
|
||||
(us/verify ::geom/dist-axis axis)
|
||||
(us/verify ::gal/dist-axis axis)
|
||||
(ptk/reify :align-objects
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -1009,7 +969,7 @@
|
|||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
moved (-> (map #(get objects %) selected)
|
||||
(geom/distribute-space axis objects))]
|
||||
(gal/distribute-space axis objects))]
|
||||
(loop [moved (seq moved)
|
||||
rchanges []
|
||||
uchanges []]
|
||||
|
@ -1034,62 +994,6 @@
|
|||
:operations ops2
|
||||
:id (:id curr)})))))))))
|
||||
|
||||
;; --- Start shape "edition mode"
|
||||
|
||||
(declare clear-edition-mode)
|
||||
|
||||
(defn start-edition-mode
|
||||
[id]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::start-edition-mode
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :edition] id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> stream
|
||||
(rx/filter dwc/interrupt?)
|
||||
(rx/take 1)
|
||||
(rx/map (constantly clear-edition-mode))))))
|
||||
|
||||
(def clear-edition-mode
|
||||
(ptk/reify ::clear-edition-mode
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :edition))))
|
||||
|
||||
;; --- Select for Drawing
|
||||
|
||||
(def clear-drawing
|
||||
(ptk/reify ::clear-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-drawing dissoc :tool :object))))
|
||||
|
||||
(defn select-for-drawing
|
||||
([tool] (select-for-drawing tool nil))
|
||||
([tool data]
|
||||
(ptk/reify ::select-for-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-drawing assoc :tool tool :object data))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter (ptk/type? ::clear-drawing) stream)]
|
||||
(rx/merge
|
||||
(rx/of (dws/deselect-all))
|
||||
|
||||
;; NOTE: comments are a special case and they manage they
|
||||
;; own interrupt cycle.
|
||||
(when (not= tool :comments)
|
||||
(->> stream
|
||||
(rx/filter dwc/interrupt?)
|
||||
(rx/take 1)
|
||||
(rx/map (constantly clear-drawing))
|
||||
(rx/take-until stoper)))))))))
|
||||
|
||||
;; --- Update Dimensions
|
||||
|
||||
;; Event mainly used for handling user modification of the size of the
|
||||
|
@ -1103,7 +1007,7 @@
|
|||
(ptk/reify ::update-dimensions
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (dwc/update-shapes ids #(geom/resize-rect % attr value))))))
|
||||
(rx/of (dwc/update-shapes ids #(gsh/resize-rect % attr value))))))
|
||||
|
||||
|
||||
;; --- Shape Proportions
|
||||
|
@ -1117,7 +1021,7 @@
|
|||
(if-not lock
|
||||
(assoc shape :proportion-lock false)
|
||||
(-> (assoc shape :proportion-lock true)
|
||||
(geom/assign-proportions)))))))))
|
||||
(gpr/assign-proportions)))))))))
|
||||
;; --- Update Shape Position
|
||||
|
||||
(s/def ::x number?)
|
||||
|
@ -1142,23 +1046,6 @@
|
|||
(rx/of (dwt/set-modifiers [id] {:displacement displ})
|
||||
(dwt/apply-modifiers [id]))))))
|
||||
|
||||
;; --- Path Modifications
|
||||
|
||||
(defn update-path
|
||||
"Update a concrete point in the path shape."
|
||||
[id index delta]
|
||||
(us/verify ::us/uuid id)
|
||||
(us/verify ::us/integer index)
|
||||
(us/verify gpt/point? delta)
|
||||
(js/alert "TODO: broken")
|
||||
#_(ptk/reify ::update-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)]
|
||||
(-> state
|
||||
(update-in [:workspace-data page-id :objects id :segments index] gpt/add delta)
|
||||
(update-in [:workspace-data page-id :objects id] geom/update-path-selrect))))))
|
||||
|
||||
;; --- Shape attrs (Layers Sidebar)
|
||||
|
||||
(defn toggle-collapse
|
||||
|
@ -1290,7 +1177,7 @@
|
|||
;; When the parent frame is not selected we change to relative
|
||||
;; coordinates
|
||||
(let [frame (get objects (:frame-id shape))]
|
||||
(geom/translate-to-frame shape frame))
|
||||
(gsh/translate-to-frame shape frame))
|
||||
shape))
|
||||
|
||||
(prepare [result objects selected id]
|
||||
|
@ -1329,7 +1216,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected-objs (map #(get objects %) selected)
|
||||
wrapper (geom/selection-rect selected-objs)
|
||||
wrapper (gsh/selection-rect selected-objs)
|
||||
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))
|
||||
mouse-pos @ms/mouse-position
|
||||
|
||||
|
@ -1359,7 +1246,7 @@
|
|||
(map #(get-in % [:obj :id]))
|
||||
(into (d/ordered-set)))]
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes selected))))))
|
||||
(dwc/select-shapes selected))))))
|
||||
|
||||
(defn- image-uploaded
|
||||
[image]
|
||||
|
@ -1446,7 +1333,7 @@
|
|||
page-id (:current-page-id state)
|
||||
frame-id (-> (dwc/lookup-page-objects state page-id)
|
||||
(cph/frame-id-by-position @ms/mouse-position))
|
||||
shape (geom/setup-selrect
|
||||
shape (gsh/setup-selrect
|
||||
{:id id
|
||||
:type :text
|
||||
:name "Text"
|
||||
|
@ -1459,7 +1346,7 @@
|
|||
:content (as-content text)})]
|
||||
(rx/of dwc/start-undo-transaction
|
||||
(dws/deselect-all)
|
||||
(add-shape shape)
|
||||
(dwc/add-shape shape)
|
||||
dwc/commit-undo-transaction)))))
|
||||
|
||||
(defn update-shape-flags
|
||||
|
@ -1490,7 +1377,7 @@
|
|||
(when-not (empty? shapes)
|
||||
(let [[group rchanges uchanges] (dws/prepare-create-group page-id shapes "Group-" false)]
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id group))))))))))
|
||||
(dwc/select-shapes (d/ordered-set (:id group))))))))))
|
||||
|
||||
(def ungroup-selected
|
||||
(ptk/reify ::ungroup-selected
|
||||
|
@ -1568,7 +1455,7 @@
|
|||
:val (:fill-color mask)}]}))]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id group))))))))))
|
||||
(dwc/select-shapes (d/ordered-set (:id group))))))))))
|
||||
|
||||
(def unmask-group
|
||||
(ptk/reify ::unmask-group
|
||||
|
@ -1595,7 +1482,7 @@
|
|||
:val (:masked-group? group)}]}]]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id group))))))))))
|
||||
(dwc/select-shapes (d/ordered-set (:id group))))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1718,11 +1605,16 @@
|
|||
|
||||
(def select-shape dws/select-shape)
|
||||
(def deselect-all dws/deselect-all)
|
||||
(def select-shapes dws/select-shapes)
|
||||
(def select-shapes dwc/select-shapes)
|
||||
(def duplicate-selected dws/duplicate-selected)
|
||||
(def handle-selection dws/handle-selection)
|
||||
(def select-inside-group dws/select-inside-group)
|
||||
(def select-for-drawing dwd/select-for-drawing)
|
||||
(def clear-edition-mode dwc/clear-edition-mode)
|
||||
(def add-shape dwc/add-shape)
|
||||
(def start-edition-mode dwc/start-edition-mode)
|
||||
|
||||
(defn start-path-edit [id] (dwdp/start-path-edit id))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shortcuts
|
||||
|
@ -1730,6 +1622,18 @@
|
|||
|
||||
;; Shortcuts impl https://github.com/ccampbell/mousetrap
|
||||
|
||||
(defn esc-pressed []
|
||||
(ptk/reify :esc-pressed
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
;; Not interrupt when we're editing a path
|
||||
(let [edition-id (get-in state [:workspace-local :edition])
|
||||
path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
|
||||
(if-not (= :draw path-edit-mode)
|
||||
(rx/of :interrupt
|
||||
(deselect-all true))
|
||||
(rx/empty))))))
|
||||
|
||||
(def shortcuts
|
||||
{"ctrl+i" #(st/emit! (toggle-layout-flags :assets))
|
||||
"ctrl+l" #(st/emit! (toggle-layout-flags :sitemap :layers))
|
||||
|
@ -1753,15 +1657,16 @@
|
|||
"ctrl+shift+z" #(st/emit! dwc/redo)
|
||||
"ctrl+y" #(st/emit! dwc/redo)
|
||||
"ctrl+q" #(st/emit! dwc/reinitialize-undo)
|
||||
"a" #(st/emit! (select-for-drawing :frame))
|
||||
"b" #(st/emit! (select-for-drawing :rect))
|
||||
"e" #(st/emit! (select-for-drawing :circle))
|
||||
"a" #(st/emit! (dwd/select-for-drawing :frame))
|
||||
"b" #(st/emit! (dwd/select-for-drawing :rect))
|
||||
"e" #(st/emit! (dwd/select-for-drawing :circle))
|
||||
"t" #(st/emit! dwtxt/start-edit-if-selected
|
||||
(select-for-drawing :text))
|
||||
(dwd/select-for-drawing :text))
|
||||
"p" #(st/emit! (dwd/select-for-drawing :path))
|
||||
"ctrl+c" #(st/emit! copy-selected)
|
||||
"ctrl+v" #(st/emit! paste)
|
||||
"ctrl+x" #(st/emit! copy-selected delete-selected)
|
||||
"escape" #(st/emit! :interrupt (deselect-all true))
|
||||
"escape" #(st/emit! (esc-pressed))
|
||||
"del" #(st/emit! delete-selected)
|
||||
"backspace" #(st/emit! delete-selected)
|
||||
"ctrl+up" #(st/emit! (vertical-order-selected :up))
|
||||
|
@ -1777,4 +1682,3 @@
|
|||
"right" #(st/emit! (dwt/move-selected :right false))
|
||||
"left" #(st/emit! (dwt/move-selected :left false))
|
||||
"i" #(st/emit! (mdc/picker-for-selected-shape ))})
|
||||
|
||||
|
|
|
@ -20,8 +20,12 @@
|
|||
[app.common.uuid :as uuid]
|
||||
[app.main.worker :as uw]
|
||||
[app.util.timers :as ts]
|
||||
[app.common.geom.shapes :as geom]))
|
||||
[app.common.geom.proportions :as gpr]
|
||||
[app.common.geom.shapes :as gsh]))
|
||||
|
||||
(s/def ::shape-attrs ::cp/shape-attrs)
|
||||
(s/def ::set-of-string (s/every string? :kind set?))
|
||||
(s/def ::ordered-set-of-uuid (s/every uuid? :kind d/ordered-set?))
|
||||
;; --- Protocols
|
||||
|
||||
(declare setup-selection-index)
|
||||
|
@ -158,7 +162,7 @@
|
|||
(defn get-frame-at-point
|
||||
[objects point]
|
||||
(let [frames (cph/select-frames objects)]
|
||||
(d/seek #(geom/has-point? % point) frames)))
|
||||
(d/seek #(gsh/has-point? % point) frames)))
|
||||
|
||||
|
||||
(defn- extract-numeric-suffix
|
||||
|
@ -171,8 +175,6 @@
|
|||
[objects]
|
||||
(into #{} (map :name) (vals objects)))
|
||||
|
||||
(s/def ::set-of-string
|
||||
(s/every string? :kind set?))
|
||||
|
||||
(defn generate-unique-name
|
||||
"A unique name generator"
|
||||
|
@ -434,3 +436,92 @@
|
|||
[rchanges uchanges] (impl-gen-changes objects page-id (seq ids))]
|
||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true})))))))
|
||||
|
||||
|
||||
(defn select-shapes
|
||||
[ids]
|
||||
(us/verify ::ordered-set-of-uuid ids)
|
||||
(ptk/reify ::select-shapes
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :selected] ids))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (lookup-page-objects state page-id)]
|
||||
(rx/of (expand-all-parents ids objects))))))
|
||||
|
||||
;; --- Start shape "edition mode"
|
||||
|
||||
(declare clear-edition-mode)
|
||||
|
||||
(defn start-edition-mode
|
||||
[id]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::start-edition-mode
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])]
|
||||
;; Can only edit objects that exist
|
||||
(if (contains? objects id)
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :selected] #{id})
|
||||
(assoc-in [:workspace-local :edition] id))
|
||||
state)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> stream
|
||||
(rx/filter interrupt?)
|
||||
(rx/take 1)
|
||||
(rx/map (constantly clear-edition-mode))))))
|
||||
|
||||
(def clear-edition-mode
|
||||
(ptk/reify ::clear-edition-mode
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :edition))))
|
||||
|
||||
|
||||
(defn add-shape
|
||||
[attrs]
|
||||
(us/verify ::shape-attrs attrs)
|
||||
(ptk/reify ::add-shape
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (lookup-page-objects state page-id)
|
||||
|
||||
id (or (:id attrs) (uuid/next))
|
||||
shape (gpr/setup-proportions attrs)
|
||||
|
||||
unames (retrieve-used-names objects)
|
||||
name (generate-unique-name unames (:name shape))
|
||||
|
||||
frame-id (or (:frame-id attrs)
|
||||
(cph/frame-id-by-position objects attrs))
|
||||
|
||||
shape (merge
|
||||
(if (= :frame (:type shape))
|
||||
cp/default-frame-attrs
|
||||
cp/default-shape-attrs)
|
||||
(assoc shape
|
||||
:id id
|
||||
:name name))
|
||||
|
||||
rchange {:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:obj shape}
|
||||
uchange {:type :del-obj
|
||||
:page-id page-id
|
||||
:id id}]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (commit-changes [rchange] [uchange] {:commit-local? true})
|
||||
(select-shapes (d/ordered-set id)))
|
||||
(when (= :text (:type attrs))
|
||||
(->> (rx/of (start-edition-mode id))
|
||||
(rx/observe-on :async))))))))
|
||||
|
|
|
@ -12,24 +12,48 @@
|
|||
(:require
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.spec :as us]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.snap :as snap]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.geom.path :as path]))
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.drawing.common :as common]
|
||||
[app.main.data.workspace.drawing.path :as path]
|
||||
[app.main.data.workspace.drawing.curve :as curve]
|
||||
[app.main.data.workspace.drawing.box :as box]))
|
||||
|
||||
(declare start-drawing)
|
||||
(declare handle-drawing)
|
||||
(declare handle-drawing-generic)
|
||||
(declare handle-drawing-path)
|
||||
(declare handle-drawing-curve)
|
||||
(declare handle-finish-drawing)
|
||||
(declare conditional-align)
|
||||
|
||||
;; --- Select for Drawing
|
||||
|
||||
(defn select-for-drawing
|
||||
([tool] (select-for-drawing tool nil))
|
||||
([tool data]
|
||||
(ptk/reify ::select-for-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-drawing assoc :tool tool :object data))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter (ptk/type? ::clear-drawing) stream)]
|
||||
(rx/merge
|
||||
(rx/of (dws/deselect-all))
|
||||
|
||||
(when (= tool :path)
|
||||
(rx/of (start-drawing :path)))
|
||||
|
||||
;; NOTE: comments are a special case and they manage they
|
||||
;; own interrupt cycle.q
|
||||
(when (and (not= tool :comments)
|
||||
(not= tool :path))
|
||||
(->> stream
|
||||
(rx/filter dwc/interrupt?)
|
||||
(rx/take 1)
|
||||
(rx/map (constantly common/clear-drawing))
|
||||
(rx/take-until stoper)))))))))
|
||||
|
||||
|
||||
;; NOTE/TODO: when an exception is raised in some point of drawing the
|
||||
;; draw lock is not released so the user need to refresh in order to
|
||||
|
@ -38,20 +62,22 @@
|
|||
(defn start-drawing
|
||||
[type]
|
||||
{:pre [(keyword? type)]}
|
||||
(let [id (uuid/next)]
|
||||
(let [lock-id (uuid/next)]
|
||||
(ptk/reify ::start-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-drawing :lock] #(if (nil? %) id %)))
|
||||
(update-in state [:workspace-drawing :lock] #(if (nil? %) lock-id %)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [lock (get-in state [:workspace-drawing :lock])]
|
||||
(when (= lock id)
|
||||
(rx/merge (->> (rx/filter #(= % handle-finish-drawing) stream)
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace-drawing dissoc :lock))))
|
||||
(rx/of (handle-drawing type)))))))))
|
||||
(when (= lock lock-id)
|
||||
(rx/merge
|
||||
(rx/of (handle-drawing type))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::common/handle-finish-drawing) )
|
||||
(rx/first)
|
||||
(rx/map #(fn [state] (update state :workspace-drawing dissoc :lock)))))))))))
|
||||
|
||||
(defn handle-drawing
|
||||
[type]
|
||||
|
@ -63,248 +89,15 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(case type
|
||||
:path (rx/of handle-drawing-path)
|
||||
:curve (rx/of handle-drawing-curve)
|
||||
(rx/of handle-drawing-generic)))))
|
||||
(rx/of (case type
|
||||
:path
|
||||
(path/handle-new-shape)
|
||||
|
||||
(def handle-drawing-generic
|
||||
(letfn [(resize-shape [{:keys [x y width height] :as shape} point lock? point-snap]
|
||||
(let [;; The new shape behaves like a resize on the bottom-right corner
|
||||
initial (gpt/point (+ x width) (+ y height))
|
||||
shapev (gpt/point width height)
|
||||
deltav (gpt/to-vec initial point-snap)
|
||||
scalev (gpt/divide (gpt/add shapev deltav) shapev)
|
||||
scalev (if lock?
|
||||
(let [v (max (:x scalev) (:y scalev))]
|
||||
(gpt/point v v))
|
||||
scalev)]
|
||||
(-> shape
|
||||
(assoc ::click-draw? false)
|
||||
(assoc-in [:modifiers :resize-vector] scalev)
|
||||
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
|
||||
(assoc-in [:modifiers :resize-rotation] 0))))
|
||||
:curve
|
||||
(curve/handle-drawing-curve)
|
||||
|
||||
(update-drawing [state point lock? point-snap]
|
||||
(update-in state [:workspace-drawing :object] resize-shape point lock? point-snap))]
|
||||
|
||||
(ptk/reify ::handle-drawing-generic
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [flags]} (:workspace-local state)
|
||||
|
||||
stoper? #(or (ms/mouse-up? %) (= % :interrupt))
|
||||
stoper (rx/filter stoper? stream)
|
||||
initial @ms/mouse-position
|
||||
;; default
|
||||
(box/handle-drawing-box))))))
|
||||
|
||||
|
||||
page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
layout (get state :workspace-layout)
|
||||
|
||||
frames (cph/select-frames objects)
|
||||
fid (or (->> frames
|
||||
(filter #(geom/has-point? % initial))
|
||||
first
|
||||
:id)
|
||||
uuid/zero)
|
||||
|
||||
shape (-> state
|
||||
(get-in [:workspace-drawing :object])
|
||||
(geom/setup {:x (:x initial) :y (:y initial) :width 1 :height 1})
|
||||
(assoc :frame-id fid)
|
||||
(assoc ::initialized? true)
|
||||
(assoc ::click-draw? true))]
|
||||
(rx/concat
|
||||
;; Add shape to drawing state
|
||||
(rx/of #(assoc-in state [:workspace-drawing :object] shape))
|
||||
|
||||
;; Initial SNAP
|
||||
(->> (snap/closest-snap-point page-id [shape] layout initial)
|
||||
(rx/map (fn [{:keys [x y]}]
|
||||
#(update-in % [:workspace-drawing :object] assoc :x x :y y))))
|
||||
|
||||
(->> ms/mouse-position
|
||||
(rx/filter #(> (gpt/distance % initial) 2))
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/switch-map
|
||||
(fn [[point :as current]]
|
||||
(->> (snap/closest-snap-point page-id [shape] layout point)
|
||||
(rx/map #(conj current %)))))
|
||||
(rx/map
|
||||
(fn [[pt ctrl? point-snap]]
|
||||
#(update-drawing % pt ctrl? point-snap)))
|
||||
|
||||
(rx/take-until stoper))
|
||||
(rx/of handle-finish-drawing)))))))
|
||||
|
||||
(def handle-drawing-path
|
||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||
(or (= event :path/end-path-drawing)
|
||||
(= event :interrupt)
|
||||
(and (ms/mouse-event? event)
|
||||
(or (= type :double-click)
|
||||
(= type :context-menu)))
|
||||
(and (ms/keyboard-event? event)
|
||||
(= type :down)
|
||||
(= 13 (:key event)))))
|
||||
|
||||
(initialize-drawing [state point]
|
||||
(-> state
|
||||
(assoc-in [:workspace-drawing :object :segments] [point point])
|
||||
(assoc-in [:workspace-drawing :object ::initialized?] true)))
|
||||
|
||||
(insert-point-segment [state point]
|
||||
(-> state
|
||||
(update-in [:workspace-drawing :object :segments] (fnil conj []) point)))
|
||||
|
||||
(update-point-segment [state index point]
|
||||
(let [segments (count (get-in state [:workspace-drawing :object :segments]))
|
||||
exists? (< -1 index segments)]
|
||||
(cond-> state
|
||||
exists? (assoc-in [:workspace-drawing :object :segments index] point))))
|
||||
|
||||
(finish-drawing-path [state]
|
||||
(update-in
|
||||
state [:workspace-drawing :object]
|
||||
(fn [shape] (-> shape
|
||||
(update :segments #(vec (butlast %)))
|
||||
(geom/update-path-selrect)))))]
|
||||
|
||||
(ptk/reify ::handle-drawing-path
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [flags]} (:workspace-local state)
|
||||
|
||||
last-point (volatile! @ms/mouse-position)
|
||||
|
||||
stoper (->> (rx/filter stoper-event? stream)
|
||||
(rx/share))
|
||||
|
||||
mouse (rx/sample 10 ms/mouse-position)
|
||||
|
||||
points (->> stream
|
||||
(rx/filter ms/mouse-click?)
|
||||
(rx/filter #(false? (:shift %)))
|
||||
(rx/with-latest vector mouse)
|
||||
(rx/map second))
|
||||
|
||||
counter (rx/merge (rx/scan #(inc %) 1 points) (rx/of 1))
|
||||
|
||||
stream' (->> mouse
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/with-latest vector counter)
|
||||
(rx/map flatten))
|
||||
|
||||
imm-transform #(vector (- % 7) (+ % 7) %)
|
||||
immanted-zones (vec (concat
|
||||
(map imm-transform (range 0 181 15))
|
||||
(map (comp imm-transform -) (range 0 181 15))))
|
||||
|
||||
align-position (fn [angle pos]
|
||||
(reduce (fn [pos [a1 a2 v]]
|
||||
(if (< a1 angle a2)
|
||||
(reduced (gpt/update-angle pos v))
|
||||
pos))
|
||||
pos
|
||||
immanted-zones))]
|
||||
|
||||
(rx/merge
|
||||
(rx/of #(initialize-drawing % @last-point))
|
||||
|
||||
(->> points
|
||||
(rx/take-until stoper)
|
||||
(rx/map (fn [pt] #(insert-point-segment % pt))))
|
||||
|
||||
(rx/concat
|
||||
(->> stream'
|
||||
(rx/take-until stoper)
|
||||
(rx/map (fn [[point ctrl? index :as xxx]]
|
||||
(let [point (if ctrl?
|
||||
(as-> point $
|
||||
(gpt/subtract $ @last-point)
|
||||
(align-position (gpt/angle $) $)
|
||||
(gpt/add $ @last-point))
|
||||
point)]
|
||||
#(update-point-segment % index point)))))
|
||||
(rx/of finish-drawing-path
|
||||
handle-finish-drawing))))))))
|
||||
|
||||
(def simplify-tolerance 0.3)
|
||||
|
||||
(def handle-drawing-curve
|
||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||
(ms/mouse-event? event) (= type :up))
|
||||
|
||||
(initialize-drawing [state]
|
||||
(assoc-in state [:workspace-drawing :object ::initialized?] true))
|
||||
|
||||
(insert-point-segment [state point]
|
||||
(update-in state [:workspace-drawing :object :segments] (fnil conj []) point))
|
||||
|
||||
(finish-drawing-curve [state]
|
||||
(update-in
|
||||
state [:workspace-drawing :object]
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(update :segments #(path/simplify % simplify-tolerance))
|
||||
(geom/update-path-selrect)))))]
|
||||
|
||||
(ptk/reify ::handle-drawing-curve
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [flags]} (:workspace-local state)
|
||||
stoper (rx/filter stoper-event? stream)
|
||||
mouse (rx/sample 10 ms/mouse-position)]
|
||||
(rx/concat
|
||||
(rx/of initialize-drawing)
|
||||
(->> mouse
|
||||
(rx/map (fn [pt] #(insert-point-segment % pt)))
|
||||
(rx/take-until stoper))
|
||||
(rx/of finish-drawing-curve
|
||||
handle-finish-drawing)))))))
|
||||
|
||||
(def handle-finish-drawing
|
||||
(ptk/reify ::handle-finish-drawing
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [shape (get-in state [:workspace-drawing :object])]
|
||||
(rx/concat
|
||||
(rx/of dw/clear-drawing)
|
||||
(when (::initialized? shape)
|
||||
(let [shape-click-width (case (:type shape)
|
||||
:text 3
|
||||
20)
|
||||
shape-click-height (case (:type shape)
|
||||
:text 16
|
||||
20)
|
||||
shape (if (::click-draw? shape)
|
||||
(-> shape
|
||||
(assoc-in [:modifiers :resize-vector]
|
||||
(gpt/point shape-click-width shape-click-height))
|
||||
(assoc-in [:modifiers :resize-origin]
|
||||
(gpt/point (:x shape) (:y shape))))
|
||||
shape)
|
||||
|
||||
shape (cond-> shape
|
||||
(= (:type shape) :text) (assoc :grow-type
|
||||
(if (::click-draw? shape) :auto-width :fixed)))
|
||||
|
||||
shape (-> shape
|
||||
geom/transform-shape
|
||||
(dissoc ::initialized? ::click-draw?))]
|
||||
;; Add & select the created shape to the workspace
|
||||
(rx/concat
|
||||
(if (= :text (:type shape))
|
||||
(rx/of dwc/start-undo-transaction)
|
||||
(rx/empty))
|
||||
|
||||
(rx/of (dw/deselect-all)
|
||||
(dw/add-shape shape))))))))))
|
||||
|
||||
(def close-drawing-path
|
||||
(ptk/reify ::close-drawing-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-drawing :object :close?] true))))
|
||||
|
||||
|
|
92
frontend/src/app/main/data/workspace/drawing/box.cljs
Normal file
|
@ -0,0 +1,92 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.data.workspace.drawing.box
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.snap :as snap]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.data.workspace.drawing.common :as common]))
|
||||
|
||||
(defn resize-shape [{:keys [x y width height] :as shape} point lock? point-snap]
|
||||
(let [;; The new shape behaves like a resize on the bottom-right corner
|
||||
initial (gpt/point (+ x width) (+ y height))
|
||||
shapev (gpt/point width height)
|
||||
deltav (gpt/to-vec initial point-snap)
|
||||
scalev (gpt/divide (gpt/add shapev deltav) shapev)
|
||||
scalev (if lock?
|
||||
(let [v (max (:x scalev) (:y scalev))]
|
||||
(gpt/point v v))
|
||||
scalev)]
|
||||
(-> shape
|
||||
(assoc :click-draw? false)
|
||||
(assoc-in [:modifiers :resize-vector] scalev)
|
||||
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
|
||||
(assoc-in [:modifiers :resize-rotation] 0))))
|
||||
|
||||
(defn update-drawing [state point lock? point-snap]
|
||||
(update-in state [:workspace-drawing :object] resize-shape point lock? point-snap))
|
||||
|
||||
(defn handle-drawing-box []
|
||||
(ptk/reify ::handle-drawing-box
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [flags]} (:workspace-local state)
|
||||
|
||||
stoper? #(or (ms/mouse-up? %) (= % :interrupt))
|
||||
stoper (rx/filter stoper? stream)
|
||||
initial @ms/mouse-position
|
||||
|
||||
|
||||
page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
layout (get state :workspace-layout)
|
||||
|
||||
frames (cph/select-frames objects)
|
||||
fid (or (->> frames
|
||||
(filter #(gsh/has-point? % initial))
|
||||
first
|
||||
:id)
|
||||
uuid/zero)
|
||||
|
||||
shape (-> state
|
||||
(get-in [:workspace-drawing :object])
|
||||
(gsh/setup {:x (:x initial) :y (:y initial) :width 1 :height 1})
|
||||
(assoc :frame-id fid)
|
||||
(assoc :initialized? true)
|
||||
(assoc :click-draw? true))]
|
||||
(rx/concat
|
||||
;; Add shape to drawing state
|
||||
(rx/of #(assoc-in state [:workspace-drawing :object] shape))
|
||||
|
||||
;; Initial SNAP
|
||||
(->> (snap/closest-snap-point page-id [shape] layout initial)
|
||||
(rx/map (fn [{:keys [x y]}]
|
||||
#(update-in % [:workspace-drawing :object] gsh/absolute-move (gpt/point x y))
|
||||
)))
|
||||
|
||||
(->> ms/mouse-position
|
||||
(rx/filter #(> (gpt/distance % initial) 2))
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/switch-map
|
||||
(fn [[point :as current]]
|
||||
(->> (snap/closest-snap-point page-id [shape] layout point)
|
||||
(rx/map #(conj current %)))))
|
||||
(rx/map
|
||||
(fn [[pt ctrl? point-snap]]
|
||||
#(update-drawing % pt ctrl? point-snap)))
|
||||
|
||||
(rx/take-until stoper))
|
||||
(rx/of common/handle-finish-drawing))))))
|
62
frontend/src/app/main/data/workspace/drawing/common.cljs
Normal file
|
@ -0,0 +1,62 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.data.workspace.drawing.common
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.streams :as ms]))
|
||||
|
||||
(def clear-drawing
|
||||
(ptk/reify ::clear-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-drawing dissoc :tool :object))))
|
||||
|
||||
(def handle-finish-drawing
|
||||
(ptk/reify ::handle-finish-drawing
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [shape (get-in state [:workspace-drawing :object])]
|
||||
(rx/concat
|
||||
(rx/of clear-drawing)
|
||||
(when (:initialized? shape)
|
||||
(let [shape-click-width (case (:type shape)
|
||||
:text 3
|
||||
20)
|
||||
shape-click-height (case (:type shape)
|
||||
:text 16
|
||||
20)
|
||||
shape (if (:click-draw? shape)
|
||||
(-> shape
|
||||
(assoc-in [:modifiers :resize-vector]
|
||||
(gpt/point shape-click-width shape-click-height))
|
||||
(assoc-in [:modifiers :resize-origin]
|
||||
(gpt/point (:x shape) (:y shape))))
|
||||
shape)
|
||||
|
||||
shape (cond-> shape
|
||||
(= (:type shape) :text) (assoc :grow-type
|
||||
(if (:click-draw? shape) :auto-width :fixed)))
|
||||
|
||||
shape (-> shape
|
||||
(gsh/transform-shape)
|
||||
(dissoc :initialized? :click-draw?))]
|
||||
;; Add & select the created shape to the workspace
|
||||
(rx/concat
|
||||
(if (= :text (:type shape))
|
||||
(rx/of dwc/start-undo-transaction)
|
||||
(rx/empty))
|
||||
|
||||
(rx/of (dws/deselect-all)
|
||||
(dwc/add-shape shape))))))))))
|
63
frontend/src/app/main/data/workspace/drawing/curve.cljs
Normal file
|
@ -0,0 +1,63 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.data.workspace.drawing.curve
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.geom.path :as path]
|
||||
[app.main.data.workspace.drawing.common :as common]))
|
||||
|
||||
(def simplify-tolerance 0.3)
|
||||
|
||||
(defn stoper-event? [{:keys [type shift] :as event}]
|
||||
(ms/mouse-event? event) (= type :up))
|
||||
|
||||
(defn initialize-drawing [state]
|
||||
(assoc-in state [:workspace-drawing :object :initialized?] true))
|
||||
|
||||
(defn insert-point-segment [state point]
|
||||
(update-in state [:workspace-drawing :object :segments] (fnil conj []) point))
|
||||
|
||||
(defn curve-to-path [{:keys [segments] :as shape}]
|
||||
(let [content (gsp/segments->content segments)
|
||||
selrect (gsh/content->selrect content)
|
||||
points (gsh/rect->points selrect)]
|
||||
(-> shape
|
||||
(dissoc :segments)
|
||||
(assoc :content content)
|
||||
(assoc :selrect selrect)
|
||||
(assoc :points points))))
|
||||
|
||||
(defn finish-drawing-curve [state]
|
||||
(update-in
|
||||
state [:workspace-drawing :object]
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(update :segments #(path/simplify % simplify-tolerance))
|
||||
(curve-to-path)))))
|
||||
|
||||
(defn handle-drawing-curve []
|
||||
(ptk/reify ::handle-drawing-curve
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [flags]} (:workspace-local state)
|
||||
stoper (rx/filter stoper-event? stream)
|
||||
mouse (rx/sample 10 ms/mouse-position)]
|
||||
(rx/concat
|
||||
(rx/of initialize-drawing)
|
||||
(->> mouse
|
||||
(rx/map (fn [pt] #(insert-point-segment % pt)))
|
||||
(rx/take-until stoper))
|
||||
(rx/of finish-drawing-curve
|
||||
common/handle-finish-drawing))))))
|
688
frontend/src/app/main/data/workspace/drawing/path.cljs
Normal file
|
@ -0,0 +1,688 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.data.workspace.drawing.path
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[app.common.math :as mth]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.util.data :as ud]
|
||||
[app.common.data :as cd]
|
||||
[app.util.geom.path :as ugp]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.drawing.common :as common]
|
||||
[app.common.geom.shapes.path :as gsp]))
|
||||
|
||||
;; CONSTANTS
|
||||
(defonce enter-keycode 13)
|
||||
|
||||
|
||||
;; PRIVATE METHODS
|
||||
|
||||
(defn get-path-id
|
||||
"Retrieves the currently editing path id"
|
||||
[state]
|
||||
(or (get-in state [:workspace-local :edition])
|
||||
(get-in state [:workspace-drawing :object :id])))
|
||||
|
||||
(defn get-path
|
||||
"Retrieves the location of the path object and additionaly can pass
|
||||
the arguments. This location can be used in get-in, assoc-in... functions"
|
||||
[state & path]
|
||||
(let [edit-id (get-in state [:workspace-local :edition])
|
||||
page-id (:current-page-id state)]
|
||||
(cd/concat
|
||||
(if edit-id
|
||||
[:workspace-data :pages-index page-id :objects edit-id]
|
||||
[:workspace-drawing :object])
|
||||
path)))
|
||||
|
||||
(defn update-selrect
|
||||
"Updates the selrect and points for a path"
|
||||
[shape]
|
||||
(let [selrect (gsh/content->selrect (:content shape))
|
||||
points (gsh/rect->points selrect)]
|
||||
(assoc shape :points points :selrect selrect)))
|
||||
|
||||
(defn next-node
|
||||
"Calculates the next-node to be inserted."
|
||||
[shape position prev-point prev-handler]
|
||||
(let [last-command (-> shape :content last :command)
|
||||
add-line? (and prev-point (not prev-handler) (not= last-command :close-path))
|
||||
add-curve? (and prev-point prev-handler (not= last-command :close-path))]
|
||||
(cond
|
||||
add-line? {:command :line-to
|
||||
:params position}
|
||||
add-curve? {:command :curve-to
|
||||
:params (ugp/make-curve-params position prev-handler)}
|
||||
:else {:command :move-to
|
||||
:params position})))
|
||||
|
||||
(defn append-node
|
||||
"Creates a new node in the path. Usualy used when drawing."
|
||||
[shape position prev-point prev-handler]
|
||||
(let [command (next-node shape position prev-point prev-handler)]
|
||||
(-> shape
|
||||
(update :content (fnil conj []) command)
|
||||
(update-selrect))))
|
||||
|
||||
(defn move-handler-modifiers [content index prefix match-opposite? dx dy]
|
||||
(let [[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
|
||||
[ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
|
||||
opposite-index (ugp/opposite-index content index prefix)]
|
||||
|
||||
(cond-> {}
|
||||
:always
|
||||
(update index assoc cx dx cy dy)
|
||||
|
||||
(and match-opposite? opposite-index)
|
||||
(update opposite-index assoc ocx (- dx) ocy (- dy)))))
|
||||
|
||||
(defn end-path-event? [{:keys [type shift] :as event}]
|
||||
(or (= event ::end-path)
|
||||
(= (ptk/type event) :esc-pressed)
|
||||
(= event :interrupt) ;; ESC
|
||||
(and (ms/keyboard-event? event)
|
||||
(= type :down)
|
||||
;; TODO: Enter now finish path but can finish drawing/editing as well
|
||||
(= enter-keycode (:key event)))))
|
||||
|
||||
|
||||
;; EVENTS
|
||||
|
||||
(defn init-path [id]
|
||||
(ptk/reify ::init-path))
|
||||
|
||||
(defn finish-path [id]
|
||||
(ptk/reify ::finish-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:workspace-local :edit-path id] dissoc :last-point :prev-handler :drag-handler :preview)))))
|
||||
|
||||
(defn preview-next-point [{:keys [x y]}]
|
||||
(ptk/reify ::preview-next-point
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-path-id state)
|
||||
position (gpt/point x y)
|
||||
shape (get-in state (get-path state))
|
||||
{:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])
|
||||
|
||||
command (next-node shape position last-point prev-handler)]
|
||||
(assoc-in state [:workspace-local :edit-path id :preview] command)))))
|
||||
|
||||
(defn add-node [{:keys [x y]}]
|
||||
(ptk/reify ::add-node
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-path-id state)
|
||||
position (gpt/point x y)
|
||||
{:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :last-point] position)
|
||||
(update-in [:workspace-local :edit-path id] dissoc :prev-handler)
|
||||
(update-in (get-path state) append-node position last-point prev-handler))))))
|
||||
|
||||
(defn start-drag-handler []
|
||||
(ptk/reify ::start-drag-handler
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [content (get-in state (get-path state :content))
|
||||
index (dec (count content))
|
||||
command (get-in state (get-path state :content index :command))
|
||||
|
||||
make-curve
|
||||
(fn [command]
|
||||
(let [params (ugp/make-curve-params
|
||||
(get-in content [index :params])
|
||||
(get-in content [(dec index) :params]))]
|
||||
(-> command
|
||||
(assoc :command :curve-to :params params))))]
|
||||
|
||||
(cond-> state
|
||||
(= command :line-to)
|
||||
(update-in (get-path state :content index) make-curve))))))
|
||||
|
||||
(defn drag-handler [{:keys [x y]}]
|
||||
(ptk/reify ::drag-handler
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
||||
(let [id (get-path-id state)
|
||||
handler-position (gpt/point x y)
|
||||
shape (get-in state (get-path state))
|
||||
content (:content shape)
|
||||
index (dec (count content))
|
||||
node-position (ugp/command->point (nth content index))
|
||||
{dx :x dy :y} (gpt/subtract handler-position node-position)
|
||||
match-opposite? true
|
||||
modifiers (move-handler-modifiers content (inc index) :c1 match-opposite? dx dy)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers)
|
||||
(assoc-in [:workspace-local :edit-path id :prev-handler] handler-position)
|
||||
(assoc-in [:workspace-local :edit-path id :drag-handler] handler-position))))))
|
||||
|
||||
(defn finish-drag []
|
||||
(ptk/reify ::finish-drag
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-path-id state)
|
||||
modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
|
||||
handler (get-in state [:workspace-local :edit-path id :drag-handler])]
|
||||
(-> state
|
||||
(update-in (get-path state :content) ugp/apply-content-modifiers modifiers)
|
||||
(update-in [:workspace-local :edit-path id] dissoc :drag-handler)
|
||||
(update-in [:workspace-local :edit-path id] dissoc :content-modifiers)
|
||||
(assoc-in [:workspace-local :edit-path id :prev-handler] handler)
|
||||
(update-in (get-path state) update-selrect))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-path-id state)
|
||||
handler (get-in state [:workspace-local :edit-path id :prev-handler])]
|
||||
;; Update the preview because can be outdated after the dragging
|
||||
(rx/of (preview-next-point handler))))))
|
||||
|
||||
(defn close-path [position]
|
||||
(ptk/reify ::close-path
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (add-node position)
|
||||
::end-path))))
|
||||
|
||||
(defn close-path-drag-start [position]
|
||||
(ptk/reify ::close-path-drag-start
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [zoom (get-in state [:workspace-local :zoom])
|
||||
threshold (/ 5 zoom)
|
||||
check-if-dragging
|
||||
(fn [current-position]
|
||||
(let [start (gpt/point position)
|
||||
current (gpt/point current-position)]
|
||||
(>= (gpt/distance start current) 100)))
|
||||
|
||||
stop-stream
|
||||
(->> stream (rx/filter #(or (end-path-event? %)
|
||||
(ms/mouse-up? %))))
|
||||
|
||||
position-stream
|
||||
(->> ms/mouse-position
|
||||
(rx/take-until stop-stream)
|
||||
(rx/throttle 50))
|
||||
|
||||
drag-events-stream
|
||||
(->> position-stream
|
||||
(rx/map #(drag-handler %)))]
|
||||
|
||||
|
||||
(rx/concat
|
||||
(rx/of (close-path position))
|
||||
|
||||
(->> position-stream
|
||||
(rx/filter check-if-dragging)
|
||||
(rx/take 1)
|
||||
(rx/merge-map
|
||||
#(rx/concat
|
||||
(rx/of (start-drag-handler))
|
||||
drag-events-stream
|
||||
(rx/of (finish-drag))))))))))
|
||||
|
||||
(defn close-path-drag-end [position]
|
||||
(ptk/reify ::close-path-drag-end))
|
||||
|
||||
(defn path-pointer-enter [position]
|
||||
(ptk/reify ::path-pointer-enter))
|
||||
|
||||
(defn path-pointer-leave [position]
|
||||
(ptk/reify ::path-pointer-leave))
|
||||
|
||||
(defn start-path-from-point [position]
|
||||
(ptk/reify ::start-path-from-point
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [mouse-up (->> stream (rx/filter #(or (end-path-event? %)
|
||||
(ms/mouse-up? %))))
|
||||
drag-events (->> ms/mouse-position
|
||||
(rx/take-until mouse-up)
|
||||
(rx/map #(drag-handler %)))]
|
||||
|
||||
(rx/concat (rx/of (add-node position))
|
||||
(rx/of (start-drag-handler))
|
||||
drag-events
|
||||
(rx/of (finish-drag))))
|
||||
)))
|
||||
|
||||
;; EVENT STREAMS
|
||||
|
||||
(defn make-click-stream
|
||||
[stream down-event]
|
||||
(->> stream
|
||||
(rx/filter ms/mouse-click?)
|
||||
(rx/debounce 200)
|
||||
(rx/first)
|
||||
(rx/map #(add-node down-event))))
|
||||
|
||||
(defn make-drag-stream
|
||||
[stream down-event]
|
||||
(let [mouse-up (->> stream (rx/filter #(or (end-path-event? %)
|
||||
(ms/mouse-up? %))))
|
||||
drag-events (->> ms/mouse-position
|
||||
(rx/take-until mouse-up)
|
||||
(rx/map #(drag-handler %)))]
|
||||
(->> (rx/timer 400)
|
||||
(rx/merge-map #(rx/concat
|
||||
(rx/of (add-node down-event))
|
||||
(rx/of (start-drag-handler))
|
||||
drag-events
|
||||
(rx/of (finish-drag)))))))
|
||||
|
||||
(defn make-dbl-click-stream
|
||||
[stream down-event]
|
||||
(->> stream
|
||||
(rx/filter ms/mouse-double-click?)
|
||||
(rx/first)
|
||||
(rx/merge-map
|
||||
#(rx/of (add-node down-event)
|
||||
::end-path))))
|
||||
|
||||
(defn make-node-events-stream
|
||||
[stream]
|
||||
(->> (rx/merge
|
||||
(->> stream (rx/filter (ptk/type? ::close-path)))
|
||||
(->> stream (rx/filter (ptk/type? ::close-path-drag-start))))
|
||||
(rx/take 1)
|
||||
(rx/merge-map #(rx/empty))))
|
||||
|
||||
;; MAIN ENTRIES
|
||||
|
||||
(defn handle-drawing-path
|
||||
[id]
|
||||
(ptk/reify ::handle-drawing-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-path-id state)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :edit-mode] :draw))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [mouse-down (->> stream (rx/filter ms/mouse-down?))
|
||||
end-path-events (->> stream (rx/filter end-path-event?))
|
||||
|
||||
;; Mouse move preview
|
||||
mousemove-events
|
||||
(->> ms/mouse-position
|
||||
(rx/take-until end-path-events)
|
||||
(rx/throttle 50)
|
||||
(rx/map #(preview-next-point %)))
|
||||
|
||||
;; From mouse down we can have: click, drag and double click
|
||||
mousedown-events
|
||||
(->> mouse-down
|
||||
(rx/take-until end-path-events)
|
||||
(rx/throttle 50)
|
||||
(rx/with-latest merge ms/mouse-position)
|
||||
|
||||
;; We change to the stream that emits the first event
|
||||
(rx/switch-map
|
||||
#(rx/race (make-node-events-stream stream)
|
||||
(make-click-stream stream %)
|
||||
(make-drag-stream stream %)
|
||||
(make-dbl-click-stream stream %))))]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (init-path id))
|
||||
(rx/merge mousemove-events
|
||||
mousedown-events)
|
||||
(rx/of (finish-path id)))))))
|
||||
|
||||
(defn stop-path-edit []
|
||||
(ptk/reify ::stop-path-edit
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])]
|
||||
(update state :workspace-local dissoc :edit-path id)))))
|
||||
|
||||
(defn start-path-edit
|
||||
[id]
|
||||
(ptk/reify ::start-path-edit
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; Only edit if the object has been created
|
||||
(if-let [id (get-in state [:workspace-local :edition])]
|
||||
(assoc-in state [:workspace-local :edit-path id] {:edit-mode :move
|
||||
:selected #{}
|
||||
:snap-toggled true})
|
||||
state))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> stream
|
||||
(rx/filter #(= % :interrupt))
|
||||
(rx/take 1)
|
||||
(rx/map #(stop-path-edit))))))
|
||||
|
||||
(defn modify-point [index prefix dx dy]
|
||||
(ptk/reify ::modify-point
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])
|
||||
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])]
|
||||
(-> state
|
||||
(update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc
|
||||
:c1x dx :c1y dy)
|
||||
(update-in [:workspace-local :edit-path id :content-modifiers index] assoc
|
||||
:x dx :y dy :c2x dx :c2y dy)
|
||||
)))))
|
||||
|
||||
(defn modify-handler [id index prefix dx dy match-opposite?]
|
||||
(ptk/reify ::modify-point
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [content (get-in state (get-path state :content))
|
||||
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
|
||||
[ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
|
||||
opposite-index (ugp/opposite-index content index prefix)]
|
||||
(cond-> state
|
||||
:always
|
||||
(update-in [:workspace-local :edit-path id :content-modifiers index] assoc
|
||||
cx dx cy dy)
|
||||
|
||||
(and match-opposite? opposite-index)
|
||||
(update-in [:workspace-local :edit-path id :content-modifiers opposite-index] assoc
|
||||
ocx (- dx) ocy (- dy)))))))
|
||||
|
||||
(defn apply-content-modifiers []
|
||||
(ptk/reify ::apply-content-modifiers
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:workspace-local :edition])
|
||||
page-id (:current-page-id state)
|
||||
shape (get-in state [:workspace-data :pages-index page-id :objects id])
|
||||
{old-content :content old-selrect :selrect old-points :points} shape
|
||||
content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {})
|
||||
new-content (ugp/apply-content-modifiers old-content content-modifiers)
|
||||
new-selrect (gsh/content->selrect new-content)
|
||||
new-points (gsh/rect->points new-selrect)
|
||||
|
||||
rch [{:type :mod-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:operations [{:type :set :attr :content :val new-content}
|
||||
{:type :set :attr :selrect :val new-selrect}
|
||||
{:type :set :attr :points :val new-points}]}
|
||||
{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes [id]}]
|
||||
|
||||
uch [{:type :mod-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:operations [{:type :set :attr :content :val old-content}
|
||||
{:type :set :attr :selrect :val old-selrect}
|
||||
{:type :set :attr :points :val old-points}]}
|
||||
{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes [id]}]]
|
||||
|
||||
(rx/of (dwc/commit-changes rch uch {:commit-local? true})
|
||||
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers)))))))
|
||||
|
||||
(defn save-path-content []
|
||||
(ptk/reify ::save-path-content
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:workspace-local :edition])
|
||||
page-id (:current-page-id state)
|
||||
old-content (get-in state [:workspace-local :edit-path id :old-content])
|
||||
old-selrect (gsh/content->selrect old-content)
|
||||
old-points (gsh/rect->points old-content)
|
||||
shape (get-in state [:workspace-data :pages-index page-id :objects id])
|
||||
{new-content :content new-selrect :selrect new-points :points} shape
|
||||
|
||||
rch [{:type :mod-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:operations [{:type :set :attr :content :val new-content}
|
||||
{:type :set :attr :selrect :val new-selrect}
|
||||
{:type :set :attr :points :val new-points}]}
|
||||
{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes [id]}]
|
||||
|
||||
uch [{:type :mod-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:operations [{:type :set :attr :content :val old-content}
|
||||
{:type :set :attr :selrect :val old-selrect}
|
||||
{:type :set :attr :points :val old-points}]}
|
||||
{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes [id]}]]
|
||||
|
||||
(rx/of (dwc/commit-changes rch uch {:commit-local? true}))))))
|
||||
|
||||
(declare start-draw-mode)
|
||||
(defn check-changed-content []
|
||||
(ptk/reify ::check-changed-content
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-path-id state)
|
||||
content (get-in state (get-path state :content))
|
||||
old-content (get-in state [:workspace-local :edit-path id :old-content])
|
||||
mode (get-in state [:workspace-local :edit-path id :edit-mode])]
|
||||
|
||||
(cond
|
||||
(not= content old-content) (rx/of (save-path-content)
|
||||
(start-draw-mode))
|
||||
(= mode :draw) (rx/of :interrupt)
|
||||
:else (rx/of (finish-path id)))))))
|
||||
|
||||
(defn move-path-point [start-point end-point]
|
||||
(ptk/reify ::move-point
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-path-id state)
|
||||
content (get-in state (get-path state :content))
|
||||
|
||||
{dx :x dy :y} (gpt/subtract end-point start-point)
|
||||
|
||||
handler-indices (-> (ugp/content->handlers content)
|
||||
(get start-point))
|
||||
|
||||
command-for-point (fn [[index command]]
|
||||
(let [point (ugp/command->point command)]
|
||||
(= point start-point)))
|
||||
|
||||
point-indices (->> (d/enumerate content)
|
||||
(filter command-for-point)
|
||||
(map first))
|
||||
|
||||
|
||||
point-reducer (fn [modifiers index]
|
||||
(-> modifiers
|
||||
(assoc-in [index :x] dx)
|
||||
(assoc-in [index :y] dy)))
|
||||
|
||||
handler-reducer (fn [modifiers [index prefix]]
|
||||
(let [cx (ud/prefix-keyword prefix :x)
|
||||
cy (ud/prefix-keyword prefix :y)]
|
||||
(-> modifiers
|
||||
(assoc-in [index cx] dx)
|
||||
(assoc-in [index cy] dy))))
|
||||
|
||||
modifiers (as-> (get-in state [:workspace-local :edit-path id :content-modifiers] {}) $
|
||||
(reduce point-reducer $ point-indices)
|
||||
(reduce handler-reducer $ handler-indices))]
|
||||
|
||||
(assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers)))))
|
||||
|
||||
(defn start-move-path-point
|
||||
[position]
|
||||
(ptk/reify ::start-move-path-point
|
||||
ptk/WatchEvent
|
||||
;; TODO REWRITE
|
||||
(watch [_ state stream]
|
||||
(let [stopper (->> stream (rx/filter ms/mouse-up?))]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/take-until stopper)
|
||||
(rx/map #(move-path-point position %)))
|
||||
(rx/of (apply-content-modifiers)))))))
|
||||
|
||||
(defn start-move-handler
|
||||
[index prefix]
|
||||
(ptk/reify ::start-move-handler
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:workspace-local :edition])
|
||||
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
|
||||
start-point @ms/mouse-position
|
||||
start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers index cx] 0)
|
||||
start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers index cy] 0)]
|
||||
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
|
||||
(rx/with-latest vector ms/mouse-position-alt)
|
||||
(rx/map
|
||||
(fn [[pos alt?]]
|
||||
(modify-handler
|
||||
id
|
||||
index
|
||||
prefix
|
||||
(+ start-delta-x (- (:x pos) (:x start-point)))
|
||||
(+ start-delta-y (- (:y pos) (:y start-point)))
|
||||
(not alt?))))
|
||||
)
|
||||
(rx/concat (rx/of (apply-content-modifiers))))))))
|
||||
|
||||
(defn start-draw-mode []
|
||||
(ptk/reify ::start-draw-mode
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])
|
||||
page-id (:current-page-id state)
|
||||
old-content (get-in state [:workspace-data :pages-index page-id :objects id :content])]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :old-content] old-content))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:workspace-local :edition])
|
||||
edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])]
|
||||
(if (= :draw edit-mode)
|
||||
(rx/concat
|
||||
(rx/of (handle-drawing-path id))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::finish-path))
|
||||
(rx/take 1)
|
||||
(rx/merge-map #(rx/of (check-changed-content)))))
|
||||
(rx/empty))))))
|
||||
|
||||
(defn change-edit-mode [mode]
|
||||
(ptk/reify ::change-edit-mode
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])]
|
||||
(cond-> state
|
||||
id (assoc-in [:workspace-local :edit-path id :edit-mode] mode))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-path-id state)]
|
||||
(cond
|
||||
(and id (= :move mode)) (rx/of ::end-path)
|
||||
(and id (= :draw mode)) (rx/of (start-draw-mode))
|
||||
:else (rx/empty))))))
|
||||
|
||||
(defn select-handler [index type]
|
||||
(ptk/reify ::select-handler
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])]
|
||||
(-> state
|
||||
(update-in [:workspace-local :edit-path id :selected] (fnil conj #{}) [index type]))))))
|
||||
|
||||
(defn select-node [position]
|
||||
(ptk/reify ::select-node
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])]
|
||||
(-> state
|
||||
(update-in [:workspace-local :edit-path id :selected-node] (fnil conj #{}) position))))))
|
||||
|
||||
(defn deselect-node [position]
|
||||
(ptk/reify ::deselect-node
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])]
|
||||
(-> state
|
||||
(update-in [:workspace-local :edit-path id :selected-node] (fnil disj #{}) position))))))
|
||||
|
||||
(defn add-to-selection-handler [index type]
|
||||
(ptk/reify ::add-to-selection-handler
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
state)))
|
||||
|
||||
(defn add-to-selection-node [index]
|
||||
(ptk/reify ::add-to-selection-node
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
state)))
|
||||
|
||||
(defn remove-from-selection-handler [index]
|
||||
(ptk/reify ::remove-from-selection-handler
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
state)))
|
||||
|
||||
(defn remove-from-selection-node [index]
|
||||
(ptk/reify ::remove-from-selection-handler
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
state)))
|
||||
|
||||
(defn handle-new-shape-result [shape-id]
|
||||
(ptk/reify ::handle-new-shape-result
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [content (get-in state [:workspace-drawing :object :content] [])]
|
||||
(if (> (count content) 1)
|
||||
(assoc-in state [:workspace-drawing :object :initialized?] true)
|
||||
state)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rx/of common/handle-finish-drawing
|
||||
(dwc/start-edition-mode shape-id)
|
||||
(start-path-edit shape-id)
|
||||
(change-edit-mode :draw))))))
|
||||
|
||||
(defn handle-new-shape
|
||||
"Creates a new path shape"
|
||||
[]
|
||||
(ptk/reify ::handle-new-shape
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [shape-id (get-in state [:workspace-drawing :object :id])]
|
||||
(rx/concat
|
||||
(rx/of (handle-drawing-path shape-id))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::finish-path))
|
||||
(rx/take 1)
|
||||
(rx/observe-on :async)
|
||||
(rx/map #(handle-new-shape-result shape-id))))))))
|
|
@ -251,7 +251,7 @@
|
|||
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id group))))))))))
|
||||
(dwc/select-shapes (d/ordered-set (:id group))))))))))
|
||||
|
||||
(defn rename-component
|
||||
[id new-name]
|
||||
|
@ -407,7 +407,7 @@
|
|||
new-shapes)]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id new-shape))))))))
|
||||
(dwc/select-shapes (d/ordered-set (:id new-shape))))))))
|
||||
|
||||
(defn detach-component
|
||||
"Remove all references to components in the shape with the given id,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
[goog.object :as gobj]
|
||||
[potok.core :as ptk]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.attrs :as attrs]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.util.object :as obj]
|
||||
|
@ -125,7 +126,7 @@
|
|||
(map #(if (is-text-node? %)
|
||||
(merge ut/default-text-attrs %)
|
||||
%)))]
|
||||
(geom/get-attrs-multi nodes attrs)))
|
||||
(attrs/get-attrs-multi nodes attrs)))
|
||||
|
||||
(defn current-text-values
|
||||
[{:keys [editor default attrs shape]}]
|
||||
|
|
|
@ -80,10 +80,11 @@
|
|||
(defn start-resize
|
||||
[handler initial ids shape]
|
||||
(letfn [(resize [shape initial resizing-shapes [point lock? point-snap]]
|
||||
(let [{:keys [width height rotation]} shape
|
||||
(let [{:keys [width height]} (:selrect shape)
|
||||
{:keys [rotation]} shape
|
||||
shapev (-> (gpt/point width height))
|
||||
|
||||
rotation (if (#{:curve :path} (:type shape)) 0 rotation)
|
||||
rotation (if (= :path (:type shape)) 0 rotation)
|
||||
|
||||
;; Vector modifiers depending on the handler
|
||||
handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y))
|
||||
|
@ -101,9 +102,11 @@
|
|||
shape-transform (:transform shape (gmt/matrix))
|
||||
shape-transform-inverse (:transform-inverse shape (gmt/matrix))
|
||||
|
||||
shape-center (gsh/center-shape shape)
|
||||
|
||||
;; Resize origin point given the selected handler
|
||||
origin (-> (handler-resize-origin shape handler)
|
||||
(gsh/transform-shape-point shape shape-transform))]
|
||||
origin (-> (handler-resize-origin (:selrect shape) handler)
|
||||
(gsh/transform-point-center shape-center shape-transform))]
|
||||
|
||||
(rx/of (set-modifiers ids
|
||||
{:resize-vector scalev
|
||||
|
@ -170,7 +173,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter ms/mouse-up? stream)
|
||||
group (gsh/selection-rect shapes)
|
||||
group-center (gsh/center group)
|
||||
group-center (gsh/center-selrect group)
|
||||
initial-angle (gpt/angle @ms/mouse-position group-center)
|
||||
calculate-angle (fn [pos ctrl?]
|
||||
(let [angle (- (gpt/angle pos group-center) initial-angle)
|
||||
|
@ -403,7 +406,7 @@
|
|||
#(reduce update-shape % ids-with-children)))))))
|
||||
|
||||
(defn rotation-modifiers [center shape angle]
|
||||
(let [displacement (let [shape-center (gsh/center shape)]
|
||||
(let [displacement (let [shape-center (gsh/center-shape shape)]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/rotate angle center)
|
||||
(gmt/rotate (- angle) shape-center)))]
|
||||
|
@ -416,7 +419,7 @@
|
|||
|
||||
(defn set-rotation
|
||||
([delta-rotation shapes]
|
||||
(set-rotation delta-rotation shapes (-> shapes gsh/selection-rect gsh/center)))
|
||||
(set-rotation delta-rotation shapes (-> shapes gsh/selection-rect gsh/center-selrect)))
|
||||
|
||||
([delta-rotation shapes center]
|
||||
(letfn [(rotate-shape [objects angle shape center]
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.math :as mth]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.align :as gal]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
|
@ -42,9 +43,9 @@
|
|||
(defn- calculate-dimensions
|
||||
[{:keys [objects] :as data} vport]
|
||||
(let [shapes (cph/select-toplevel-shapes objects {:include-frames? true})]
|
||||
(->> (geom/selection-rect shapes)
|
||||
(geom/adjust-to-viewport vport)
|
||||
(geom/fix-invalid-rect-values))))
|
||||
(->> (gsh/selection-rect shapes)
|
||||
(gal/adjust-to-viewport vport)
|
||||
#_(gsh/fix-invalid-rect-values))))
|
||||
|
||||
(declare shape-wrapper-factory)
|
||||
|
||||
|
@ -55,7 +56,7 @@
|
|||
(mf/fnc frame-wrapper
|
||||
[{:keys [shape] :as props}]
|
||||
(let [childs (mapv #(get objects %) (:shapes shape))
|
||||
shape (geom/transform-shape shape)]
|
||||
shape (gsh/transform-shape shape)]
|
||||
[:> shape-container {:shape shape}
|
||||
[:& frame-shape {:shape shape :childs childs}]]))))
|
||||
|
||||
|
@ -78,11 +79,11 @@
|
|||
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
|
||||
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (geom/transform-shape frame shape)
|
||||
(let [shape (-> (gsh/transform-shape shape)
|
||||
(gsh/translate-to-frame frame))
|
||||
opts #js {:shape shape}]
|
||||
[:> shape-container {:shape shape}
|
||||
(case (:type shape)
|
||||
:curve [:> path/path-shape opts]
|
||||
:text [:> text/text-shape opts]
|
||||
:rect [:> rect/rect-shape opts]
|
||||
:path [:> path/path-shape opts]
|
||||
|
|
|
@ -166,7 +166,7 @@
|
|||
(rx/merge-map
|
||||
(fn [[frame selrect]]
|
||||
(let [areas (->> (gsh/selrect->areas (or (:selrect frame)
|
||||
(gsh/rect->rect-shape @refs/vbox)) selrect)
|
||||
(gsh/rect->selrect @refs/vbox)) selrect)
|
||||
(d/mapm #(select-shapes-area page-id shapes objects %2)))
|
||||
snap-x (search-snap-distance selrect :x (:left areas) (:right areas))
|
||||
snap-y (search-snap-distance selrect :y (:top areas) (:bottom areas))]
|
||||
|
@ -195,7 +195,7 @@
|
|||
(or (filter-shapes id)
|
||||
(not (contains? layout :dynamic-alignment)))))
|
||||
shape (if (> (count shapes) 1)
|
||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect)
|
||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
||||
(->> shapes (first)))
|
||||
|
||||
shapes-points (->> shape
|
||||
|
|
|
@ -41,11 +41,10 @@
|
|||
|
||||
(when *assert*
|
||||
(defonce debug-subscription
|
||||
(as-> stream $
|
||||
#_(rx/filter ptk/event? $)
|
||||
(rx/filter (fn [s] (debug? :events)) $)
|
||||
(rx/subscribe $ (fn [event]
|
||||
(println "[stream]: " (repr-event event)))))))
|
||||
(->> stream
|
||||
(rx/filter ptk/event?)
|
||||
(rx/filter (fn [s] (debug? :events)))
|
||||
(rx/subs #(println "[stream]: " (repr-event %))))))
|
||||
(defn emit!
|
||||
([] nil)
|
||||
([event]
|
||||
|
@ -73,6 +72,11 @@
|
|||
(defn ^:export dump-state []
|
||||
(logjs "state" @state))
|
||||
|
||||
(defn ^:export get-state [str-path]
|
||||
(let [path (->> (str/split str-path " ")
|
||||
(map d/read-string))]
|
||||
(clj->js (get-in @state path))))
|
||||
|
||||
(defn ^:export dump-objects []
|
||||
(let [page-id (get @state :current-page-id)]
|
||||
(logjs "state" (get-in @state [:workspace-data :pages-index page-id :objects]))))
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
[v]
|
||||
(instance? MouseEvent v))
|
||||
|
||||
(defn mouse-down?
|
||||
[v]
|
||||
(and (mouse-event? v)
|
||||
(= :down (:type v))))
|
||||
|
||||
(defn mouse-up?
|
||||
[v]
|
||||
(and (mouse-event? v)
|
||||
|
@ -36,6 +41,11 @@
|
|||
(and (mouse-event? v)
|
||||
(= :click (:type v))))
|
||||
|
||||
(defn mouse-double-click?
|
||||
[v]
|
||||
(and (mouse-event? v)
|
||||
(= :double-click (:type v))))
|
||||
|
||||
(defrecord PointerEvent [source pt ctrl shift alt])
|
||||
|
||||
(defn pointer-event?
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
(def default-hotspot-x 12)
|
||||
(def default-hotspot-y 12)
|
||||
(def default-rotation 0)
|
||||
(def default-height 20)
|
||||
|
||||
(defn parse-svg [svg-data]
|
||||
(-> svg-data
|
||||
|
@ -53,25 +54,27 @@
|
|||
(str/replace #"\s+$" "")))
|
||||
|
||||
(defn encode-svg-cursor
|
||||
[id rotation x y]
|
||||
[id rotation x y height]
|
||||
(let [svg-path (str cursor-folder "/" (name id) ".svg")
|
||||
data (-> svg-path io/resource slurp parse-svg uri/percent-encode)
|
||||
transform (if rotation (str " transform='rotate(" rotation ")'") "")
|
||||
data (clojure.pprint/cl-format
|
||||
nil
|
||||
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='20px' height='20px'~A%3E~A%3C/svg%3E\") ~A ~A, auto"
|
||||
transform data x y)]
|
||||
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='20px' height='~Apx'~A%3E~A%3C/svg%3E\") ~A ~A, auto"
|
||||
height transform data x y )]
|
||||
data))
|
||||
|
||||
(defmacro cursor-ref
|
||||
"Creates a static cursor given its name, rotation and x/y hotspot"
|
||||
([id] (encode-svg-cursor id default-rotation default-hotspot-x default-hotspot-y))
|
||||
([id rotation] (encode-svg-cursor id rotation default-hotspot-x default-hotspot-y))
|
||||
([id rotation x y] (encode-svg-cursor id rotation x y)))
|
||||
([id] (encode-svg-cursor id default-rotation default-hotspot-x default-hotspot-y default-height))
|
||||
([id rotation] (encode-svg-cursor id rotation default-hotspot-x default-hotspot-y default-height))
|
||||
([id rotation x y] (encode-svg-cursor id rotation x y default-height))
|
||||
([id rotation x y height] (encode-svg-cursor id rotation x y height))
|
||||
)
|
||||
|
||||
(defmacro cursor-fn
|
||||
"Creates a dynamic cursor that can be rotated in runtime"
|
||||
[id initial]
|
||||
(let [cursor (encode-svg-cursor id "{{rotation}}" default-hotspot-x default-hotspot-y)]
|
||||
(let [cursor (encode-svg-cursor id "{{rotation}}" default-hotspot-x default-hotspot-y default-height)]
|
||||
`(fn [rot#]
|
||||
(str/replace ~cursor "{{rotation}}" (+ ~initial rot#)))))
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.cursors
|
||||
(:require-macros [app.main.ui.cursors :refer [cursor-ref
|
||||
cursor-fn]])
|
||||
(:require-macros [app.main.ui.cursors :refer [cursor-ref cursor-fn]])
|
||||
(:require [rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[app.util.timers :as ts]))
|
||||
|
@ -33,6 +32,10 @@
|
|||
(def rotate (cursor-fn :rotate 90))
|
||||
(def text (cursor-ref :text))
|
||||
(def picker (cursor-ref :picker 0 0 24))
|
||||
(def pointer-node (cursor-ref :pointer-node 0 0 10 32))
|
||||
(def pointer-move (cursor-ref :pointer-move 0 0 10 42))
|
||||
(def pen-node (cursor-ref :pen-node 0 0 10 36))
|
||||
(def comments (cursor-ref :comments 0 2 20))
|
||||
|
||||
(mf/defc debug-preview
|
||||
{::mf/wrap-props false}
|
||||
|
@ -49,7 +52,9 @@
|
|||
[:div {:style {:width "100px"
|
||||
:height "100px"
|
||||
:background-image (-> value (str/replace #"(url\(.*\)).*" "$1"))
|
||||
:background-size "cover"
|
||||
:background-size "contain"
|
||||
:background-repeat "no-repeat"
|
||||
:background-position "center"
|
||||
:cursor value}}]
|
||||
|
||||
[:span {:style {:white-space "nowrap"
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
:rect [:layout :fill :stroke :shadow :blur]
|
||||
:circle [:layout :fill :stroke :shadow :blur]
|
||||
:path [:layout :fill :stroke :shadow :blur]
|
||||
:curve [:layout :fill :stroke :shadow :blur]
|
||||
:image [:image :layout :shadow :blur]
|
||||
:text [:layout :text :shadow :blur]})
|
||||
|
||||
|
|
|
@ -122,11 +122,11 @@
|
|||
(mf/deps objects)
|
||||
#(group-container-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (geom/transform-shape frame shape)
|
||||
(let [shape (-> (geom/transform-shape shape)
|
||||
(geom/translate-to-frame frame))
|
||||
opts #js {:shape shape
|
||||
:frame frame}]
|
||||
(case (:type shape)
|
||||
:curve [:> path-wrapper opts]
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
|
|
|
@ -128,6 +128,16 @@
|
|||
(def checkbox-checked (icon-xref :checkbox-checked))
|
||||
(def checkbox-unchecked (icon-xref :checkbox-unchecked))
|
||||
(def code (icon-xref :code))
|
||||
(def nodes-add (icon-xref :nodes-add))
|
||||
(def nodes-corner (icon-xref :nodes-corner))
|
||||
(def nodes-curve (icon-xref :nodes-curve))
|
||||
(def nodes-join (icon-xref :nodes-join))
|
||||
(def nodes-merge (icon-xref :nodes-merge))
|
||||
(def nodes-remove (icon-xref :nodes-remove))
|
||||
(def nodes-separate (icon-xref :nodes-separate))
|
||||
(def nodes-snap (icon-xref :nodes-snap))
|
||||
(def pen (icon-xref :pen))
|
||||
(def pointer-inner (icon-xref :pointer-inner))
|
||||
|
||||
(def loader-pencil
|
||||
(mf/html
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
(let [shape (unchecked-get props "shape")
|
||||
base-props (unchecked-get props "base-props")
|
||||
elem-name (unchecked-get props "elem-name")
|
||||
{:keys [x y width height]} (geom/shape->rect-shape shape)
|
||||
;; {:keys [x y width height]} (geom/shape->rect-shape shape)
|
||||
{:keys [x y width height]} (:selrect shape)
|
||||
mask-id (mf/use-ctx mask-id-ctx)
|
||||
stroke-id (mf/use-var (uuid/next))
|
||||
stroke-style (:stroke-style shape :none)
|
||||
|
|
|
@ -15,40 +15,21 @@
|
|||
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
|
||||
[app.main.ui.shapes.group :refer [mask-id-ctx]]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.util.object :as obj]))
|
||||
[app.util.object :as obj]
|
||||
[app.util.geom.path :as ugp]))
|
||||
|
||||
;; --- Path Shape
|
||||
|
||||
(defn- render-path
|
||||
[{:keys [segments close?] :as shape}]
|
||||
(let [numsegs (count segments)]
|
||||
(loop [buffer []
|
||||
index 0]
|
||||
(cond
|
||||
(>= index numsegs)
|
||||
(if close?
|
||||
(str/join " " (conj buffer "Z"))
|
||||
(str/join " " buffer))
|
||||
|
||||
(zero? index)
|
||||
(let [{:keys [x y] :as segment} (nth segments index)
|
||||
buffer (conj buffer (str/istr "M~{x},~{y}"))]
|
||||
(recur buffer (inc index)))
|
||||
|
||||
:else
|
||||
(let [{:keys [x y] :as segment} (nth segments index)
|
||||
buffer (conj buffer (str/istr "L~{x},~{y}"))]
|
||||
(recur buffer (inc index)))))))
|
||||
|
||||
(mf/defc path-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
background? (unchecked-get props "background?")
|
||||
{:keys [id x y width height]} (geom/shape->rect-shape shape)
|
||||
;; {:keys [id x y width height]} (geom/shape->rect-shape shape)
|
||||
{:keys [id x y width height]} (:selrect shape)
|
||||
mask-id (mf/use-ctx mask-id-ctx)
|
||||
transform (geom/transform-matrix shape)
|
||||
pdata (render-path shape)
|
||||
pdata (ugp/content->path (:content shape))
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:transform transform
|
||||
|
|
|
@ -149,10 +149,10 @@
|
|||
shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (geom/transform-shape frame shape)
|
||||
(let [shape (-> (geom/transform-shape shape)
|
||||
(geom/translate-to-frame frame))
|
||||
opts #js {:shape shape}]
|
||||
(case (:type shape)
|
||||
:curve [:> path-wrapper opts]
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
[app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
|
||||
[app.main.ui.workspace.scroll :as scroll]
|
||||
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
|
||||
[app.main.ui.workspace.viewport :refer [viewport coordinates]]
|
||||
[app.main.ui.workspace.viewport :refer [viewport viewport-actions coordinates]]
|
||||
[app.util.dom :as dom]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -65,6 +65,7 @@
|
|||
(when (contains? layout :rules)
|
||||
[:& workspace-rules {:local local}])
|
||||
|
||||
[:& viewport-actions]
|
||||
[:& viewport {:file file
|
||||
:local local
|
||||
:layout layout}]]]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.main.data.workspace.drawing :as dd]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.workspace.shapes :as shapes]
|
||||
[app.main.ui.workspace.shapes.path :refer [path-editor]]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.data :as d]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -22,10 +23,13 @@
|
|||
|
||||
(mf/defc draw-area
|
||||
[{:keys [shape zoom] :as props}]
|
||||
(when (:id shape)
|
||||
(case (:type shape)
|
||||
(:path :curve) [:& path-draw-area {:shape shape}]
|
||||
[:& generic-draw-area {:shape shape :zoom zoom}])))
|
||||
|
||||
[:g.draw-area
|
||||
[:& shapes/shape-wrapper {:shape shape}]
|
||||
|
||||
(case (:type shape)
|
||||
:path [:& path-editor {:shape shape :zoom zoom}]
|
||||
#_:default [:& generic-draw-area {:shape shape :zoom zoom}])])
|
||||
|
||||
(mf/defc generic-draw-area
|
||||
[{:keys [shape zoom]}]
|
||||
|
@ -34,43 +38,10 @@
|
|||
(not (d/nan? x))
|
||||
(not (d/nan? y)))
|
||||
|
||||
[:g
|
||||
[:& shapes/shape-wrapper {:shape shape}]
|
||||
[:rect.main {:x x :y y
|
||||
:width width
|
||||
:height height
|
||||
:style {:stroke "#1FDEA7"
|
||||
:fill "transparent"
|
||||
:stroke-width (/ 1 zoom)}}]])))
|
||||
[:rect.main {:x x :y y
|
||||
:width width
|
||||
:height height
|
||||
:style {:stroke "#1FDEA7"
|
||||
:fill "transparent"
|
||||
:stroke-width (/ 1 zoom)}}])))
|
||||
|
||||
(mf/defc path-draw-area
|
||||
[{:keys [shape] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
|
||||
on-click
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/assign-cursor-tooltip nil)
|
||||
dd/close-drawing-path
|
||||
:path/end-path-drawing))
|
||||
|
||||
on-mouse-enter
|
||||
(fn [event]
|
||||
(let [msg (t locale "workspace.viewport.click-to-close-path")]
|
||||
(st/emit! (dw/assign-cursor-tooltip msg))))
|
||||
|
||||
on-mouse-leave
|
||||
(fn [event]
|
||||
(st/emit! (dw/assign-cursor-tooltip nil)))]
|
||||
|
||||
(when-let [{:keys [x y] :as segment} (first (:segments shape))]
|
||||
[:g
|
||||
[:& shapes/shape-wrapper {:shape shape}]
|
||||
(when (not= :curve (:type shape))
|
||||
[:circle.close-bezier
|
||||
{:cx x
|
||||
:cy y
|
||||
:r 5
|
||||
:on-click on-click
|
||||
:on-mouse-enter on-mouse-enter
|
||||
:on-mouse-leave on-mouse-leave}])])))
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.util.debug :refer [debug?]]
|
||||
[app.main.ui.workspace.shapes.outline :refer [outline]]
|
||||
[app.main.ui.measurements :as msr]))
|
||||
[app.main.ui.measurements :as msr]
|
||||
[app.main.ui.workspace.shapes.path :refer [path-editor]]))
|
||||
|
||||
(def rotation-handler-size 25)
|
||||
(def resize-point-radius 4)
|
||||
|
@ -181,7 +182,7 @@
|
|||
on-rotate (obj/get props "on-rotate")
|
||||
current-transform (mf/deref refs/current-transform)
|
||||
|
||||
selrect (geom/shape->rect-shape shape)
|
||||
selrect (:selrect shape)
|
||||
transform (geom/transform-matrix shape)
|
||||
|
||||
tr-shape (geom/transform-shape shape)]
|
||||
|
@ -214,44 +215,6 @@
|
|||
:resize-side [:> resize-side-handler props])))])))
|
||||
|
||||
;; --- Selection Handlers (Component)
|
||||
(mf/defc path-edition-selection-handlers
|
||||
[{:keys [shape modifiers zoom color] :as props}]
|
||||
(letfn [(on-mouse-down [event index]
|
||||
(dom/stop-propagation event)
|
||||
;; TODO: this need code ux refactor
|
||||
(let [stoper (get-edition-stream-stoper)
|
||||
stream (->> (ms/mouse-position-deltas @ms/mouse-position)
|
||||
(rx/take-until stoper))]
|
||||
;; (when @refs/selected-alignment
|
||||
;; (st/emit! (dw/initial-path-point-align (:id shape) index)))
|
||||
(rx/subscribe stream #(on-handler-move % index))))
|
||||
|
||||
(get-edition-stream-stoper []
|
||||
(let [stoper? #(and (ms/mouse-event? %) (= (:type %) :up))]
|
||||
(rx/merge
|
||||
(rx/filter stoper? st/stream)
|
||||
(->> st/stream
|
||||
(rx/filter #(= % :interrupt))
|
||||
(rx/take 1)))))
|
||||
|
||||
(on-handler-move [delta index]
|
||||
(st/emit! (dw/update-path (:id shape) index delta)))]
|
||||
|
||||
(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)]
|
||||
(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 color
|
||||
:style {:cursor cur/move-pointer}}]))])))
|
||||
|
||||
;; TODO: add specs for clarity
|
||||
|
||||
(mf/defc text-edition-selection-handlers
|
||||
|
@ -269,8 +232,8 @@
|
|||
|
||||
(mf/defc multiple-selection-handlers
|
||||
[{:keys [shapes selected zoom color show-distances] :as props}]
|
||||
(let [shape (geom/selection-rect shapes)
|
||||
shape-center (geom/center shape)
|
||||
(let [shape (geom/setup {:type :rect} (geom/selection-rect (->> shapes (map geom/transform-shape))))
|
||||
shape-center (geom/center-shape shape)
|
||||
|
||||
hover-id (-> (mf/deref refs/current-hover) first)
|
||||
hover-id (when-not (d/seek #(= hover-id (:id %)) shapes) hover-id)
|
||||
|
@ -314,7 +277,7 @@
|
|||
hover-id (when-not (= shape-id hover-id) hover-id)
|
||||
hover-shape (mf/deref (refs/object-by-id hover-id))
|
||||
|
||||
shape' (if (debug? :simple-selection) (geom/selection-rect [shape]) shape)
|
||||
shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape)
|
||||
on-resize (fn [current-position initial-position event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/start-resize current-position initial-position #{shape-id} shape')))
|
||||
|
@ -322,7 +285,6 @@
|
|||
on-rotate
|
||||
#(do (dom/stop-propagation %)
|
||||
(st/emit! (dw/start-rotate [shape])))]
|
||||
|
||||
[:*
|
||||
[:& controls {:shape shape'
|
||||
:zoom zoom
|
||||
|
@ -366,12 +328,11 @@
|
|||
[:& text-edition-selection-handlers {:shape shape
|
||||
:zoom zoom
|
||||
:color color}]
|
||||
(and (or (= type :path)
|
||||
(= type :curve))
|
||||
|
||||
(and (= type :path)
|
||||
(= edition (:id shape)))
|
||||
[:& path-edition-selection-handlers {:shape shape
|
||||
:zoom zoom
|
||||
:color color}]
|
||||
[:& path-editor {:zoom zoom
|
||||
:shape shape}]
|
||||
|
||||
:else
|
||||
[:& single-selection-handlers {:shape shape
|
||||
|
|
|
@ -82,7 +82,8 @@
|
|||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
ghost? (unchecked-get props "ghost?")
|
||||
shape (geom/transform-shape frame shape)
|
||||
shape (-> (geom/transform-shape shape)
|
||||
(geom/translate-to-frame frame))
|
||||
opts #js {:shape shape
|
||||
:frame frame}
|
||||
alt? (mf/use-state false)
|
||||
|
@ -107,7 +108,6 @@
|
|||
:on-mouse-leave on-mouse-leave
|
||||
:style {:cursor (if @alt? cur/duplicate nil)}}
|
||||
(case (:type shape)
|
||||
:curve [:> path/path-wrapper opts]
|
||||
:path [:> path/path-wrapper opts]
|
||||
:text [:> text/text-wrapper opts]
|
||||
:group [:> group-wrapper opts]
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
selrect (-> shape :selrect)
|
||||
shape-center (geom/center shape)
|
||||
shape-center (geom/center-shape shape)
|
||||
line-color (rdcolor #js {:seed (str (:id shape))})
|
||||
zoom (mf/deref refs/selected-zoom)]
|
||||
[:g.bounding-box
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
(defn- on-mouse-down
|
||||
[event {:keys [id type] :as shape}]
|
||||
(let [selected @refs/selected-shapes
|
||||
edition @refs/selected-edition
|
||||
selected? (contains? selected id)
|
||||
drawing? @refs/selected-drawing-tool
|
||||
button (.-which (.-nativeEvent event))]
|
||||
|
@ -35,9 +36,8 @@
|
|||
nil
|
||||
|
||||
(= type :frame)
|
||||
(when selected?
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/start-move-selected)))
|
||||
(do (dom/stop-propagation event)
|
||||
(st/emit! (dw/start-move-selected)))
|
||||
|
||||
:else
|
||||
(do
|
||||
|
@ -50,7 +50,8 @@
|
|||
(st/emit! (dw/deselect-all)))
|
||||
(st/emit! (dw/select-shape id))))
|
||||
|
||||
(st/emit! (dw/start-move-selected)))))))
|
||||
(when (not= edition id)
|
||||
(st/emit! (dw/start-move-selected))))))))
|
||||
|
||||
(defn on-context-menu
|
||||
[event shape]
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
(not (:hidden shape)))
|
||||
[:g {:class (when selected? "selected")
|
||||
:on-context-menu on-context-menu
|
||||
:on-double-click on-double-click
|
||||
;; :on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
|
||||
[:& frame-title {:frame shape
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.util.object :as obj]
|
||||
[rumext.util :refer [map->obj]]
|
||||
[app.main.ui.shapes.path :as path]
|
||||
[app.main.refs :as refs]))
|
||||
[app.main.refs :as refs]
|
||||
[app.util.geom.path :as ugp]))
|
||||
|
||||
|
||||
(mf/defc outline
|
||||
|
@ -28,7 +28,7 @@
|
|||
|
||||
outline-type (case (:type shape)
|
||||
:circle "ellipse"
|
||||
(:curve :path) "path"
|
||||
:path "path"
|
||||
"rect")
|
||||
|
||||
common {:fill "transparent"
|
||||
|
@ -44,8 +44,8 @@
|
|||
:rx (/ width 2)
|
||||
:ry (/ height 2)}
|
||||
|
||||
(:curve :path)
|
||||
{:d (path/render-path shape)}
|
||||
:path
|
||||
{:d (ugp/content->path (:content shape))}
|
||||
|
||||
{:x x
|
||||
:y y
|
||||
|
|
|
@ -10,46 +10,290 @@
|
|||
(ns app.main.ui.workspace.shapes.path
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.data :as d]
|
||||
[okulary.core :as l]
|
||||
[app.util.data :as d]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.timers :as ts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.constants :as c]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.drawing :as dr]
|
||||
[app.main.data.workspace.drawing.path :as drp]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.shapes.path :as path]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.main.ui.workspace.shapes.common :as common]))
|
||||
[app.main.ui.workspace.shapes.common :as common]
|
||||
[app.util.geom.path :as ugp]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.main.ui.cursors :as cur]
|
||||
[app.main.ui.icons :as i]))
|
||||
|
||||
(def primary-color "#1FDEA7")
|
||||
(def secondary-color "#DB00FF")
|
||||
(def black-color "#000000")
|
||||
(def white-color "#FFFFFF")
|
||||
(def gray-color "#B1B2B5")
|
||||
|
||||
(def current-edit-path-ref
|
||||
(let [selfn (fn [local]
|
||||
(let [id (:edition local)]
|
||||
(get-in local [:edit-path id])))]
|
||||
(l/derived selfn refs/workspace-local)))
|
||||
|
||||
(defn make-edit-path-ref [id]
|
||||
(mf/use-memo
|
||||
(mf/deps id)
|
||||
(let [selfn #(get-in % [:edit-path id])]
|
||||
#(l/derived selfn refs/workspace-local))))
|
||||
|
||||
(defn make-content-modifiers-ref [id]
|
||||
(mf/use-memo
|
||||
(mf/deps id)
|
||||
(let [selfn #(get-in % [:edit-path id :content-modifiers])]
|
||||
#(l/derived selfn refs/workspace-local))))
|
||||
|
||||
(mf/defc path-wrapper
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
hover? (or (mf/deref refs/current-hover) #{})
|
||||
|
||||
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 (and (not (::dr/initialized? shape)) (hover? (:id shape)))
|
||||
(when (not (::dr/initialized? shape))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dw/start-edition-mode (:id shape)))))))]
|
||||
(st/emit! (dw/start-edition-mode (:id shape))
|
||||
(dw/start-path-edit (:id shape)))))))
|
||||
content-modifiers-ref (make-content-modifiers-ref (:id shape))
|
||||
content-modifiers (mf/deref content-modifiers-ref)
|
||||
editing-id (mf/deref refs/selected-edition)
|
||||
editing? (= editing-id (:id shape))
|
||||
shape (update shape :content ugp/apply-content-modifiers content-modifiers)]
|
||||
|
||||
[:> shape-container {:shape shape
|
||||
:pointer-events (when editing? "none")
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-context-menu on-context-menu}
|
||||
|
||||
[:& path/path-shape {:shape shape
|
||||
:background? true}]]))
|
||||
|
||||
(mf/defc path-actions [{:keys [shape]}]
|
||||
(let [id (mf/deref refs/selected-edition)
|
||||
{:keys [edit-mode selected snap-toggled] :as all} (mf/deref current-edit-path-ref)]
|
||||
[:div.path-actions
|
||||
[:div.viewport-actions-group
|
||||
[:div.viewport-actions-entry {:class (when (= edit-mode :draw) "is-toggled")
|
||||
:on-click #(st/emit! (drp/change-edit-mode :draw))} i/pen]
|
||||
[:div.viewport-actions-entry {:class (when (= edit-mode :move) "is-toggled")
|
||||
:on-click #(st/emit! (drp/change-edit-mode :move))} i/pointer-inner]]
|
||||
|
||||
[:div.viewport-actions-group
|
||||
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-add]
|
||||
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-remove]]
|
||||
|
||||
[:div.viewport-actions-group
|
||||
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-merge]
|
||||
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-join]
|
||||
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-separate]]
|
||||
|
||||
[:div.viewport-actions-group
|
||||
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-corner]
|
||||
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-curve]]
|
||||
|
||||
[:div.viewport-actions-group
|
||||
[:div.viewport-actions-entry {:class (when snap-toggled "is-toggled")} i/nodes-snap]]]))
|
||||
|
||||
|
||||
(mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path?]}]
|
||||
(let [{:keys [x y]} position
|
||||
|
||||
on-enter
|
||||
(fn [event]
|
||||
(st/emit! (drp/path-pointer-enter position)))
|
||||
|
||||
on-leave
|
||||
(fn [event]
|
||||
(st/emit! (drp/path-pointer-leave position)))
|
||||
|
||||
on-click
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
|
||||
(cond
|
||||
(and (= edit-mode :move) (not selected?))
|
||||
(st/emit! (drp/select-node position))
|
||||
|
||||
(and (= edit-mode :move) selected?)
|
||||
(st/emit! (drp/deselect-node position))))
|
||||
|
||||
on-mouse-down
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
|
||||
(cond
|
||||
(= edit-mode :move)
|
||||
(st/emit! (drp/start-move-path-point position))
|
||||
|
||||
(and (= edit-mode :draw) start-path?)
|
||||
(st/emit! (drp/start-path-from-point position))
|
||||
|
||||
(and (= edit-mode :draw) (not start-path?))
|
||||
(st/emit! (drp/close-path-drag-start position))))]
|
||||
[:g.path-point
|
||||
[:circle.path-point
|
||||
{:cx x
|
||||
:cy y
|
||||
:r (/ 3 zoom)
|
||||
:style {:cursor (when (= edit-mode :draw) cur/pen-node)
|
||||
:stroke-width (/ 1 zoom)
|
||||
:stroke (cond (or selected? hover?) black-color
|
||||
preview? secondary-color
|
||||
:else primary-color)
|
||||
:fill (cond selected? primary-color
|
||||
:else white-color)}}]
|
||||
[:circle {:cx x
|
||||
:cy y
|
||||
:r (/ 10 zoom)
|
||||
:on-click on-click
|
||||
:on-mouse-down on-mouse-down
|
||||
:style {:fill "transparent"}}]]))
|
||||
|
||||
(mf/defc path-handler [{:keys [index prefix point handler zoom selected? hover? edit-mode]}]
|
||||
(when (and point handler)
|
||||
(let [{:keys [x y]} handler
|
||||
on-click
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(cond
|
||||
(= edit-mode :move)
|
||||
(drp/select-handler index prefix)))
|
||||
|
||||
on-mouse-down
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
|
||||
(cond
|
||||
(= edit-mode :move)
|
||||
(st/emit! (drp/start-move-handler index prefix))))]
|
||||
|
||||
[:g.handler {:pointer-events (when (= edit-mode :draw))}
|
||||
[:line
|
||||
{:x1 (:x point)
|
||||
:y1 (:y point)
|
||||
:x2 x
|
||||
:y2 y
|
||||
:style {:stroke gray-color
|
||||
:stroke-width (/ 1 zoom)}}]
|
||||
[:rect
|
||||
{:x (- x (/ 3 zoom))
|
||||
:y (- y (/ 3 zoom))
|
||||
:width (/ 6 zoom)
|
||||
:height (/ 6 zoom)
|
||||
|
||||
:style {:cursor cur/pointer-move
|
||||
:stroke-width (/ 1 zoom)
|
||||
:stroke (cond (or selected? hover?) black-color
|
||||
:else primary-color)
|
||||
:fill (cond selected? primary-color
|
||||
:else white-color)}}]
|
||||
[:circle {:cx x
|
||||
:cy y
|
||||
:r (/ 10 zoom)
|
||||
:on-click on-click
|
||||
:on-mouse-down on-mouse-down
|
||||
:style {:fill "transparent"}}]])))
|
||||
|
||||
(mf/defc path-preview [{:keys [zoom command from]}]
|
||||
[:g.preview {:style {:pointer-events "none"}}
|
||||
(when (not= :move-to (:command command))
|
||||
[:path {:style {:fill "transparent"
|
||||
:stroke secondary-color
|
||||
:stroke-width (/ 1 zoom)}
|
||||
:d (ugp/content->path [{:command :move-to
|
||||
:params {:x (:x from)
|
||||
:y (:y from)}}
|
||||
command])}])
|
||||
[:& path-point {:position (:params command)
|
||||
:preview? true
|
||||
:zoom zoom}]])
|
||||
|
||||
(mf/defc path-editor
|
||||
[{:keys [shape zoom]}]
|
||||
|
||||
(let [edit-path-ref (make-edit-path-ref (:id shape))
|
||||
{:keys [edit-mode selected drag-handler prev-handler preview content-modifiers last-point]} (mf/deref edit-path-ref)
|
||||
{:keys [content]} shape
|
||||
selected (or selected #{})
|
||||
content (ugp/apply-content-modifiers content content-modifiers)
|
||||
points (->> content ugp/content->points (into #{}))
|
||||
last-command (last content)
|
||||
last-p (->> content last ugp/command->point)
|
||||
handlers (ugp/content->handlers content)]
|
||||
|
||||
[:g.path-editor
|
||||
(when (and preview (not drag-handler))
|
||||
[:& path-preview {:command preview
|
||||
:from last-p
|
||||
:zoom zoom}])
|
||||
|
||||
(for [position points]
|
||||
[:g.path-node
|
||||
[:& path-point {:position position
|
||||
:selected? false
|
||||
:zoom zoom
|
||||
:edit-mode edit-mode
|
||||
:start-path? (nil? last-point)}]
|
||||
|
||||
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
|
||||
(for [[index prefix] (get handlers position)]
|
||||
(let [command (get content index)
|
||||
x (get-in command [:params (d/prefix-keyword prefix :x)])
|
||||
y (get-in command [:params (d/prefix-keyword prefix :y)])
|
||||
handler-position (gpt/point x y)]
|
||||
[:& path-handler {:point position
|
||||
:handler handler-position
|
||||
:index index
|
||||
:prefix prefix
|
||||
:zoom zoom
|
||||
:selected? false
|
||||
:hover? false
|
||||
:preview? false
|
||||
:edit-mode edit-mode}]))]])
|
||||
|
||||
(when prev-handler
|
||||
[:g.prev-handler {:pointer-events "none"}
|
||||
[:& path-handler {:point last-p
|
||||
:handler prev-handler
|
||||
:zoom zoom
|
||||
:selected false}]])
|
||||
|
||||
(when drag-handler
|
||||
[:g.drag-handler {:pointer-events "none"}
|
||||
(when (not= :move-to (:command last-command))
|
||||
[:& path-handler {:point last-p
|
||||
:handler (ugp/opposite-handler last-p drag-handler)
|
||||
:zoom zoom
|
||||
:selected false}])
|
||||
[:& path-handler {:point last-p
|
||||
:handler drag-handler
|
||||
:zoom zoom
|
||||
:selected false}]])]))
|
||||
|
|
|
@ -129,7 +129,6 @@
|
|||
:rect i/box
|
||||
:circle i/circle
|
||||
:text i/text
|
||||
:curve i/curve
|
||||
:path i/curve
|
||||
:frame i/artboard
|
||||
:group i/folder
|
||||
|
@ -141,7 +140,7 @@
|
|||
i/layers))
|
||||
|
||||
(defn is-shape? [type]
|
||||
#{:shape :rect :circle :text :curve :path :frame :group})
|
||||
#{:shape :rect :circle :text :path :frame :group})
|
||||
|
||||
(defn parse-entry [{:keys [redo-changes]}]
|
||||
(->> redo-changes
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
:circle i/circle
|
||||
:path i/curve
|
||||
:rect i/box
|
||||
:curve i/curve
|
||||
:text i/text
|
||||
:group (if (some? (:component-id shape))
|
||||
i/component
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
:icon [:& icon/options {:shape shape}]
|
||||
:circle [:& circle/options {:shape shape}]
|
||||
:path [:& path/options {:shape shape}]
|
||||
:curve [:& path/options {:shape shape}]
|
||||
:image [:& image/options {:shape shape}]
|
||||
nil)
|
||||
[:& exports-menu
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
(ns app.main.ui.workspace.sidebar.options.group
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.attrs :as attrs]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -43,7 +44,7 @@
|
|||
(merge
|
||||
;; All values extracted from the group shape, except
|
||||
;; border radius, that needs to be looked up from children
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
measure-attrs
|
||||
nil
|
||||
|
@ -51,7 +52,7 @@
|
|||
nil)
|
||||
[shape])
|
||||
measure-attrs)
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
[:rx :ry]
|
||||
nil
|
||||
|
@ -64,10 +65,10 @@
|
|||
(select-keys shape component-attrs)
|
||||
|
||||
fill-values
|
||||
(geom/get-attrs-multi shape-with-children fill-attrs)
|
||||
(attrs/get-attrs-multi shape-with-children fill-attrs)
|
||||
|
||||
stroke-values
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
stroke-attrs
|
||||
nil
|
||||
|
@ -77,7 +78,7 @@
|
|||
stroke-attrs)
|
||||
|
||||
font-values
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
nil
|
||||
text-font-attrs
|
||||
|
@ -87,7 +88,7 @@
|
|||
text-font-attrs)
|
||||
|
||||
align-values
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
nil
|
||||
text-align-attrs
|
||||
|
@ -97,7 +98,7 @@
|
|||
text-align-attrs)
|
||||
|
||||
spacing-values
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
nil
|
||||
text-spacing-attrs
|
||||
|
@ -107,7 +108,7 @@
|
|||
text-spacing-attrs)
|
||||
|
||||
valign-values
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
nil
|
||||
text-valign-attrs
|
||||
|
@ -117,7 +118,7 @@
|
|||
text-valign-attrs)
|
||||
|
||||
decoration-values
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
nil
|
||||
text-decoration-attrs
|
||||
|
@ -127,7 +128,7 @@
|
|||
text-decoration-attrs)
|
||||
|
||||
transform-values
|
||||
(geom/get-attrs-multi (map #(get-shape-attrs
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
nil
|
||||
text-transform-attrs
|
||||
|
|
|
@ -43,11 +43,15 @@
|
|||
|
||||
old-shapes (deref (refs/objects-by-id ids))
|
||||
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
|
||||
shapes (map gsh/transform-shape frames old-shapes)
|
||||
|
||||
values (cond-> values
|
||||
(not= (:x values) :multiple) (assoc :x (:x (:selrect (first shapes))))
|
||||
(not= (:y values) :multiple) (assoc :y (:y (:selrect (first shapes)))))
|
||||
shapes (as-> old-shapes $
|
||||
(map gsh/transform-shape $)
|
||||
(map gsh/translate-to-frame $ frames))
|
||||
|
||||
values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)]
|
||||
(cond-> values
|
||||
(not= (:x values) :multiple) (assoc :x x)
|
||||
(not= (:y values) :multiple) (assoc :y y)))
|
||||
|
||||
proportion-lock (:proportion-lock values)
|
||||
|
||||
|
@ -65,7 +69,7 @@
|
|||
|
||||
do-position-change
|
||||
(fn [shape' frame' value attr]
|
||||
(let [from (-> shape' :selrect attr)
|
||||
(let [from (-> shape' :points gsh/points->selrect attr)
|
||||
to (+ value (attr frame'))
|
||||
target (+ (attr shape') (- to from))]
|
||||
(st/emit! (udw/update-position (:id shape') {attr target}))))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.attrs :as attrs]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]]
|
||||
|
@ -48,9 +49,9 @@
|
|||
text-attrs
|
||||
convert-attrs
|
||||
extract-fn))]
|
||||
(geom/get-attrs-multi (map mapfn shapes) (or attrs text-attrs))))
|
||||
(attrs/get-attrs-multi (map mapfn shapes) (or attrs text-attrs))))
|
||||
|
||||
measure-values (geom/get-attrs-multi shapes measure-attrs)
|
||||
measure-values (attrs/get-attrs-multi shapes measure-attrs)
|
||||
|
||||
fill-values (extract {:attrs fill-attrs
|
||||
:text-attrs ot/text-fill-attrs
|
||||
|
|
|
@ -141,8 +141,9 @@
|
|||
(fn [[selrect selected frame]]
|
||||
(let [lt-side (if (= coord :x) :left :top)
|
||||
gt-side (if (= coord :x) :right :bottom)
|
||||
areas (gsh/selrect->areas (or (:selrect frame)
|
||||
(gsh/rect->rect-shape @refs/vbox)) selrect)
|
||||
container-selrec (or (:selrect frame)
|
||||
(gsh/rect->selrect @refs/vbox))
|
||||
areas (gsh/selrect->areas container-selrec selrect)
|
||||
query-side (fn [side]
|
||||
(->> (uw/ask! {:cmd :selection/query
|
||||
:page-id page-id
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
(defn get-snap
|
||||
[coord {:keys [shapes page-id filter-shapes local]}]
|
||||
(let [shape (if (> (count shapes) 1)
|
||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect)
|
||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
||||
(->> shapes (first)))
|
||||
|
||||
shape (if (:modifiers local)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
; 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/.
|
||||
;;
|
||||
|
@ -52,7 +52,8 @@
|
|||
[goog.events :as events]
|
||||
[potok.core :as ptk]
|
||||
[promesa.core :as p]
|
||||
[rumext.alpha :as mf])
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.ui.workspace.shapes.path :refer [path-actions]])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; --- Coordinates Widget
|
||||
|
@ -198,17 +199,21 @@
|
|||
vport
|
||||
vbox
|
||||
edition
|
||||
edit-path
|
||||
tooltip
|
||||
selected
|
||||
panning
|
||||
picking-color?]} local
|
||||
|
||||
page-id (mf/use-ctx ctx/current-page-id)
|
||||
selrect-orig (->> (mf/deref refs/selected-objects)
|
||||
(gsh/selection-rect))
|
||||
selrect (-> selrect-orig
|
||||
(assoc :modifiers (:modifiers local))
|
||||
(gsh/transform-shape))
|
||||
|
||||
selected-objects (mf/deref refs/selected-objects)
|
||||
selrect-orig (->> selected-objects
|
||||
(gsh/selection-rect))
|
||||
selrect (->> selected-objects
|
||||
(map #(assoc % :modifiers (:modifiers local)))
|
||||
(map gsh/transform-shape)
|
||||
(gsh/selection-rect))
|
||||
|
||||
alt? (mf/use-state false)
|
||||
viewport-ref (mf/use-ref nil)
|
||||
|
@ -217,9 +222,9 @@
|
|||
drawing (mf/deref refs/workspace-drawing)
|
||||
drawing-tool (:tool drawing)
|
||||
drawing-obj (:object drawing)
|
||||
drawing-path? (and edition (= :draw (get-in edit-path [edition :edit-mode])))
|
||||
zoom (or zoom 1)
|
||||
|
||||
|
||||
on-mouse-down
|
||||
(mf/use-callback
|
||||
(mf/deps drawing-tool edition)
|
||||
|
@ -231,14 +236,13 @@
|
|||
alt? (kbd/alt? event)]
|
||||
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt?))
|
||||
(cond
|
||||
(and (= 1 (.-which event)))
|
||||
(and (= 1 (.-which event)) (not edition))
|
||||
(if drawing-tool
|
||||
(when (not= drawing-tool :comments)
|
||||
(when (not (#{:comments :path} drawing-tool))
|
||||
(st/emit! (dd/start-drawing drawing-tool)))
|
||||
(st/emit! dw/handle-selection))
|
||||
|
||||
(and (not edition)
|
||||
(= 2 (.-which event)))
|
||||
(and (= 2 (.-which event)))
|
||||
(handle-viewport-positioning viewport-ref)))))
|
||||
|
||||
on-context-menu
|
||||
|
@ -265,18 +269,18 @@
|
|||
|
||||
on-pointer-down
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)]
|
||||
; Capture mouse pointer to detect the movements even if cursor
|
||||
; leaves the viewport or the browser itself
|
||||
; https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
|
||||
; Capture mouse pointer to detect the movements even if cursor
|
||||
; leaves the viewport or the browser itself
|
||||
; https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
|
||||
(.setPointerCapture target (.-pointerId event)))))
|
||||
|
||||
on-pointer-up
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)]
|
||||
; Release pointer on mouse up
|
||||
; Release pointer on mouse up
|
||||
(.releasePointerCapture target (.-pointerId event)))))
|
||||
|
||||
on-click
|
||||
|
@ -290,12 +294,16 @@
|
|||
|
||||
on-double-click
|
||||
(mf/use-callback
|
||||
(mf/deps edition edit-path)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
alt? (kbd/alt? event)]
|
||||
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?)))))
|
||||
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?))
|
||||
|
||||
(if (not drawing-path?)
|
||||
(st/emit! dw/clear-edition-mode)))))
|
||||
|
||||
on-key-down
|
||||
(mf/use-callback
|
||||
|
@ -425,6 +433,7 @@
|
|||
final-x (- (:x viewport-coord) (/ (:width shape) 2))
|
||||
final-y (- (:y viewport-coord) (/ (:height shape) 2))]
|
||||
(st/emit! (dw/add-shape (-> shape
|
||||
(assoc :id (uuid/next))
|
||||
(assoc :x final-x)
|
||||
(assoc :y final-y)))))
|
||||
|
||||
|
@ -527,12 +536,12 @@
|
|||
:class (when drawing-tool "drawing")
|
||||
:style {:cursor (cond
|
||||
panning cur/hand
|
||||
(= drawing-tool :comments) cur/hand
|
||||
(= drawing-tool :comments) cur/comments
|
||||
(= drawing-tool :frame) cur/create-artboard
|
||||
(= drawing-tool :rect) cur/create-rectangle
|
||||
(= drawing-tool :circle) cur/create-ellipse
|
||||
(= drawing-tool :path) cur/pen
|
||||
(= drawing-tool :curve)cur/pencil
|
||||
(or (= drawing-tool :path) drawing-path?) cur/pen
|
||||
(= drawing-tool :curve) cur/pencil
|
||||
drawing-tool cur/create-shape
|
||||
:else cur/pointer-inner)
|
||||
:background-color (get options :background "#E8E9EA")}
|
||||
|
@ -606,3 +615,13 @@
|
|||
(when (= options-mode :prototype)
|
||||
[:& interactions {:selected selected}])]]))
|
||||
|
||||
|
||||
(mf/defc viewport-actions []
|
||||
(let [edition (mf/deref refs/selected-edition)
|
||||
selected (mf/deref refs/selected-objects)
|
||||
shape (-> selected first)]
|
||||
(when (and (= (count selected) 1)
|
||||
(= (:id shape) edition)
|
||||
(= :path (:type shape)))
|
||||
[:div.viewport-actions
|
||||
[:& path-actions {:shape shape}]])))
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
[app.util.worker :as uw]))
|
||||
|
||||
(defn on-error
|
||||
[instance error]
|
||||
(js/console.error "Error on worker" (.-data error)))
|
||||
[error]
|
||||
(js/console.error "Error on worker" error))
|
||||
|
||||
(defonce instance
|
||||
(when (not= *target* "nodejs")
|
||||
|
|
|
@ -118,6 +118,33 @@
|
|||
(into {}))
|
||||
m1))
|
||||
|
||||
(defn with-next
|
||||
"Given a collectin will return a new collection where each element
|
||||
is paried with the next item in the collection
|
||||
(with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]"
|
||||
[coll]
|
||||
(map vector
|
||||
coll
|
||||
(concat [] (rest coll) [nil])))
|
||||
|
||||
(defn with-prev
|
||||
"Given a collectin will return a new collection where each element
|
||||
is paried with the previous item in the collection
|
||||
(with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]"
|
||||
[coll]
|
||||
(map vector
|
||||
coll
|
||||
(concat [nil] coll)))
|
||||
|
||||
(defn with-prev-next
|
||||
"Given a collection will return a new collection where every item is paired
|
||||
with the previous and the next item of a collection
|
||||
(with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]"
|
||||
[coll]
|
||||
(map vector
|
||||
coll
|
||||
(concat [nil] coll)
|
||||
(concat [] (rest coll) [nil])))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Numbers Parsing
|
||||
|
@ -221,3 +248,7 @@
|
|||
;; nil
|
||||
;; (throw e#)))))))
|
||||
|
||||
(defn prefix-keyword [prefix kw]
|
||||
(let [prefix (if (keyword? prefix) (name prefix) prefix)
|
||||
kw (if (keyword? kw) (name kw) kw)]
|
||||
(keyword (str prefix kw))))
|
||||
|
|
|
@ -8,7 +8,12 @@
|
|||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns app.util.geom.path
|
||||
(:require [app.util.geom.path-impl-simplify :as impl-simplify]))
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[app.util.data :as d]
|
||||
[app.common.data :as cd]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.util.geom.path-impl-simplify :as impl-simplify]))
|
||||
|
||||
(defn simplify
|
||||
([points]
|
||||
|
@ -16,3 +21,269 @@
|
|||
([points tolerance]
|
||||
(let [points (into-array points)]
|
||||
(into [] (impl-simplify/simplify points tolerance true)))))
|
||||
|
||||
;;
|
||||
(def commands-regex #"(?i)[a-z][^a-z]*")
|
||||
|
||||
;; Matches numbers for path values allows values like... -.01, 10, +12.22
|
||||
;; 0 and 1 are special because can refer to flags
|
||||
(def num-regex #"([+-]?(([1-9]\d*(\.\d+)?)|(\.\d+)|0|1))")
|
||||
|
||||
|
||||
(defn coord-n [size]
|
||||
(re-pattern (str "(?i)[a-z]\\s*"
|
||||
(->> (range size)
|
||||
(map #(identity num-regex))
|
||||
(str/join "\\s+")))))
|
||||
|
||||
|
||||
(defn parse-params [cmd-str num-params]
|
||||
(let [fix-starting-dot (fn [arg] (str/replace arg #"([^\d]|^)\." "$10."))]
|
||||
(->> (re-seq num-regex cmd-str)
|
||||
(map first)
|
||||
(map fix-starting-dot)
|
||||
(map d/read-string)
|
||||
(partition num-params))))
|
||||
|
||||
(defn command->param-list [{:keys [command params]}]
|
||||
(case command
|
||||
(:move-to :line-to :smooth-quadratic-bezier-curve-to)
|
||||
(let [{:keys [x y]} params] [x y])
|
||||
|
||||
:close-path
|
||||
[]
|
||||
|
||||
(:line-to-horizontal :line-to-vertical)
|
||||
(let [{:keys [value]} params] [value])
|
||||
|
||||
:curve-to
|
||||
(let [{:keys [c1x c1y c2x c2y x y]} params] [c1x c1y c2x c2y x y])
|
||||
|
||||
(:smooth-curve-to :quadratic-bezier-curve-to)
|
||||
(let [{:keys [cx cy x y]} params] [cx cy x y])
|
||||
|
||||
:elliptical-arc
|
||||
(let [{:keys [rx ry x-axis-rotation large-arc-flag sweep-flag x y]} params]
|
||||
[rx ry x-axis-rotation large-arc-flag sweep-flag x y])))
|
||||
|
||||
;; Path specification
|
||||
;; https://www.w3.org/TR/SVG11/paths.html
|
||||
(defmulti parse-command (comp str/upper first))
|
||||
|
||||
(defmethod parse-command "M" [cmd]
|
||||
(let [relative (str/starts-with? cmd "m")
|
||||
params (parse-params cmd 2)]
|
||||
(for [[x y] params]
|
||||
{:command :move-to
|
||||
:relative relative
|
||||
:params {:x x :y y}})))
|
||||
|
||||
(defmethod parse-command "Z" [cmd]
|
||||
[{:command :close-path}])
|
||||
|
||||
(defmethod parse-command "L" [cmd]
|
||||
(let [relative (str/starts-with? cmd "l")
|
||||
params (parse-params cmd 2)]
|
||||
(for [[x y] params]
|
||||
{:command :line-to
|
||||
:relative relative
|
||||
:params {:x x :y y}})))
|
||||
|
||||
(defmethod parse-command "H" [cmd]
|
||||
(let [relative (str/starts-with? cmd "h")
|
||||
params (parse-params cmd 1)]
|
||||
(for [[value] params]
|
||||
{:command :line-to-horizontal
|
||||
:relative relative
|
||||
:params {:value value}})))
|
||||
|
||||
(defmethod parse-command "V" [cmd]
|
||||
(let [relative (str/starts-with? cmd "v")
|
||||
params (parse-params cmd 1)]
|
||||
(for [[value] params]
|
||||
{:command :line-to-vertical
|
||||
:relative relative
|
||||
:params {:value value}})))
|
||||
|
||||
(defmethod parse-command "C" [cmd]
|
||||
(let [relative (str/starts-with? cmd "c")
|
||||
params (parse-params cmd 6)]
|
||||
(for [[c1x c1y c2x c2y x y] params]
|
||||
{:command :curve-to
|
||||
:relative relative
|
||||
:params {:c1x c1x
|
||||
:c1y c1y
|
||||
:c2x c2x
|
||||
:c2y c2y
|
||||
:x x
|
||||
:y y}})))
|
||||
|
||||
(defmethod parse-command "S" [cmd]
|
||||
(let [relative (str/starts-with? cmd "s")
|
||||
params (parse-params cmd 4)]
|
||||
(for [[cx cy x y] params]
|
||||
{:command :smooth-curve-to
|
||||
:relative relative
|
||||
:params {:cx cx
|
||||
:cy cy
|
||||
:x x
|
||||
:y y}})))
|
||||
|
||||
(defmethod parse-command "Q" [cmd]
|
||||
(let [relative (str/starts-with? cmd "s")
|
||||
params (parse-params cmd 4)]
|
||||
(for [[cx cy x y] params]
|
||||
{:command :quadratic-bezier-curve-to
|
||||
:relative relative
|
||||
:params {:cx cx
|
||||
:cy cy
|
||||
:x x
|
||||
:y y}})))
|
||||
|
||||
(defmethod parse-command "T" [cmd]
|
||||
(let [relative (str/starts-with? cmd "t")
|
||||
params (parse-params cmd (coord-n 2))]
|
||||
(for [[cx cy x y] params]
|
||||
{:command :smooth-quadratic-bezier-curve-to
|
||||
:relative relative
|
||||
:params {:x x
|
||||
:y y}})))
|
||||
|
||||
(defmethod parse-command "A" [cmd]
|
||||
(let [relative (str/starts-with? cmd "a")
|
||||
params (parse-params cmd 7)]
|
||||
(for [[rx ry x-axis-rotation large-arc-flag sweep-flag x y] params]
|
||||
{:command :elliptical-arc
|
||||
:relative relative
|
||||
:params {:rx rx
|
||||
:ry ry
|
||||
:x-axis-rotation x-axis-rotation
|
||||
:large-arc-flag large-arc-flag
|
||||
:sweep-flag sweep-flag
|
||||
:x x
|
||||
:y y}})))
|
||||
|
||||
(defn command->string [{:keys [command relative params] :as entry}]
|
||||
(let [command-str (case command
|
||||
:move-to "M"
|
||||
:close-path "Z"
|
||||
:line-to "L"
|
||||
:line-to-horizontal "H"
|
||||
:line-to-vertical "V"
|
||||
:curve-to "C"
|
||||
:smooth-curve-to "S"
|
||||
:quadratic-bezier-curve-to "Q"
|
||||
:smooth-quadratic-bezier-curve-to "T"
|
||||
:elliptical-arc "A")
|
||||
command-str (if relative (str/lower command-str) command-str)
|
||||
param-list (command->param-list entry)]
|
||||
(str/fmt "%s%s" command-str (str/join " " param-list))))
|
||||
|
||||
(defn path->content [string]
|
||||
(let [clean-string (-> string
|
||||
(str/trim)
|
||||
;; Change "commas" for spaces
|
||||
(str/replace #"," " ")
|
||||
;; Remove all consecutive spaces
|
||||
(str/replace #"\s+" " "))
|
||||
commands (re-seq commands-regex clean-string)]
|
||||
(mapcat parse-command commands)))
|
||||
|
||||
(defn content->path [content]
|
||||
(->> content
|
||||
(map command->string)
|
||||
(str/join "")))
|
||||
|
||||
(defn make-curve-params
|
||||
([point]
|
||||
(make-curve-params point point point))
|
||||
|
||||
([point handler] (make-curve-params point handler point))
|
||||
|
||||
([point h1 h2]
|
||||
{:x (:x point)
|
||||
:y (:y point)
|
||||
:c1x (:x h1)
|
||||
:c1y (:y h1)
|
||||
:c2x (:x h2)
|
||||
:c2y (:y h2)}))
|
||||
|
||||
(defn opposite-handler
|
||||
"Calculates the coordinates of the opposite handler"
|
||||
[point handler]
|
||||
(let [phv (gpt/to-vec point handler)]
|
||||
(gpt/add point (gpt/negate phv))))
|
||||
|
||||
(defn opposite-handler-keep-distance
|
||||
"Calculates the coordinates of the opposite handler but keeping the old distance"
|
||||
[point handler old-opposite]
|
||||
(let [old-distance (gpt/distance point old-opposite)
|
||||
phv (gpt/to-vec point handler)
|
||||
phv2 (gpt/multiply
|
||||
(gpt/unit (gpt/negate phv))
|
||||
(gpt/point old-distance))]
|
||||
(gpt/add point phv2)))
|
||||
|
||||
(defn apply-content-modifiers [content modifiers]
|
||||
(letfn [(apply-to-index [content [index params]]
|
||||
(if (contains? content index)
|
||||
(cond-> content
|
||||
(and
|
||||
(or (:c1x params) (:c1y params) (:c2x params) (:c2y params))
|
||||
(= :line-to (get-in content [index :params :command])))
|
||||
(-> (assoc-in [index :command] :curve-to)
|
||||
(assoc-in [index :params] :curve-to) (make-curve-params
|
||||
(get-in content [index :params])
|
||||
(get-in content [(dec index) :params])))
|
||||
|
||||
(:x params) (update-in [index :params :x] + (:x params))
|
||||
(:y params) (update-in [index :params :y] + (:y params))
|
||||
|
||||
(:c1x params) (update-in [index :params :c1x] + (:c1x params))
|
||||
(:c1y params) (update-in [index :params :c1y] + (:c1y params))
|
||||
|
||||
(:c2x params) (update-in [index :params :c2x] + (:c2x params))
|
||||
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
|
||||
content))]
|
||||
(reduce apply-to-index content modifiers)))
|
||||
|
||||
(defn command->point [{{:keys [x y]} :params}]
|
||||
(gpt/point x y))
|
||||
|
||||
(defn content->points [content]
|
||||
(->> content
|
||||
(map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y))))
|
||||
(remove nil?)
|
||||
(into [])))
|
||||
|
||||
(defn content->handlers [content]
|
||||
(->> (d/with-prev content) ;; [cmd, prev]
|
||||
(d/enumerate) ;; [idx [cmd, prev]]
|
||||
|
||||
(mapcat (fn [[index [cur-cmd prev-cmd]]]
|
||||
(if (and prev-cmd
|
||||
(= :curve-to (:command cur-cmd)))
|
||||
(let [cur-pos (command->point cur-cmd)
|
||||
pre-pos (command->point prev-cmd)]
|
||||
[[pre-pos [index :c1]]
|
||||
[cur-pos [index :c2]]])
|
||||
[])))
|
||||
|
||||
(group-by first)
|
||||
(cd/mapm #(mapv second %2))))
|
||||
|
||||
(defn opposite-index [content index prefix]
|
||||
(let [point (if (= prefix :c2)
|
||||
(command->point (nth content index))
|
||||
(command->point (nth content (dec index))))
|
||||
|
||||
handlers (-> (content->handlers content)
|
||||
(get point))
|
||||
|
||||
opposite-prefix (if (= prefix :c1) :c2 :c1)
|
||||
|
||||
result (when (<= (count handlers) 2)
|
||||
(->> handlers
|
||||
(d/seek (fn [[index prefix]] (= prefix opposite-prefix)))
|
||||
(first)))]
|
||||
result))
|
||||
|
|
|
@ -14,22 +14,22 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.point :as gpt]))
|
||||
|
||||
(defn- frame-snap-points [{:keys [x y width height] :as frame}]
|
||||
(into #{(gpt/point x y)
|
||||
(gpt/point (+ x (/ width 2)) y)
|
||||
(gpt/point (+ x width) y)
|
||||
(defn- selrect-snap-points [{:keys [x y width height]}]
|
||||
#{(gpt/point x y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point x (+ y height))})
|
||||
|
||||
(defn- frame-snap-points [{:keys [x y width height] :as selrect}]
|
||||
(into (selrect-snap-points selrect)
|
||||
#{(gpt/point (+ x (/ width 2)) y)
|
||||
(gpt/point (+ x width) (+ y (/ height 2)))
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point (+ x (/ width 2)) (+ y height))
|
||||
(gpt/point x (+ y height))
|
||||
(gpt/point x (+ y (/ height 2)))}))
|
||||
|
||||
(defn shape-snap-points
|
||||
[shape]
|
||||
(let [shape (gsh/transform-shape shape)
|
||||
shape-center (gsh/center shape)]
|
||||
(if (= :frame (:type shape))
|
||||
(-> shape
|
||||
(gsh/shape->rect-shape)
|
||||
(frame-snap-points))
|
||||
(into #{shape-center} (:points shape)))))
|
||||
(let [shape (gsh/transform-shape shape)]
|
||||
(case (:type shape)
|
||||
:frame (-> shape :selrect frame-snap-points)
|
||||
(into #{(gsh/center-shape shape)} (:points shape)))))
|
||||
|
|
|
@ -38,10 +38,12 @@
|
|||
(fn [event]
|
||||
(let [data (.-data event)
|
||||
data (t/decode data)]
|
||||
(rx/push! bus data))))
|
||||
(if (:error data)
|
||||
(on-error (:error data))
|
||||
(rx/push! bus data)))))
|
||||
(.addEventListener ins "error"
|
||||
(fn [error]
|
||||
(on-error wrk error)))
|
||||
(on-error wrk (.-data error))))
|
||||
|
||||
wrk))
|
||||
|
||||
|
|
|
@ -65,8 +65,7 @@
|
|||
|
||||
(defn- create-index
|
||||
[objects]
|
||||
(let [shapes (->> (cph/select-toplevel-shapes objects {:include-frames? true})
|
||||
(map #(merge % (select-keys % [:x :y :width :height]))))
|
||||
(let [shapes (cph/select-toplevel-shapes objects {:include-frames? true})
|
||||
bounds (geom/selection-rect shapes)
|
||||
bounds #js {:x (:x bounds)
|
||||
:y (:y bounds)
|
||||
|
@ -77,7 +76,8 @@
|
|||
shapes)))
|
||||
|
||||
(defn- index-object
|
||||
[index {:keys [id x y width height] :as obj}]
|
||||
(let [rect #js {:x x :y y :width width :height height}]
|
||||
[index obj]
|
||||
(let [{:keys [id x y width height]} (:selrect obj)
|
||||
rect #js {:x x :y y :width width :height height}]
|
||||
(qdt/insert index rect obj)))
|
||||
|
||||
|
|