mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
286 lines
11 KiB
Clojure
286 lines
11 KiB
Clojure
;; 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.ui.workspace.shapes.path
|
|
(:require
|
|
[rumext.alpha :as mf]
|
|
[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.util.geom.path :as ugp]
|
|
[app.common.geom.point :as gpt]
|
|
[app.common.geom.shapes.path :as gsp]
|
|
[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 (not (::dr/initialized? shape))
|
|
(do
|
|
(dom/stop-propagation event)
|
|
(dom/prevent-default event)
|
|
(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 gsp/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-preview [{:keys [zoom command from]}]
|
|
(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])}]))
|
|
|
|
(mf/defc path-point [{:keys [index position stroke-color fill-color zoom edit-mode selected]}]
|
|
(let [{:keys [x y]} position
|
|
on-click (fn [event]
|
|
(cond
|
|
(= edit-mode :move)
|
|
(do
|
|
(dom/stop-propagation event)
|
|
(dom/prevent-default event)
|
|
(st/emit! (drp/select-node index)))))
|
|
|
|
on-mouse-down (fn [event]
|
|
(cond
|
|
(= edit-mode :move)
|
|
(do
|
|
(dom/stop-propagation event)
|
|
(dom/prevent-default event)
|
|
(st/emit! (drp/start-move-path-point index)))))]
|
|
[:g.path-point
|
|
[:circle.path-point
|
|
{:cx x
|
|
:cy y
|
|
:r (/ 3 zoom)
|
|
:style { ;; :cursor cur/resize-alt
|
|
:stroke-width (/ 1 zoom)
|
|
:stroke (or stroke-color black-color)
|
|
:fill (or fill-color 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 point handler zoom selected type edit-mode]}]
|
|
(when (and point handler)
|
|
(let [{:keys [x y]} handler
|
|
on-click (fn [event]
|
|
(cond
|
|
(= edit-mode :move)
|
|
(do
|
|
(dom/stop-propagation event)
|
|
(dom/prevent-default event)
|
|
(drp/select-handler index type))))
|
|
|
|
on-mouse-down (fn [event]
|
|
(cond
|
|
(= edit-mode :move)
|
|
(do
|
|
(dom/stop-propagation event)
|
|
(dom/prevent-default event)
|
|
(st/emit! (drp/start-move-handler index type)))))]
|
|
[:g.handler {:class (name type)}
|
|
[: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/resize-alt
|
|
:stroke-width (/ 1 zoom)
|
|
:stroke (if selected black-color primary-color)
|
|
:fill (if selected primary-color 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-editor
|
|
[{:keys [shape zoom]}]
|
|
|
|
(let [{:keys [content]} shape
|
|
edit-path-ref (make-edit-path-ref (:id shape))
|
|
{:keys [edit-mode selected drag-handler prev-handler preview content-modifiers]} (mf/deref edit-path-ref)
|
|
selected (or selected #{})
|
|
content (gsp/apply-content-modifiers content content-modifiers)
|
|
points (gsp/content->points content)
|
|
last-command (last content)
|
|
last-p (last points)]
|
|
|
|
[:g.path-editor
|
|
(when (and preview (not drag-handler))
|
|
[:g.preview {:style {:pointer-events "none"}}
|
|
[:& path-preview {:command preview
|
|
:from last-p
|
|
:zoom zoom}]
|
|
[:& path-point {:position (:params preview)
|
|
:fill-color secondary-color
|
|
:zoom zoom}]])
|
|
|
|
(for [[index [cmd next]] (d/enumerate (d/with-next content))]
|
|
(let [point (gpt/point (:params cmd))]
|
|
[:g.path-node
|
|
(when (= :curve-to (:command cmd))
|
|
[:& path-handler {:point point
|
|
:handler (gpt/point (-> cmd :params :c2x) (-> cmd :params :c2y))
|
|
:zoom zoom
|
|
:type :prev
|
|
:index index
|
|
:selected (selected [index :prev])
|
|
:edit-mode edit-mode}])
|
|
|
|
(when (= :curve-to (:command next))
|
|
[:& path-handler {:point point
|
|
:handler (gpt/point (-> next :params :c1x) (-> next :params :c1y))
|
|
:zoom zoom
|
|
:type :next
|
|
:index index
|
|
:selected (selected [index :next])
|
|
:edit-mode edit-mode}])
|
|
|
|
(when (and (= index (dec (count content)))
|
|
prev-handler (not drag-handler))
|
|
[:& path-handler {:point point
|
|
:handler prev-handler
|
|
:zoom zoom
|
|
:type :prev
|
|
:index index
|
|
:selected (selected index)
|
|
:edit-mode edit-mode}])
|
|
|
|
[:& path-point {:position point
|
|
:stroke-color (when-not (selected index) primary-color)
|
|
:fill-color (when (selected index) primary-color)
|
|
:index index
|
|
:zoom zoom
|
|
:edit-mode edit-mode}]]))
|
|
|
|
(when drag-handler
|
|
[:g.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}]])]))
|