0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-25 07:58:49 -05:00

First version drawing beziers

This commit is contained in:
alonso.torres 2020-11-12 10:42:09 +01:00
parent af68c26aea
commit f3cce1904c
7 changed files with 255 additions and 56 deletions

View file

@ -19,8 +19,12 @@
(defn center-rect
[{:keys [x y width height]}]
(gpt/point (+ x (/ width 2))
(+ y (/ height 2))))
(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."

View file

@ -23,7 +23,8 @@
"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 (gco/center-shape 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)))

View file

@ -23,7 +23,7 @@
(defn finite?
[v]
#?(:cljs (js/isFinite v)
#?(:cljs (and (not (nil? v)) (js/isFinite v))
:clj (Double/isFinite v)))
(defn abs

View file

@ -14,15 +14,12 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.main.streams :as ms]
[app.util.geom.path :as path]
[app.util.geom.path :as ugp]
[app.main.data.workspace.drawing.common :as common]))
(defn finish-event? [{:keys [type shift] :as event}]
(or (= event ::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)))))
@ -78,26 +75,105 @@
(assoc-in [:workspace-drawing :object :last-point] nil)
(update-in [:workspace-drawing :object] calculate-selrect)))))
(defn preview-next-point [{:keys [x y]}]
(ptk/reify ::add-node
ptk/UpdateEvent
(update [_ state]
(let [point {:x x :y y}
{:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object])
command (cond
(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]}]
(ptk/reify ::add-node
ptk/UpdateEvent
(update [_ state]
(let [point {:x x :y y}
last-point (get-in state [:workspace-drawing :object :last-point])
command (if last-point
{:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object])
command (cond
(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
{:command :move-to
:params point})]
:params point})
]
(-> state
(assoc-in [:workspace-drawing :object :last-point] point)
(update-in [:workspace-drawing :object :content] (fnil conj []) command))))))
(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]}]
(ptk/reify ::drag-handler
ptk/UpdateEvent
(update [_ state]
(-> 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
(and prev (= (:command last) :line-to))
(-> content
(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
(update-in [:workspace-drawing :object :content] change-handler)
(assoc-in [:workspace-drawing :object :drag-handler] handler))))))
(defn finish-drag []
(ptk/reify ::finish-drag
ptk/UpdateEvent
(update [_ state]
(let [handler (get-in state [:workspace-drawing :object :drag-handler])]
(-> state
(update-in [:workspace-drawing :object] dissoc :drag-handler)
(assoc-in [:workspace-drawing :object :prev-handler] handler))))))
(defn make-click-stream
[stream down-event]
@ -115,8 +191,9 @@
(rx/map #(drag-handler %)))]
(->> (rx/timer 400)
(rx/merge-map #(rx/concat
(add-node down-event)
drag-events)))))
(rx/of (add-node down-event))
drag-events
(rx/of (finish-drag)))))))
(defn make-dbl-click-stream
[stream down-event]
@ -133,26 +210,32 @@
(watch [_ state stream]
;; clicks stream<[MouseEvent, Position]>
(let [
mouse-down (->> stream (rx/filter ms/mouse-down?))
(let [mouse-down (->> stream (rx/filter ms/mouse-down?))
finish-events (->> stream (rx/filter finish-event?))
events (->> mouse-down
(rx/take-until finish-events)
(rx/throttle 100)
(rx/with-latest merge ms/mouse-position)
mousemove-events
(->> ms/mouse-position
(rx/take-until finish-events)
(rx/throttle 100)
(rx/map #(preview-next-point %)))
;; We change to the stream that emits the first event
(rx/switch-map
#(rx/race (make-click-stream stream %)
(make-drag-stream stream %)
(make-dbl-click-stream stream %))))]
mousedown-events
(->> mouse-down
(rx/take-until finish-events)
(rx/throttle 100)
(rx/with-latest merge ms/mouse-position)
;; We change to the stream that emits the first event
(rx/switch-map
#(rx/race (make-click-stream stream %)
(make-drag-stream stream %)
(make-dbl-click-stream stream %))))]
(rx/concat
(rx/of (init-path))
events
(rx/merge mousemove-events
mousedown-events)
(rx/of (finish-path))
(rx/of common/handle-finish-drawing)))

View file

@ -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,19 +38,16 @@
(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
#_(mf/defc path-draw-area
[{:keys [shape] :as props}]
(let [locale (i18n/use-locale)
on-click
(fn [event]
(dom/stop-propagation event)
@ -62,14 +63,13 @@
(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))
[:g.drawing
[:& 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}])])))
:on-mouse-leave on-mouse-leave}])]))

View file

