diff --git a/frontend/src/app/main/data/workspace/layout.cljs b/frontend/src/app/main/data/workspace/layout.cljs index 5b7209c67..9c7ad57a0 100644 --- a/frontend/src/app/main/data/workspace/layout.cljs +++ b/frontend/src/app/main/data/workspace/layout.cljs @@ -20,7 +20,7 @@ :document-history :colorpalette :element-options - :rules + :rulers :display-grid :snap-grid :scale-text @@ -50,7 +50,7 @@ #{:sitemap :layers :element-options - :rules + :rulers :display-grid :snap-grid :dynamic-alignment diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 28e2be350..ccbb35e7d 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -351,10 +351,10 @@ ;; MAIN MENU - :toggle-rules {:tooltip (ds/meta-shift "R") + :toggle-rulers {:tooltip (ds/meta-shift "R") :command (ds/c-mod "shift+r") :subsections [:main-menu] - :fn #(st/emit! (toggle-layout-flag :rules))} + :fn #(st/emit! (toggle-layout-flag :rulers))} :select-all {:tooltip (ds/meta "A") :command (ds/c-mod "a") diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 434bca57e..46c1ca493 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -212,8 +212,8 @@ (def snap-pixel? (l/derived #(contains? % :snap-pixel-grid) workspace-layout)) -(def rules? - (l/derived #(contains? % :rules) workspace-layout)) +(def rulers? + (l/derived #(contains? % :rulers) workspace-layout)) (def workspace-file "A ref to a striped vision of file (without data)." diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 5b164429d..374ec676b 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -46,7 +46,7 @@ (not (contains? focus id)))) (= type :guide) - (or (not (contains? layout :rules)) + (or (not (contains? layout :rulers)) (not (contains? layout :snap-guides)) (and (d/not-empty? focus) (not (contains? focus frame-id)))) diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 42d17f70b..7f964061e 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -305,14 +305,14 @@ :on-key-down (fn [event] (when (kbd/enter? event) (toggle-flag event))) - :data-test "rules" - :id "file-menu-rules"} + :data-test "rulers" + :id "file-menu-rulers"} [:span {:class (stl/css :item-name)} - (if (contains? layout :rules) + (if (contains? layout :rulers) (tr "workspace.header.menu.hide-rules") (tr "workspace.header.menu.show-rules"))] [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-rules))] + (for [sc (scd/split-sc (sc/get-tooltip :toggle-rulers))] [:span {:class (stl/css :shortcut-key) :key sc} sc])]] diff --git a/frontend/src/app/main/ui/workspace/palette.cljs b/frontend/src/app/main/ui/workspace/palette.cljs index e30a18e5b..8eb2ac84a 100644 --- a/frontend/src/app/main/ui/workspace/palette.cljs +++ b/frontend/src/app/main/ui/workspace/palette.cljs @@ -60,7 +60,7 @@ selected-text* (mf/use-state :file) selected-text (deref selected-text*) on-select (mf/use-fn #(reset! selected %)) - rulers? (mf/deref refs/rules?) + rulers? (mf/deref refs/rulers?) {:keys [on-pointer-down on-lost-pointer-capture on-pointer-move parent-ref size]} (r/use-resize-hook :palette 72 54 80 :y true :bottom on-change-palette-size) diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.cljs b/frontend/src/app/main/ui/workspace/top_toolbar.cljs index ee4de48ac..86f3484df 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/top_toolbar.cljs @@ -71,7 +71,7 @@ read-only? (mf/use-ctx ctx/workspace-read-only?) - rulers? (mf/deref refs/rules?) + rulers? (mf/deref refs/rulers?) hide-toolbar? (mf/deref refs/toolbar-visibility) interrupt diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 181e5f5da..58e5548c8 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -37,7 +37,7 @@ [app.main.ui.workspace.viewport.outline :as outline] [app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay] [app.main.ui.workspace.viewport.presence :as presence] - [app.main.ui.workspace.viewport.rules :as rules] + [app.main.ui.workspace.viewport.rulers :as rulers] [app.main.ui.workspace.viewport.scroll-bars :as scroll-bars] [app.main.ui.workspace.viewport.selection :as selection] [app.main.ui.workspace.viewport.snap-distances :as snap-distances] @@ -231,7 +231,7 @@ (or show-distances? mode-inspect?)) show-artboard-names? (contains? layout :display-artboard-names) hide-ui? (contains? layout :hide-ui) - show-rules? (and (contains? layout :rules) (not hide-ui?)) + show-rulers? (and (contains? layout :rulers) (not hide-ui?)) disabled-guides? (or drawing-tool transform drawing-path? node-editing?) @@ -264,7 +264,7 @@ (:y first-shape) (:y selected-frame)) - rule-area-size (/ rules/rule-area-size zoom)] + rule-area-size (/ rulers/ruler-area-size zoom)] (hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only?) (hooks/setup-viewport-size vport viewport-ref) @@ -377,7 +377,7 @@ :on-pointer-up on-pointer-up} [:defs - ;; This clip is so the handlers are not over the rules + ;; This clip is so the handlers are not over the rulers [:clipPath {:id "clip-handlers"} [:rect {:x (+ (:x vbox) rule-area-size) :y (+ (:y vbox) rule-area-size) @@ -544,16 +544,16 @@ {:page-id page-id}]) (when-not hide-ui? - [:& rules/rules + [:& rulers/rulers {:zoom zoom :zoom-inverse zoom-inverse :vbox vbox :selected-shapes selected-shapes :offset-x offset-x :offset-y offset-y - :show-rules? show-rules?}]) + :show-rulers? show-rulers?}]) - (when show-rules? + (when show-rulers? [:& guides/viewport-guides {:zoom zoom :vbox vbox diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index 9d1b481d2..13ba8503b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -19,7 +19,7 @@ [app.main.streams :as ms] [app.main.ui.css-cursors :as cur] [app.main.ui.formats :as fmt] - [app.main.ui.workspace.viewport.rules :as rules] + [app.main.ui.workspace.viewport.rulers :as rulers] [app.util.dom :as dom] [rumext.v2 :as mf])) @@ -129,7 +129,7 @@ (defn guide-area-axis [pos vbox zoom frame axis] - (let [rules-pos (/ rules/rules-pos zoom) + (let [rulers-pos (/ rulers/rulers-pos zoom) guide-active-area (/ guide-active-area zoom)] (cond (and (some? frame) (= axis :x)) @@ -146,12 +146,12 @@ (= axis :x) {:x (- pos (/ guide-active-area 2)) - :y (+ (:y vbox) rules-pos) + :y (+ (:y vbox) rulers-pos) :width guide-active-area :height (:height vbox)} :else - {:x (+ (:x vbox) rules-pos) + {:x (+ (:x vbox) rulers-pos) :y (- pos (/ guide-active-area 2)) :width (:width vbox) :height guide-active-area}))) @@ -198,23 +198,23 @@ (defn guide-pill-axis [pos vbox zoom axis] - (let [rules-pos (/ rules/rules-pos zoom) + (let [rulers-pos (/ rulers/rulers-pos zoom) guide-pill-width (/ guide-pill-width zoom) guide-pill-height (/ guide-pill-height zoom)] (if (= axis :x) {:rect-x (- pos (/ guide-pill-width 2)) - :rect-y (+ (:y vbox) rules-pos (- (/ guide-pill-width 2)) (/ 3 zoom)) + :rect-y (+ (:y vbox) rulers-pos (- (/ guide-pill-width 2)) (/ 3 zoom)) :rect-width guide-pill-width :rect-height guide-pill-height :text-x pos - :text-y (+ (:y vbox) rules-pos (- (/ 3 zoom)))} + :text-y (+ (:y vbox) rulers-pos (- (/ 3 zoom)))} - {:rect-x (+ (:x vbox) rules-pos (- (/ guide-pill-height 2)) (- (/ 4 zoom))) + {:rect-x (+ (:x vbox) rulers-pos (- (/ guide-pill-height 2)) (- (/ 4 zoom))) :rect-y (- pos (/ guide-pill-width 2)) :rect-width guide-pill-height :rect-height guide-pill-width - :text-x (+ (:x vbox) rules-pos (- (/ 3 zoom))) + :text-x (+ (:x vbox) rulers-pos (- (/ 3 zoom))) :text-y pos}))) (defn guide-inside-vbox? @@ -222,7 +222,7 @@ (partial guide-inside-vbox? zoom vbox)) ([zoom {:keys [x y width height]} {:keys [axis position]}] - (let [rule-area-size (/ rules/rule-area-size zoom) + (let [rule-area-size (/ rulers/ruler-area-size zoom) x1 x x2 (+ x width) y1 y @@ -376,8 +376,8 @@ :text-anchor "middle" :dominant-baseline "middle" :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) - :style {:font-size (/ rules/font-size zoom) - :font-family rules/font-family + :style {:font-size (/ rulers/font-size zoom) + :font-family rulers/font-family :fill colors/black}} ;; If the guide is associated to a frame we show the position relative to the frame (fmt/format-number (- pos (if (= axis :x) (:x frame) (:y frame))))]]))]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/rulers.cljs b/frontend/src/app/main/ui/workspace/viewport/rulers.cljs new file mode 100644 index 000000000..46206b5c7 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/rulers.cljs @@ -0,0 +1,347 @@ +;; 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/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.workspace.viewport.rulers + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.main.ui.formats :as fmt] + [app.main.ui.hooks :as hooks] + [app.util.object :as obj] + [rumext.v2 :as mf])) + +(def rulers-pos 15) +(def rulers-size 4) +(def rulers-width 1) +(def ruler-area-size 22) +(def ruler-area-half-size (/ ruler-area-size 2)) +(def rulers-background "var(--panel-background-color)") +(def selection-area-color "var(--color-accent-tertiary)") +(def selection-area-opacity 0.3) +(def over-number-size 100) +(def over-number-opacity 0.8) +(def over-number-percent 0.75) + +(def font-size 12) +(def font-family "worksans") +(def font-color "var(--layer-row-foreground-color)") +(def canvas-border-radius 12) + +;; ---------------- +;; RULERS +;; ---------------- + +(defn- calculate-step-size + [zoom] + (cond + (< 0 zoom 0.008) 10000 + (< 0.008 zoom 0.015) 5000 + (< 0.015 zoom 0.04) 2500 + (< 0.04 zoom 0.07) 1000 + (< 0.07 zoom 0.2) 500 + (< 0.2 zoom 0.5) 250 + (< 0.5 zoom 1) 100 + (<= 1 zoom 2) 50 + (< 2 zoom 4) 25 + (< 4 zoom 6) 10 + (< 6 zoom 15) 5 + (< 15 zoom 25) 2 + (< 25 zoom) 1 + :else 1)) + +(defn get-clip-area + [vbox zoom-inverse axis] + (if (= axis :x) + (let [x (+ (:x vbox) (* 25 zoom-inverse)) + y (:y vbox) + width (- (:width vbox) (* 21 zoom-inverse)) + height (* 25 zoom-inverse)] + {:x x :y y :width width :height height}) + + (let [x (:x vbox) + y (+ (:y vbox) (* 25 zoom-inverse)) + width (* 25 zoom-inverse) + height (- (:height vbox) (* 21 zoom-inverse))] + {:x x :y y :width width :height height}))) + +(defn get-background-area + [vbox zoom-inverse axis] + (if (= axis :x) + (let [x (:x vbox) + y (:y vbox) + width (:width vbox) + height (* ruler-area-size zoom-inverse)] + {:x x :y y :width width :height height}) + + (let [x (:x vbox) + y (+ (:y vbox) (* ruler-area-size zoom-inverse)) + width (* ruler-area-size zoom-inverse) + height (- (:height vbox) (* 21 zoom-inverse))] + {:x x :y y :width width :height height}))) + +(defn get-ruler-params + [vbox axis] + (if (= axis :x) + (let [start (:x vbox) + end (+ start (:width vbox))] + {:start start :end end}) + + (let [start (:y vbox) + end (+ start (:height vbox))] + {:start start :end end}))) + +(defn get-ruler-axis + [val vbox zoom-inverse axis] + (let [rulers-pos (* rulers-pos zoom-inverse) + rulers-size (* rulers-size zoom-inverse)] + (if (= axis :x) + {:text-x val + :text-y (+ (:y vbox) (- rulers-pos (* 4 zoom-inverse))) + :line-x1 val + :line-y1 (+ (:y vbox) rulers-pos (* 2 zoom-inverse)) + :line-x2 val + :line-y2 (+ (:y vbox) rulers-pos (* 2 zoom-inverse) rulers-size)} + + {:text-x (+ (:x vbox) (- rulers-pos (* 4 zoom-inverse))) + :text-y val + :line-x1 (+ (:x vbox) rulers-pos (* 2 zoom-inverse)) + :line-y1 val + :line-x2 (+ (:x vbox) rulers-pos (* 2 zoom-inverse) rulers-size) + :line-y2 val}))) + +(defn rulers-outside-path + "Path data for the viewport outside" + [x1 y1 x2 y2] + (dm/str + "M" x1 "," y1 + "L" x2 "," y1 + "L" x2 "," y2 + "L" x1 "," y2 + "Z")) + +(defn rulers-inside-path + "Calculates the path for the inside of the viewport frame" + [x1 y1 x2 y2 br bw] + (dm/str + "M" (+ x1 bw) "," (+ y1 bw br) + "Q" (+ x1 bw) "," (+ y1 bw) "," (+ x1 bw br) "," (+ y1 bw) + + "L" (- x2 br) "," (+ y1 bw) + "Q" x2 "," (+ y1 bw) "," x2 "," (+ y1 bw br) + + "L" x2 "," (- y2 br) + "Q" x2 "," y2 "," (- x2 br) "," y2 + + "L" (+ x1 bw br) "," y2 + "Q" (+ x1 bw) "," y2 "," (+ x1 bw) "," (- y2 br) + + "Z")) + +(mf/defc rulers-text + "Draws the text for the rulers in a specific axis" + [{:keys [vbox step offset axis zoom-inverse]}] + (let [clip-id (str "clip-ruler-" (d/name axis)) + {:keys [start end]} (get-ruler-params vbox axis) + minv (max start -100000) + minv (* (mth/ceil (/ minv step)) step) + maxv (min end 100000) + maxv (* (mth/floor (/ maxv step)) step) + + ;; These extra operations ensure that we are selecting a frame its initial location is rendered in the ruler + minv (+ minv (mod offset step)) + maxv (+ maxv (mod offset step))] + + [:g.rulers {:clipPath (str "url(#" clip-id ")")} + [:defs + [:clipPath {:id clip-id} + (let [{:keys [x y width height]} (get-clip-area vbox zoom-inverse axis)] + [:rect {:x x :y y :width width :height height}])]] + + (for [step-val (range minv (inc maxv) step)] + (let [{:keys [text-x text-y line-x1 line-y1 line-x2 line-y2]} + (get-ruler-axis step-val vbox zoom-inverse axis)] + [:* {:key (dm/str "text-" (d/name axis) "-" step-val)} + [:text {:x text-x + :y text-y + :text-anchor "middle" + :dominant-baseline "middle" + :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill font-color}} + ;; If the guide is associated to a frame we show the position relative to the frame + (fmt/format-number (- step-val offset))] + + [:line {:key (str "line-" (d/name axis) "-" step-val) + :x1 line-x1 + :y1 line-y1 + :x2 line-x2 + :y2 line-y2 + :style {:stroke font-color + :stroke-width rulers-width}}]]))])) + +(mf/defc viewport-frame + [{:keys [show-rulers? zoom zoom-inverse vbox offset-x offset-y]}] + + (let [{:keys [width height] x1 :x y1 :y} vbox + x2 (+ x1 width) + y2 (+ y1 height) + bw (if show-rulers? (* ruler-area-size zoom-inverse) 0) + br (/ canvas-border-radius zoom) + bs (* 4 zoom-inverse)] + [:* + [:g.viewport-frame-background + ;; This goes behind because if it goes in front the background bleeds through + [:path {:d (rulers-inside-path x1 y1 x2 y2 br bw) + :fill "none" + :stroke-width bs + :stroke "var(--panel-border-color)"}] + + [:path {:d (dm/str (rulers-outside-path x1 y1 x2 y2) + (rulers-inside-path x1 y1 x2 y2 br bw)) + :fill-rule "evenodd" + :fill rulers-background}]] + + (when show-rulers? + (let [step (calculate-step-size zoom)] + [:g.viewport-frame-rulers + [:& rulers-text {:vbox vbox :offset offset-x :step step :zoom-inverse zoom-inverse :axis :x}] + [:& rulers-text {:vbox vbox :offset offset-y :step step :zoom-inverse zoom-inverse :axis :y}]]))])) + +(mf/defc selection-area + [{:keys [vbox zoom-inverse selection-rect offset-x offset-y]}] + ;; When using the format-number callls we consider if the guide is associated to a frame and we show the position relative to it with the offset + [:g.selection-area + [:defs + [:linearGradient {:id "selection-gradient-start"} + [:stop {:offset "0%" :stop-color rulers-background :stop-opacity 0}] + [:stop {:offset "40%" :stop-color rulers-background :stop-opacity 1}] + [:stop {:offset "100%" :stop-color rulers-background :stop-opacity 1}]] + + [:linearGradient {:id "selection-gradient-end"} + [:stop {:offset "0%" :stop-color rulers-background :stop-opacity 1}] + [:stop {:offset "60%" :stop-color rulers-background :stop-opacity 1}] + [:stop {:offset "100%" :stop-color rulers-background :stop-opacity 0}]]] + [:g + [:rect {:x (- (:x selection-rect) (* (* over-number-size over-number-percent) zoom-inverse)) + :y (:y vbox) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :fill "url('#selection-gradient-start')"}] + + [:rect {:x (- (:x2 selection-rect) (* over-number-size (- 1 over-number-percent))) + :y (:y vbox) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :fill "url('#selection-gradient-end')"}] + + [:rect {:x (:x selection-rect) + :y (:y vbox) + :width (:width selection-rect) + :height (* ruler-area-size zoom-inverse) + :style {:fill selection-area-color + :fill-opacity selection-area-opacity}}] + + [:text {:x (- (:x1 selection-rect) (* 4 zoom-inverse)) + :y (+ (:y vbox) (* 10.6 zoom-inverse)) + :text-anchor "end" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:x1 selection-rect) offset-x))] + + [:text {:x (+ (:x2 selection-rect) (* 4 zoom-inverse)) + :y (+ (:y vbox) (* 10.6 zoom-inverse)) + :text-anchor "start" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:x2 selection-rect) offset-x))]] + + (let [center-x (+ (:x vbox) (* ruler-area-half-size zoom-inverse)) + center-y (- (+ (:y selection-rect) (/ (:height selection-rect) 2)) (* ruler-area-half-size zoom-inverse))] + + [:g {:transform (str "rotate(-90 " center-x "," center-y ")")} + [:rect {:x (- center-x (/ (:height selection-rect) 2) (* ruler-area-half-size zoom-inverse)) + :y (- center-y (* ruler-area-half-size zoom-inverse)) + :width (:height selection-rect) + :height (* ruler-area-size zoom-inverse) + :style {:fill selection-area-color + :fill-opacity selection-area-opacity}}] + + [:rect {:x (- center-x (/ (:height selection-rect) 2) (* ruler-area-half-size zoom-inverse) (* over-number-size zoom-inverse)) + :y (- center-y (* ruler-area-half-size zoom-inverse)) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :style {:fill rulers-background + :fill-opacity over-number-opacity}}] + + [:rect {:x (+ (- center-x (/ (:height selection-rect) 2) (* ruler-area-half-size zoom-inverse)) (:height selection-rect)) + :y (- center-y (* ruler-area-half-size zoom-inverse)) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :style {:fill rulers-background + :fill-opacity over-number-opacity}}] + + [:text {:x (- center-x (/ (:height selection-rect) 2) (* 15 zoom-inverse)) + :y center-y + :text-anchor "end" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:y2 selection-rect) offset-y))] + + [:text {:x (+ center-x (/ (:height selection-rect) 2)) + :y center-y + :text-anchor "start" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:y1 selection-rect) offset-y))]])]) + +(mf/defc rulers + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % (mf/check-props ["zoom" "vbox" "selected-shapes" "show-rulers?"]))]} + [props] + (let [zoom (obj/get props "zoom") + zoom-inverse (obj/get props "zoom-inverse") + vbox (obj/get props "vbox") + offset-x (obj/get props "offset-x") + offset-y (obj/get props "offset-y") + selected-shapes (-> (obj/get props "selected-shapes") + (hooks/use-equal-memo)) + show-rulers? (obj/get props "show-rulers?") + + selection-rect + (mf/use-memo + (mf/deps selected-shapes) + #(when (d/not-empty? selected-shapes) + (gsh/shapes->rect selected-shapes)))] + + (when (some? vbox) + [:g.viewport-frame {:pointer-events "none"} + [:& viewport-frame + {:show-rulers? show-rulers? + :zoom zoom + :zoom-inverse zoom-inverse + :vbox vbox + :offset-x offset-x + :offset-y offset-y}] + + (when (and show-rulers? (some? selection-rect)) + [:& selection-area + {:zoom zoom + :zoom-inverse zoom-inverse + :vbox vbox + :selection-rect selection-rect + :offset-x offset-x + :offset-y offset-y}])]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/rules.cljs b/frontend/src/app/main/ui/workspace/viewport/rules.cljs deleted file mode 100644 index 0b2833e29..000000000 --- a/frontend/src/app/main/ui/workspace/viewport/rules.cljs +++ /dev/null @@ -1,350 +0,0 @@ -;; 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/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.main.ui.workspace.viewport.rules - (:require - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] - [app.common.math :as mth] - [app.main.ui.formats :as fmt] - [app.main.ui.hooks :as hooks] - [app.util.object :as obj] - [rumext.v2 :as mf])) - -(def rules-pos 15) -(def rules-size 4) -(def rules-width 1) -(def rule-area-size 22) -(def rule-area-half-size (/ rule-area-size 2)) -(def rules-background "var(--panel-background-color)") -(def selection-area-color "var(--color-accent-tertiary)") -(def selection-area-opacity 0.3) -(def over-number-size 100) -(def over-number-opacity 0.8) -(def over-number-percent 0.75) - -(def font-size 12) -(def font-family "worksans") -(def font-color "var(--layer-row-foreground-color)") -(def canvas-border-radius 12) - -;; ---------------- -;; RULES -;; ---------------- - -(defn- calculate-step-size - [zoom] - (cond - (< 0 zoom 0.008) 10000 - (< 0.008 zoom 0.015) 5000 - (< 0.015 zoom 0.04) 2500 - (< 0.04 zoom 0.07) 1000 - (< 0.07 zoom 0.2) 500 - (< 0.2 zoom 0.5) 250 - (< 0.5 zoom 1) 100 - (<= 1 zoom 2) 50 - (< 2 zoom 4) 25 - (< 4 zoom 6) 10 - (< 6 zoom 15) 5 - (< 15 zoom 25) 2 - (< 25 zoom) 1 - :else 1)) - -(defn get-clip-area - [vbox zoom-inverse axis] - (if (= axis :x) - (let [x (+ (:x vbox) (* 25 zoom-inverse)) - y (:y vbox) - width (- (:width vbox) (* 21 zoom-inverse)) - height (* 25 zoom-inverse)] - {:x x :y y :width width :height height}) - - (let [x (:x vbox) - y (+ (:y vbox) (* 25 zoom-inverse)) - width (* 25 zoom-inverse) - height (- (:height vbox) (* 21 zoom-inverse))] - {:x x :y y :width width :height height}))) - -(defn get-background-area - [vbox zoom-inverse axis] - (if (= axis :x) - (let [x (:x vbox) - y (:y vbox) - width (:width vbox) - height (* rule-area-size zoom-inverse)] - {:x x :y y :width width :height height}) - - (let [x (:x vbox) - y (+ (:y vbox) (* rule-area-size zoom-inverse)) - width (* rule-area-size zoom-inverse) - height (- (:height vbox) (* 21 zoom-inverse))] - {:x x :y y :width width :height height}))) - -(defn get-rule-params - [vbox axis] - (if (= axis :x) - (let [start (:x vbox) - end (+ start (:width vbox))] - {:start start :end end}) - - (let [start (:y vbox) - end (+ start (:height vbox))] - {:start start :end end}))) - -(defn get-rule-axis - [val vbox zoom-inverse axis] - (let [rules-pos (* rules-pos zoom-inverse) - rules-size (* rules-size zoom-inverse)] - (if (= axis :x) - {:text-x val - :text-y (+ (:y vbox) (- rules-pos (* 4 zoom-inverse))) - :line-x1 val - :line-y1 (+ (:y vbox) rules-pos (* 2 zoom-inverse)) - :line-x2 val - :line-y2 (+ (:y vbox) rules-pos (* 2 zoom-inverse) rules-size)} - - {:text-x (+ (:x vbox) (- rules-pos (* 4 zoom-inverse))) - :text-y val - :line-x1 (+ (:x vbox) rules-pos (* 2 zoom-inverse)) - :line-y1 val - :line-x2 (+ (:x vbox) rules-pos (* 2 zoom-inverse) rules-size) - :line-y2 val}))) - -(defn- round-corner-path-tl - [cx cy radius] - (dm/str - "M" cx "," cy - "L" (+ cx radius) "," cy - "Q" cx "," cy "," cx "," (+ cy radius) - "Z")) - -(defn- round-corner-path-tr - [cx cy radius] - (dm/str - "M" cx "," cy - "L" (+ cx radius) "," cy - "L" (+ cx radius) "," (+ cy radius) - "Q" (+ cx radius) "," cy "," cx "," cy - "Z")) - -(defn- round-corner-path-bl - [cx cy radius] - (dm/str - "M" cx "," cy - "Q" cx "," (+ cy radius) "," (+ cx radius) "," (+ cy radius) - "L" cx "," (+ cy radius) - "Z")) - -(defn- round-corner-path-br - [cx cy radius] - (dm/str - "M" (+ cx radius) "," cy - "L" (+ cx radius) "," (+ cy radius) - "L" cx "," (+ cy radius) - "Q" (+ cx radius) "," (+ cy radius) "," (+ cx radius) "," cy - "Z")) - -(mf/defc rules-axis - [{:keys [zoom zoom-inverse vbox axis offset]}] - (let [rules-width (* rules-width zoom-inverse) - step (calculate-step-size zoom) - clip-id (str "clip-rule-" (d/name axis)) - font-color font-color - rules-background rules-background] - - [:* - (let [{:keys [x y width height]} (get-background-area vbox zoom-inverse axis)] - [:rect {:x x :y y :width width :height height :style {:fill rules-background}}]) - - [:g.rules {:clipPath (str "url(#" clip-id ")")} - - [:defs - [:clipPath {:id clip-id} - (let [{:keys [x y width height]} (get-clip-area vbox zoom-inverse axis)] - [:rect {:x x :y y :width width :height height}])]] - - (let [{:keys [start end]} (get-rule-params vbox axis) - minv (max start -100000) - minv (* (mth/ceil (/ minv step)) step) - maxv (min end 100000) - maxv (* (mth/floor (/ maxv step)) step) - - ;; These extra operations ensure that we are selecting a frame its initial location is rendered in the rule - minv (+ minv (mod offset step)) - maxv (+ maxv (mod offset step))] - - (for [step-val (range minv (inc maxv) step)] - (let [{:keys [text-x text-y line-x1 line-y1 line-x2 line-y2]} - (get-rule-axis step-val vbox zoom-inverse axis)] - [:* {:key (dm/str "text-" (d/name axis) "-" step-val)} - [:text {:x text-x - :y text-y - :text-anchor "middle" - :dominant-baseline "middle" - :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill font-color}} - ;; If the guide is associated to a frame we show the position relative to the frame - (fmt/format-number (- step-val offset))] - - [:line {:key (str "line-" (d/name axis) "-" step-val) - :x1 line-x1 - :y1 line-y1 - :x2 line-x2 - :y2 line-y2 - :style {:stroke font-color - :stroke-width rules-width}}]])))]])) - -(mf/defc selection-area - [{:keys [vbox zoom-inverse selection-rect offset-x offset-y]}] - ;; When using the format-number callls we consider if the guide is associated to a frame and we show the position relative to it with the offset - (let [rules-background rules-background] - [:g.selection-area - [:defs - [:linearGradient {:id "selection-gradient-start"} - [:stop {:offset "0%" :stop-color rules-background :stop-opacity 0}] - [:stop {:offset "40%" :stop-color rules-background :stop-opacity 1}] - [:stop {:offset "100%" :stop-color rules-background :stop-opacity 1}]] - - [:linearGradient {:id "selection-gradient-end"} - [:stop {:offset "0%" :stop-color rules-background :stop-opacity 1}] - [:stop {:offset "60%" :stop-color rules-background :stop-opacity 1}] - [:stop {:offset "100%" :stop-color rules-background :stop-opacity 0}]]] - [:g - [:rect {:x (- (:x selection-rect) (* (* over-number-size over-number-percent) zoom-inverse)) - :y (:y vbox) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :fill "url('#selection-gradient-start')"}] - - [:rect {:x (- (:x2 selection-rect) (* over-number-size (- 1 over-number-percent))) - :y (:y vbox) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :fill "url('#selection-gradient-end')"}] - - [:rect {:x (:x selection-rect) - :y (:y vbox) - :width (:width selection-rect) - :height (* rule-area-size zoom-inverse) - :style {:fill selection-area-color - :fill-opacity selection-area-opacity}}] - - [:text {:x (- (:x1 selection-rect) (* 4 zoom-inverse)) - :y (+ (:y vbox) (* 10.6 zoom-inverse)) - :text-anchor "end" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:x1 selection-rect) offset-x))] - - [:text {:x (+ (:x2 selection-rect) (* 4 zoom-inverse)) - :y (+ (:y vbox) (* 10.6 zoom-inverse)) - :text-anchor "start" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:x2 selection-rect) offset-x))]] - - (let [center-x (+ (:x vbox) (* rule-area-half-size zoom-inverse)) - center-y (- (+ (:y selection-rect) (/ (:height selection-rect) 2)) (* rule-area-half-size zoom-inverse))] - - [:g {:transform (str "rotate(-90 " center-x "," center-y ")")} - [:rect {:x (- center-x (/ (:height selection-rect) 2) (* rule-area-half-size zoom-inverse)) - :y (- center-y (* rule-area-half-size zoom-inverse)) - :width (:height selection-rect) - :height (* rule-area-size zoom-inverse) - :style {:fill selection-area-color - :fill-opacity selection-area-opacity}}] - - [:rect {:x (- center-x (/ (:height selection-rect) 2) (* rule-area-half-size zoom-inverse) (* over-number-size zoom-inverse)) - :y (- center-y (* rule-area-half-size zoom-inverse)) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :style {:fill rules-background - :fill-opacity over-number-opacity}}] - - [:rect {:x (+ (- center-x (/ (:height selection-rect) 2) (* rule-area-half-size zoom-inverse)) (:height selection-rect)) - :y (- center-y (* rule-area-half-size zoom-inverse)) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :style {:fill rules-background - :fill-opacity over-number-opacity}}] - - [:text {:x (- center-x (/ (:height selection-rect) 2) (* 15 zoom-inverse)) - :y center-y - :text-anchor "end" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:y2 selection-rect) offset-y))] - - [:text {:x (+ center-x (/ (:height selection-rect) 2)) - :y center-y - :text-anchor "start" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:y1 selection-rect) offset-y))]])])) - -(mf/defc rules - {::mf/wrap-props false - ::mf/wrap [#(mf/memo' % (mf/check-props ["zoom" "vbox" "selected-shapes" "show-rules?"]))]} - [props] - (let [zoom (obj/get props "zoom") - zoom-inverse (obj/get props "zoom-inverse") - vbox (obj/get props "vbox") - offset-x (obj/get props "offset-x") - offset-y (obj/get props "offset-y") - selected-shapes (-> (obj/get props "selected-shapes") - (hooks/use-equal-memo)) - show-rules? (obj/get props "show-rules?") - - rules-background rules-background - border-radius (/ canvas-border-radius zoom) - - selection-rect - (mf/use-memo - (mf/deps selected-shapes) - #(when (d/not-empty? selected-shapes) - (gsh/shapes->rect selected-shapes)))] - - (when (some? vbox) - [:g.rules {:pointer-events "none"} - (when show-rules? - [:* - [:& rules-axis {:zoom zoom :zoom-inverse zoom-inverse :vbox vbox :axis :x :offset offset-x}] - [:& rules-axis {:zoom zoom :zoom-inverse zoom-inverse :vbox vbox :axis :y :offset offset-y}]]) - - ;; Draw the rules' rounded corners in the viewport corners - (let [{:keys [x y width height]} vbox - rule-area-size (if show-rules? (/ rule-area-size zoom) 0)] - [:* - [:path {:d (round-corner-path-tl (+ x rule-area-size) (+ y rule-area-size) border-radius) - :style {:fill rules-background}}] - - [:path {:d (round-corner-path-tr (+ x width (- border-radius)) (+ y rule-area-size) border-radius) - :style {:fill rules-background}}] - - [:path {:d (round-corner-path-bl (+ x rule-area-size) (+ y height (- border-radius)) border-radius) - :style {:fill rules-background}}] - - [:path {:d (round-corner-path-br (+ x (:width vbox) (- border-radius)) (+ y height (- border-radius)) border-radius) - :style {:fill rules-background}}]]) - - (when (and show-rules? (some? selection-rect)) - [:& selection-area {:zoom zoom - :zoom-inverse zoom-inverse - :vbox vbox - :selection-rect selection-rect - :offset-x offset-x - :offset-y offset-y}])])))