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:
parent
af68c26aea
commit
f3cce1904c
7 changed files with 255 additions and 56 deletions
|
@ -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."
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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}])]))
|
||||
|
|
|
@ -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")}
|
||||
}])]))
|
||||
|
|
|
@ -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]))))
|
||||
|
|
Loading…
Add table
Reference in a new issue