🎉 Improved reusability of drawing functions

This commit is contained in:
alonso.torres 2020-11-13 16:23:36 +01:00
parent 05366eac6f
commit 275f6e3dc2
6 changed files with 154 additions and 77 deletions

View file

@ -26,6 +26,14 @@
[v] [v]
(instance? Point 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 (defn point
"Create a Point instance." "Create a Point instance."
([] (Point. 0 0)) ([] (Point. 0 0))
@ -37,6 +45,9 @@
(number? v) (number? v)
(Point. v v) (Point. v v)
(point-like? v)
(Point. (:x v) (:y v))
:else :else
(throw (ex-info "Invalid arguments" {:v v})))) (throw (ex-info "Invalid arguments" {:v v}))))
([x y] ([x y]

View file

@ -21,7 +21,10 @@
segments) segments)
(defn content->points [content] (defn content->points [content]
(mapv #(gpt/point (-> % :params :x) (-> % :params :y)) 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://medium.com/@Acegikmo/the-ever-so-lovely-b%C3%A9zier-curve-eb27514da3bf
;; https://en.wikipedia.org/wiki/Bernstein_polynomial ;; https://en.wikipedia.org/wiki/Bernstein_polynomial
@ -105,6 +108,7 @@
(let [calc-extremities (let [calc-extremities
(fn [command prev] (fn [command prev]
(case (:command command) (case (:command command)
:close-path []
:move-to [(command->point command)] :move-to [(command->point command)]
;; If it's a line we add the beginning point and endpoint ;; If it's a line we add the beginning point and endpoint

View file

@ -226,7 +226,8 @@
:content content :content content
:points points :points points
:selrect selrect :selrect selrect
:rotation rotation))) ;;:rotation rotation
)))
(defn apply-transform-curve (defn apply-transform-curve
[shape transform] [shape transform]

View file

@ -11,12 +11,101 @@
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
[app.common.math :as mth]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.main.streams :as ms] [app.util.data :as d]
[app.util.geom.path :as ugp] [app.util.geom.path :as ugp]
[app.main.streams :as ms]
[app.main.data.workspace.drawing.common :as common])) [app.main.data.workspace.drawing.common :as common]))
;;;;
(def close-path-distance 5)
(defn seek-start-path [content]
(->> content
reverse
(d/seek (fn [{cmd :command}] (= cmd :move-to)))
:params))
(defn next-node
"Calculates the next-node to be inserted."
[shape position prev-point prev-handler]
(let [last-command (-> shape :content last :command)
start-point (-> shape :content seek-start-path)
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))
close-path? (and start-point
(< (mth/abs (gpt/distance (gpt/point start-point)
(gpt/point position)))
close-path-distance))]
(cond
close-path? {:command :close-path
:params []}
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)]
(as-> shape $
(update $ :content (fnil conj []) command)
(update $ :selrect (gsh/content->selrect (:content $))))))
(defn suffix-keyword [kw suffix]
(let [strkw (if kw (name kw) "")]
(keyword (str strkw suffix))))
;; handler-type => :prev :next
(defn move-handler [shape index handler-type match-opposite? position]
(let [content (:content shape)
[command next-command] (-> (d/with-next content) (nth index))
update-command
(fn [{cmd :command params :params :as command} param-prefix prev-command]
(if (#{:line-to :curve-to} cmd)
(let [command (if (= cmd :line-to)
{:command :curve-to
:params (ugp/make-curve-params params (:params prev-command))}
command)]
(-> command
(update :params assoc
(suffix-keyword param-prefix "x") (:x position)
(suffix-keyword param-prefix "y") (:y position))))
command))
update-content
(fn [shape index prefix]
(if (contains? (:content shape) index)
(let [prev-command (get-in shape [:content (dec index)])]
(update-in shape [:content index] update-command prefix prev-command))
shape))]
(cond-> shape
(= :prev handler-type)
(update-content index :c2)
(and (= :next handler-type) next-command)
(update-content (inc index) :c1)
match-opposite?
(move-handler
index
(if (= handler-type :prev) :next :prev)
false
(ugp/opposite-handler (gpt/point (:params command))
(gpt/point position))))))
;;;;
(defn finish-event? [{:keys [type shift] :as event}] (defn finish-event? [{:keys [type shift] :as event}]
(or (= event ::end-path-drawing) (or (= event ::end-path-drawing)
(= event :interrupt) (= event :interrupt)
@ -74,92 +163,37 @@
(ptk/reify ::add-node (ptk/reify ::add-node
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [point {:x x :y y} (let [position (gpt/point x y)
{:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object]) {:keys [last-point prev-handler] :as shape} (get-in state [:workspace-drawing :object])
command (next-node shape position last-point prev-handler)]
command (cond (assoc-in state [:workspace-drawing :object :preview] command)))))
(and last-point (not prev-handler))
{:command :line-to
:params point}
(and last-point prev-handler)
{:command :curve-to
:params (ugp/make-curve-params point prev-handler)}
:else
nil)
]
(-> state
(assoc-in [:workspace-drawing :object :preview] command))))))
(defn add-node [{:keys [x y]}] (defn add-node [{:keys [x y]}]
(ptk/reify ::add-node (ptk/reify ::add-node
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [point {:x x :y y}
{:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object])
command (cond (let [position (gpt/point x y)
(and last-point (not prev-handler)) {:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object])]
{:command :line-to (update-in
:params point} state
[:workspace-drawing :object]
(and last-point prev-handler) #(-> %
{:command :curve-to (append-node position last-point prev-handler)
:params (ugp/make-curve-params point prev-handler)} (assoc :last-point position)
(dissoc :prev-handler)))))))
:else
{:command :move-to
:params point})
]
(-> state
(assoc-in [:workspace-drawing :object :last-point] point)
(update-in [:workspace-drawing :object] dissoc :prev-handler)
(update-in [:workspace-drawing :object :content] (fnil conj []) command)
(update-in [:workspace-drawing :object] calculate-selrect))))))
(defn drag-handler [{:keys [x y]}] (defn drag-handler [{:keys [x y]}]
(ptk/reify ::drag-handler (ptk/reify ::drag-handler
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [change-handler (fn [content]
(let [last-idx (dec (count content))
last (get content last-idx nil)
prev (get content (dec last-idx) nil)
{last-x :x last-y :y} (:params last)
opposite (when last (ugp/opposite-handler (gpt/point last-x last-y) (gpt/point x y)))]
(cond (let [position (gpt/point x y)
(and prev (= (:command last) :line-to)) shape (get-in state [:workspace-drawing :object])
(-> content index (dec (count (:content shape)))]
(assoc last-idx {:command :curve-to
:params {:x (-> last :params :x)
:y (-> last :params :y)
:c1x (-> prev :params :x)
:c1y (-> prev :params :y)
:c2x (-> last :params :x)
:c2y (-> last :params :y)}})
(update-in
[last-idx :params]
#(-> %
(assoc :c2x (:x opposite)
:c2y (:y opposite)))))
(= (:command last) :curve-to)
(update-in content
[last-idx :params]
#(-> %
(assoc :c2x (:x opposite)
:c2y (:y opposite))))
:else
content))
)
handler (gpt/point x y)]
(-> state (-> state
(update-in [:workspace-drawing :object :content] change-handler) (update-in [:workspace-drawing :object] move-handler index :next true position)
(assoc-in [:workspace-drawing :object :drag-handler] handler)))))) (assoc-in [:workspace-drawing :object :drag-handler] position))))))
(defn finish-drag [] (defn finish-drag []
(ptk/reify ::finish-drag (ptk/reify ::finish-drag

View file

@ -118,6 +118,33 @@
(into {})) (into {}))
m1)) 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 ;; Numbers Parsing
@ -221,3 +248,4 @@
;; nil ;; nil
;; (throw e#))))))) ;; (throw e#)))))))

View file

@ -30,7 +30,6 @@
(defn shape-snap-points (defn shape-snap-points
[shape] [shape]
(let [shape (gsh/transform-shape shape)] (let [shape (gsh/transform-shape shape)]
(case (:type shape) (case (:type shape)
:frame (-> shape :selrect frame-snap-points) :frame (-> shape :selrect frame-snap-points)
(:path :curve) (-> shape :selrect selrect-snap-points) (:path :curve) (-> shape :selrect selrect-snap-points)