0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 00:01:51 -05:00

Add border to rulers

This commit is contained in:
alonso.torres 2024-02-02 13:11:24 +01:00 committed by Andrey Antukh
parent 1df4118523
commit 564843b297
11 changed files with 379 additions and 382 deletions

View file

@ -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

View file

@ -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")

View file

@ -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)."

View file

@ -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))))

View file

@ -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])]]

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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))))]]))])))

View file

@ -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}])])))

View file

@ -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}])])))