@ -23,7 +23,8 @@
[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]))
(mf/defc path-wrapper
{::mf/wrap-props false}
@ -56,3 +57,93 @@
[:& path/path-shape {:shape shape
:background? true}]]))
(mf/defc path-handler [{:keys [point handler zoom selected]}]
(when (and point handler)
(let [{:keys [x y]} handler]
[:g.handler
[:line
{:x1 (:x point)
:y1 (:y point)
:x2 x
:y2 y
:style {:stroke "#B1B2B5"
:stroke-width (/ 1 zoom)}}]
[:rect
{:x (- x (/ 3 zoom))
:y (- y (/ 3 zoom))
:width (/ 6 zoom)
:height (/ 6 zoom)
:style {:stroke-width (/ 1 zoom)
:stroke (if selected "#000000" "#1FDEA7")
:fill (if selected "#1FDEA7" "#FFFFFF")}}]])))
(mf/defc path-editor
[{:keys [shape zoom]}]
(let [points (:points shape)
drag-handler (:drag-handler shape)
prev-handler (:prev-handler shape)
last-command (last (:content shape))
selected false
last-p (last points)
handlers (ugp/extract-handlers (:content shape))
handlers (if (and prev-handler (not drag-handler))
(conj handlers {:point last-p :prev prev-handler})
handlers)
]
[:g.path-editor
(when (and (:preview shape) (not (:drag-handler shape)))
[:*
[:path {:style {:fill "transparent"
:stroke "#DB00FF"
:stroke-width (/ 1 zoom)}
:d (ugp/content->path [{:command :move-to
:params {:x (:x last-p)
:y (:y last-p)}}
(:preview shape)])}]
[:circle
{:cx (-> shape :preview :params :x)
:cy (-> shape :preview :params :y)
:r (/ 3 zoom)
:style {:stroke-width (/ 1 zoom)
:stroke "#DB00FF"
:fill "#FFFFFF"}}]])
(for [{:keys [point prev next]} handlers]
[:*
[:& path-handler {:point point
:handler prev
:zoom zoom
:type :prev
:selected false}]
[:& path-handler {:point point
:handler next
:zoom zoom
:type :next
:selected false}]])
(when drag-handler
[:*
(when (not= :move-to (:command last-command))
[:& path-handler {:point last-p
:handler (ugp/opposite-handler last-p drag-handler)
:zoom zoom
:type :drag-opposite
:selected false}])
[:& path-handler {:point last-p
:handler drag-handler
:zoom zoom
:type :drag
:selected false}]])
(for [{:keys [x y] :as point} points]
[:circle
{:cx x
:cy y
:r (/ 3 zoom)
:style {:stroke-width (/ 1 zoom)
:stroke (if selected "#000000" "#1FDEA7")
:fill (if selected "#1FDEA7" "#FFFFFF")}
}])]))

View file

@ -11,6 +11,7 @@
(:require
[cuerdas.core :as str]
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.util.geom.path-impl-simplify :as impl-simplify]))
(defn simplify
@ -192,10 +193,29 @@
(map command->string)
(str/join "")))
#_(let [path "M.343 15.974a.514.514 0 01-.317-.321c-.023-.07-.026-.23-.026-1.43 0-1.468-.001-1.445.09-1.586.02-.032 1.703-1.724 3.74-3.759a596.805 596.805 0 003.7-3.716c0-.009-.367-.384-.816-.833a29.9 29.9 0 01-.817-.833c0-.01.474-.49 1.054-1.07l1.053-1.053.948.946.947.947 1.417-1.413C12.366.806 12.765.418 12.856.357c.238-.161.52-.28.792-.334.17-.034.586-.03.76.008.801.173 1.41.794 1.57 1.603.03.15.03.569 0 .718a2.227 2.227 0 01-.334.793c-.061.09-.45.49-1.496 1.54L12.734 6.1l.947.948.947.947-1.053 1.054c-.58.58-1.061 1.054-1.07 1.054-.01 0-.384-.368-.833-.817-.45-.45-.824-.817-.834-.817-.009 0-1.68 1.666-3.716 3.701a493.093 493.093 0 01-3.759 3.74c-.14.091-.117.09-1.59.089-1.187 0-1.366-.004-1.43-.027zm6.024-4.633a592.723 592.723 0 003.663-3.68c0-.02-1.67-1.69-1.69-1.69-.01 0-1.666 1.648-3.68 3.663L.996 13.297v.834c0 .627.005.839.02.854.015.014.227.02.854.02h.833l3.664-3.664z"
content (path->content path)
new-path (content->path content)
]
(prn "path" path)
(.log js/console "?? 1" (clj->js content))
(prn "?? 2" (= path new-path) new-path))
(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
[point handler]
(let [phv (gpt/to-vec point handler)
opposite (gpt/add point (gpt/negate phv))]
opposite))
(defn extract-handlers [content]
(let [extract (fn [{param1 :params :as cmd1} {param2 :params :as cmd2}]
{:point (gpt/point (:x param1) (:y param1))
:prev (when (:c2x param1) (gpt/point (:c2x param1) (:c2y param1)))
:next (when (:c1x param2) (gpt/point (:c1x param2) (:c1y param2)))})]
(map extract content (d/concat [] (rest content) [nil]))))