0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 02:28:18 -05:00

♻️ Refactor viewport sizing.

This commit is contained in:
Andrey Antukh 2020-05-07 15:15:39 +02:00 committed by Alonso Torres
parent 581b1912ae
commit 51356c10f5
10 changed files with 433 additions and 279 deletions

View file

@ -120,12 +120,18 @@
.workspace-viewport {
height: 100%;
overflow: scroll;
overflow: hidden;
transition: none;
width: 100%;
margin-top: 20px;
display: grid;
grid-template-rows: 20px 1fr;
grid-template-columns: 20px 1fr;
.viewport {
grid-column: 2 / span 1;
grid-row: 2 / span 1;
&.drawing {
cursor: cell;
}
@ -157,11 +163,21 @@
/* Rules */
.empty-rule-square {
grid-column: 1 / span 1;
grid-row: 1 / span 1;
background-color: $color-canvas;
z-index: 13;
border-right: 1px solid $color-gray-30;
border-bottom: 1px solid $color-gray-30;
}
.horizontal-rule {
transition: none;
position: absolute;
pointer-events: none;
top: 0px;
grid-column: 2 / span 1;
grid-row: 1 / span 1;
z-index: 13;
rect {
fill: $color-canvas;
@ -174,7 +190,9 @@
.vertical-rule {
transition: none;
pointer-events: none;
left: 0px;
grid-column: 1 / span 1;
grid-row: 2 / span 1;
z-index: 13;
rect {
fill: $color-canvas;

View file

@ -72,6 +72,12 @@
(def workspace-default
{:zoom 1
:size {:x 0
:y 0
:width c/viewport-width
:height c/viewport-width
:viewport-width c/viewport-width
:viewport-height c/viewport-height}
:flags #{}
:selected #{}
:drawing nil
@ -189,6 +195,56 @@
;; Workspace State Manipulation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Viewport Sizing
(defn initialize-viewport
[{:keys [width height] :as size}]
(ptk/reify ::initialize-viewport
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
(fn [local]
(-> local
(assoc :zoom 1)
(update :size assoc
:x 0
:y 0
:width width
:height height
:viewport-width width
:viewport-height height)))))))
(defn update-viewport-position
[{:keys [x y] :or {x identity y identity}}]
(ptk/reify ::update-viewport-position
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :size]
(fn [size]
(-> size
(update :x x)
(update :y y)))))))
(defn update-viewport-size
[{:keys [width height]}]
(ptk/reify ::update-viewport-size
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local
(fn [{:keys [size] :as local}]
(let [wprop (/ (:viewport-width size) width)
hprop (/ (:viewport-height size) height)
size' (-> size
(assoc :viewport-width width)
(assoc :viewport-height height)
(update :width #(/ % wprop))
(update :height #(/ % hprop)))]
(assoc local :size size')))))))
;; ---
(defn adjust-group-shapes
[ids]
(ptk/reify ::adjust-group-shapes
@ -269,41 +325,103 @@
;; --- Zoom Management
(def increase-zoom
(defn- impl-update-zoom
[local zoom]
(let [base-width (get-in local [:size :viewport-width])
base-height (get-in local [:size :viewport-height])
zoom (if (fn? zoom)
(zoom (:zoom local))
zoom)
width (/ base-width zoom)
height (/ base-height zoom)]
(-> local
(assoc :zoom zoom)
(update :size assoc :width width :height height))))
(defn increase-zoom
[center]
(ptk/reify ::increase-zoom
ptk/UpdateEvent
(update [_ state]
(let [increase #(nth c/zoom-levels
(+ (d/index-of c/zoom-levels %) 1)
(last c/zoom-levels))]
(update-in state [:workspace-local :zoom] (fnil increase 1))))))
(update state :workspace-local
#(impl-update-zoom % (fn [z] (* z 1.1)))))))
(def decrease-zoom
(defn decrease-zoom
[center]
(ptk/reify ::decrease-zoom
ptk/UpdateEvent
(update [_ state]
(let [decrease #(nth c/zoom-levels
(- (d/index-of c/zoom-levels %) 1)
(first c/zoom-levels))]
(update-in state [:workspace-local :zoom] (fnil decrease 1))))))
(update state :workspace-local
#(impl-update-zoom % (fn [z] (* z 0.9)))))))
(def reset-zoom
(ptk/reify ::reset-zoom
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :zoom] 1))))
(update state :workspace-local
#(impl-update-zoom % 1)))))
(def zoom-to-50
(ptk/reify ::zoom-to-50
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :zoom] 0.5))))
(update state :workspace-local
#(impl-update-zoom % 0.5)))))
(def zoom-to-200
(ptk/reify ::zoom-to-200
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :zoom] 2))))
(update state :workspace-local
#(impl-update-zoom % 2)))))
;; (defn impl-expand-rect
;; [{:keys [x y width height] :as rect} padding]
;; (assoc rect
;; :x (- x padding)
;; :y (- y padding)
;; :width (+ width padding)
;; :height (+ height padding)))
;; (def zoom-to-selected-shape
;; (ptk/reify ::zoom-to-selected-shape
;; ptk/UpdateEvent
;; (update [_ state]
;; (let [page-id (get-in state [:workspace-page :id])
;; objects (get-in state [:workspace-data page-id :objects])
;; selected (get-in state [:workspace-local :selected])
;; shapes (map #(get objects %) selected)
;; rect (geom/selection-rect shapes)
;; rect (impl-expand-rect rect 20)
;; ]
;; (update state :workspace-local
;; (fn [{:keys [size] :as local}]
;; (let [proportion (/ (:viewport-width size) (:viewport-height size))
;; width (:width rect)
;; height (/ (:width rect) proportion)
;; [width height] (if (> (:height rect) height)
;; [(* width proportion) (:height rect)]
;; [width height])
;; zoom (/ (:viewport-width size) width)]
;; ;; (js/console.log "proportion=" proportion
;; ;; "width=" width
;; ;; "height=" height
;; ;; "viewport-width=" (:viewport-width size)
;; ;; "viewport-height=" (:viewport-height size))
;; (-> local
;; (assoc :zoom zoom)
;; (update :size assoc
;; :x (:x rect)
;; :y (:y rect)
;; :width width
;; :height height
;; )))))))))
;; --- Selection Rect
@ -328,8 +446,8 @@
{:type :rect
:x start-x
:y start-y
:width (- end-x start-x)
:height (- end-y start-y)}))]
:width (mth/abs (- end-x start-x))
:height (mth/abs (- end-y start-y))}))]
(ptk/reify ::handle-selection
ptk/WatchEvent
(watch [_ state stream]
@ -1341,6 +1459,7 @@
{"ctrl+shift+m" #(st/emit! (toggle-layout-flag :sitemap))
"ctrl+shift+i" #(st/emit! (toggle-layout-flag :libraries))
"ctrl+shift+l" #(st/emit! (toggle-layout-flag :layers))
"ctrl+shift+r" #(st/emit! (toggle-layout-flag :rules))
"+" #(st/emit! increase-zoom)
"-" #(st/emit! decrease-zoom)
"ctrl+g" #(st/emit! create-group)
@ -1348,6 +1467,7 @@
"shift+0" #(st/emit! zoom-to-50)
"shift+1" #(st/emit! reset-zoom)
"shift+2" #(st/emit! zoom-to-200)
;; "shift+2" #(st/emit! zoom-to-selected-shape)
"ctrl+d" #(st/emit! duplicate-selected)
"ctrl+z" #(st/emit! dwc/undo)
"ctrl+shift+z" #(st/emit! dwc/redo)

View file

@ -25,10 +25,6 @@
;; -- Helpers
(defn- apply-zoom
[point]
(gpt/divide point (gpt/point @refs/selected-zoom)))
;; For each of the 8 handlers gives the modifier for resize
;; for example, right will only grow in the x coordinate and left
;; will grow in the inverse of the x coordinate
@ -146,7 +142,6 @@
resizing-shapes (map #(get-in state [:workspace-data page-id :objects %]) ids)]
(rx/concat
(->> ms/mouse-position
(rx/map apply-zoom)
;; (rx/mapcat apply-grid-alignment)
(rx/with-latest vector ms/mouse-position-ctrl)
(rx/map normalize-proportion-lock)
@ -171,7 +166,7 @@
(let [stoper (rx/filter ms/mouse-up? stream)
group (gsh/selection-rect shapes)
group-center (gsh/center group)
initial-angle (gpt/angle (apply-zoom @ms/mouse-position) group-center)
initial-angle (gpt/angle @ms/mouse-position group-center)
calculate-angle (fn [pos ctrl?]
(let [angle (- (gpt/angle pos group-center) initial-angle)
angle (if (neg? angle) (+ 360 angle) angle)
@ -187,7 +182,6 @@
angle))]
(rx/concat
(->> ms/mouse-position
(rx/map apply-zoom)
(rx/with-latest vector ms/mouse-position-ctrl)
(rx/map (fn [[pos ctrl?]]
(let [delta-angle (calculate-angle pos ctrl?)]
@ -205,12 +199,11 @@
(ptk/reify ::start-move-selected
ptk/WatchEvent
(watch [_ state stream]
(let [initial (apply-zoom @ms/mouse-position)
(let [initial @ms/mouse-position
selected (get-in state [:workspace-local :selected])
stopper (rx/filter ms/mouse-up? stream)]
(->> ms/mouse-position
(rx/take-until stopper)
(rx/map apply-zoom)
(rx/map #(gpt/to-vec initial %))
(rx/map #(gpt/length %))
(rx/filter #(> % 0.5))
@ -234,7 +227,6 @@
(rx/concat
(->> ms/mouse-position
(rx/take-until stopper)
(rx/map apply-zoom)
(rx/map #(gpt/to-vec from-position %))
(rx/map (snap/closest-snap-move snap-data shapes))
(rx/map gmt/translate-matrix)

View file

@ -77,15 +77,20 @@
[current]
(->> (rx/concat (rx/of current)
(rx/sample 10 mouse-position))
(rx/map #(gpt/divide % (gpt/point @refs/selected-zoom)))
;; (rx/mapcat (fn [point]
;; (if @refs/selected-alignment
;; (uwrk/align-point point)
;; (rx/of point))))
(rx/buffer 2 1)
(rx/map (fn [[old new]]
(gpt/subtract new old)))))
(defonce mouse-position-delta
(let [sub (rx/behavior-subject nil)
ob (->> st/stream
(rx/filter pointer-event?)
(rx/filter #(= :delta (:source %)))
(rx/map :pt))]
(rx/subscribe-with ob sub)
sub))
(defonce viewport-scroll
(let [sub (rx/behavior-subject nil)
sob (->> (rx/filter scroll-event? st/stream)

View file

@ -45,23 +45,29 @@
right-sidebar? (not (empty? (keep layout [:icons :drawtools :element-options])))
classes (classnames
:no-tool-bar-right (not right-sidebar?)
:no-tool-bar-left (not left-sidebar?))]
:no-tool-bar-left (not left-sidebar?))
local (mf/deref refs/workspace-local)]
[:*
(when (:colorpalette layout)
[:& colorpalette {:left-sidebar? left-sidebar?}])
[:section.workspace-content {:class classes}
[:& history-dialog]
;; Rules
(when (contains? layout :rules)
[:*
[:& horizontal-rule]
[:& vertical-rule]])
[:section.workspace-viewport
(when (contains? layout :rules)
[:*
[:div.empty-rule-square]
[:& horizontal-rule {:zoom (:zoom local)
:size (:size local)}]
[:& vertical-rule {:zoom (:zoom local 1)
:size (:size local)}]])
[:section.workspace-viewport {:id "workspace-viewport"}
[:& viewport {:page page :file file}]]]
[:& viewport {:page page
:file file
:local local}]]]
[:& left-toolbar {:page page :layout layout}]

View file

@ -132,7 +132,10 @@
point-snap (snap/closest-snap-point snap-data [shape] point)
deltav (gpt/to-vec initial point-snap)
scalev (gpt/divide (gpt/add shapev deltav) shapev)
scalev (if lock? (let [v (max (:x scalev) (:y scalev))] (gpt/point v v)) scalev)]
scalev (if lock?
(let [v (max (:x scalev) (:y scalev))]
(gpt/point v v))
scalev)]
(-> shape
(assoc-in [:modifiers :resize-vector] scalev)
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
@ -144,13 +147,12 @@
(ptk/reify ::handle-drawing-generic
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [zoom flags]} (:workspace-local state)
(let [{:keys [flags]} (:workspace-local state)
stoper? #(or (ms/mouse-up? %) (= % :interrupt))
stoper (rx/filter stoper? stream)
initial @ms/mouse-position
snap-data (get state :workspace-snap-data)
mouse (->> ms/mouse-position
(rx/map #(gpt/divide % (gpt/point zoom))))
mouse ms/mouse-position
page-id (get state :current-page-id)
objects (get-in state [:workspace-data page-id :objects])
@ -204,15 +206,14 @@
(ptk/reify ::handle-drawing-path
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [zoom flags]} (:workspace-local state)
(let [{:keys [flags]} (:workspace-local state)
last-point (volatile! (gpt/divide @ms/mouse-position (gpt/point zoom)))
last-point (volatile! @ms/mouse-position)
stoper (->> (rx/filter stoper-event? stream)
(rx/share))
mouse (->> (rx/sample 10 ms/mouse-position)
(rx/map #(gpt/divide % (gpt/point zoom))))
mouse (rx/sample 10 ms/mouse-position)
points (->> stream
(rx/filter ms/mouse-click?)
@ -277,10 +278,10 @@
(ptk/reify ::handle-drawing-curve
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [zoom flags]} (:workspace-local state)
(prn "handle-drawing-curve")
(let [{:keys [flags]} (:workspace-local state)
stoper (rx/filter stoper-event? stream)
mouse (->> (rx/sample 10 ms/mouse-position)
(rx/map #(gpt/divide % (gpt/point zoom))))]
mouse (rx/sample 10 ms/mouse-position)]
(rx/concat
(rx/of initialize-drawing)
(->> mouse
@ -324,15 +325,14 @@
(declare path-draw-area)
(mf/defc draw-area
[{:keys [zoom shape] :as props}]
[{:keys [shape] :as props}]
(when (:id shape)
(case (:type shape)
(:path :curve) [:& path-draw-area {:shape shape}]
[:& generic-draw-area {:shape shape
:zoom zoom}])))
[:& generic-draw-area {:shape shape}])))
(mf/defc generic-draw-area
[{:keys [shape zoom]}]
[{:keys [shape]}]
(let [{:keys [x y width height]} (geom/selection-rect-shape shape)]
(when (and x y)
[:g

View file

@ -37,13 +37,7 @@
on-zoom-to-100
on-zoom-to-200]
:as props}]
(let [show-dropdown? (mf/use-state false)
;; increase #(st/emit! dv/increase-zoom)
;; decrease #(st/emit! dv/decrease-zoom)
;; zoom-to-50 #(st/emit! dv/zoom-to-50)
;; zoom-to-100 #(st/emit! dv/reset-zoom)
;; zoom-to-200 #(st/emit! dv/zoom-to-200)
]
(let [show-dropdown? (mf/use-state false)]
[:div.zoom-widget {:on-click #(reset! show-dropdown? true)}
[:span {} (str (mth/round (* 100 zoom)) "%")]
[:span.dropdown-button i/arrow-down]

View file

@ -2,156 +2,129 @@
;; 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) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; 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 uxbox.main.ui.workspace.rules
(:require
[beicon.core :as rx]
[cuerdas.core :as str]
[rumext.alpha :as mf]
[uxbox.main.constants :as c]
[uxbox.main.refs :as refs]
[uxbox.main.store :as s]
[uxbox.main.streams :as ms]
[uxbox.main.ui.hooks :refer [use-rxsub]]
[uxbox.util.dom :as dom]))
[uxbox.util.object :as obj]))
;; --- Constants & Helpers
(def rule-padding 20)
(def step-padding 20)
(def step-size 10)
(def scroll-padding 50)
(def +ticks+ (range 0 c/viewport-width step-size))
(defn big-ticks-mod [zoom] (/ 100 zoom))
(defn mid-ticks-mod [zoom] (/ 50 zoom))
(defn- make-vertical-tick
[zoom acc value]
(let [big-ticks-mod (big-ticks-mod zoom)
mid-ticks-mod (mid-ticks-mod zoom)
pos (+ (* value zoom)
rule-padding
scroll-padding)]
(cond
(< (mod value big-ticks-mod) step-size)
(conj acc (str/format "M %s %s L %s %s" pos 5 pos step-padding))
(< (mod value mid-ticks-mod) step-size)
(conj acc (str/format "M %s %s L %s %s" pos 10 pos step-padding))
:else
(conj acc (str/format "M %s %s L %s %s" pos 15 pos step-padding)))))
(defn- make-horizontal-tick
[zoom acc value]
(let [big-ticks-mod (big-ticks-mod zoom)
mid-ticks-mod (mid-ticks-mod zoom)
pos (+ (* value zoom)
scroll-padding)]
(cond
(< (mod value big-ticks-mod) step-size)
(conj acc (str/format "M %s %s L %s %s" 5 pos step-padding pos))
(< (mod value mid-ticks-mod) step-size)
(conj acc (str/format "M %s %s L %s %s" 10 pos step-padding pos))
:else
(conj acc (str/format "M %s %s L %s %s" 15 pos step-padding pos)))))
;; --- Horizontal Text Label
(mf/defc horizontal-text-label
[{:keys [zoom value] :as props}]
(let [big-ticks-mod (big-ticks-mod zoom)
pos (+ (* value zoom)
rule-padding
scroll-padding)]
(when (< (mod value big-ticks-mod) step-size)
[:text {:x (+ pos 2)
:y 13
:key (str pos)
:fill "#9da2a6"
:style {:font-size "12px"}}
value])))
;; --- Horizontal Text Label
(mf/defc vertical-text-label
[{:keys [zoom value] :as props}]
(let [big-ticks-mod (big-ticks-mod zoom)
pos (+ (* value zoom)
scroll-padding)]
(when (< (mod value big-ticks-mod) step-size)
[:text {:y (- pos 3)
:x 5
:key (str pos)
:fill "#9da2a6"
:transform (str/format "rotate(90 0 %s)" pos)
:style {:font-size "12px"}}
value])))
;; --- Horizontal Rule Ticks (Component)
(mf/defc horizontal-rule-ticks
{:wrap [mf/memo]}
[{:keys [zoom]}]
(let [path (reduce (partial make-vertical-tick zoom) [] +ticks+)]
[:g
[:path {:d (str/join " " path)}]
(for [tick +ticks+]
[:& horizontal-text-label {:zoom zoom :value tick :key tick}])]))
;; --- Vertical Rule Ticks (Component)
(mf/defc vertical-rule-ticks
{:wrap [mf/memo]}
[{:keys [zoom]}]
(let [path (reduce (partial make-horizontal-tick zoom) [] +ticks+)]
[:g
[:path {:d (str/join " " path)}]
(for [tick +ticks+]
[:& vertical-text-label {:zoom zoom :value tick :key tick}])]))
;; --- Horizontal Rule (Component)
(def STEP-PADDING 20)
(mf/defc horizontal-rule
{:wrap [mf/memo]}
[props]
(let [scroll (use-rxsub ms/viewport-scroll)
zoom (mf/deref refs/selected-zoom)
translate-x (- (- scroll-padding) (:x scroll))]
[:svg.horizontal-rule
{:width c/viewport-width
:height 20}
[:rect {:height 20
:width c/viewport-width}]
[:g {:transform (str "translate(" translate-x ", 0)")}
[:& horizontal-rule-ticks {:zoom zoom}]]]))
[{:keys [zoom size]}]
(let [canvas (mf/use-ref)
{:keys [x viewport-width width]} size]
(mf/use-layout-effect
(mf/deps viewport-width width x zoom)
(fn []
(let [node (mf/ref-val canvas)
dctx (.getContext node "2d")
btm 1
trx (- (* (- 0 x) zoom) 50)
min-val (js/Math.round x)
max-val (js/Math.round (+ x (/ viewport-width zoom)))
tmp0 (js/Math.abs (- max-val min-val))
tmp1 (js/Math.round (/ tmp0 200))
btm (max (* btm (* 10 tmp1)) 1)]
(obj/set! node "width" viewport-width)
(obj/set! dctx "fillStyle" "#E8E9EA")
(.fillRect dctx 0 0 viewport-width 20)
(.save dctx)
(.translate dctx trx 0)
(obj/set! dctx "font" "12px serif")
(obj/set! dctx "fillStyle" "#7B7D85")
(obj/set! dctx "strokeStyle" "#7B7D85")
(obj/set! dctx "textAlign" "center")
(loop [i min-val]
(when (< i max-val)
(let [pos (+ (* i zoom) 50)]
(when (= (mod i btm) 0)
(.fillText dctx (str i) (- pos 0) 13))
(recur (+ i 1)))))
(let [path (js/Path2D.)]
(loop [i min-val]
(if (> i max-val)
(.stroke dctx path)
(let [pos (+ (* i zoom) 50)]
(when (= (mod i btm) 0)
(.moveTo path pos 17)
(.lineTo path pos STEP-PADDING))
(recur (inc i))))))
(.restore dctx))))
[:canvas.horizontal-rule {:ref canvas :width (:viewport-width size) :height 20}]))
;; --- Vertical Rule (Component)
(mf/defc vertical-rule
{:wrap [mf/memo]}
[props]
(let [scroll (use-rxsub ms/viewport-scroll)
zoom (or (mf/deref refs/selected-zoom) 1)
scroll-y (:y scroll)
translate-y (+ (- scroll-padding)
(- (:y scroll)))
]
[:svg.vertical-rule {:width 20
;; :x 0 :y 0
:height c/viewport-height}
{::mf/wrap [mf/memo #(mf/throttle % 60)]}
[{:keys [zoom size]}]
(let [canvas (mf/use-ref)
{:keys [y height viewport-height]} size]
(mf/use-layout-effect
(mf/deps height y zoom)
(fn []
(let [node (mf/ref-val canvas)
dctx (.getContext node "2d")
btm 1
try (- (* (- 0 y) zoom) 50)
min-val (js/Math.round y)
max-val (js/Math.round (+ y (/ viewport-height zoom)))
tmp0 (js/Math.abs (- max-val min-val))
tmp1 (js/Math.round (/ tmp0 100))
btm (max (* btm (* 10 tmp1)) 1)]
(obj/set! node "height" viewport-height)
(obj/set! dctx "fillStyle" "#E8E9EA")
(.fillRect dctx 0 0 20 viewport-height)
(obj/set! dctx "font" "11px serif")
(obj/set! dctx "fillStyle" "#7B7D85")
(obj/set! dctx "strokeStyle" "#7B7D85")
(obj/set! dctx "textAlign" "center")
(.translate dctx 0 try)
(loop [i min-val]
(when (< i max-val)
(let [pos (+ (* i zoom) 50)]
(when (= (mod i btm) 0)
(.save dctx)
(.translate dctx 12 pos)
(.rotate dctx (/ (* 270 js/Math.PI) 180))
(.fillText dctx (str i) 0 0)
(.restore dctx))
(recur (inc i)))))
(let [path (js/Path2D.)]
(loop [i min-val]
(if (> i max-val)
(.stroke dctx path)
(let [pos (+ (* i zoom) 50)]
(when (= (mod i btm) 0)
(.moveTo path 17 pos)
(.lineTo path STEP-PADDING pos))
(recur (inc i)))))))))
[:canvas.vertical-rule {:ref canvas :width 20 :height height}]))
[:g {:transform (str "translate(0, " (+ translate-y 20) ")")}
[:& vertical-rule-ticks {:zoom zoom}]]
[:rect {:x 0
:y 0
:height 20
:width 20}]]))

View file

@ -9,9 +9,9 @@
(ns uxbox.main.ui.workspace.viewport
(:require
[cuerdas.core :as str]
[beicon.core :as rx]
[goog.events :as events]
[lentes.core :as l]
[potok.core :as ptk]
[rumext.alpha :as mf]
[uxbox.main.ui.icons :as i]
@ -30,6 +30,7 @@
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
[uxbox.main.ui.workspace.presence :as presence]
[uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]]
[uxbox.util.math :as mth]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.perf :as perf]
@ -41,7 +42,6 @@
(mf/defc coordinates
[{:keys [zoom] :as props}]
(let [coords (some-> (hooks/use-rxsub ms/mouse-position)
(gpt/divide (gpt/point zoom zoom))
(gpt/round 0))]
[:ul.coordinates
[:span {:alt "x"}
@ -87,22 +87,21 @@
;; --- Viewport Positioning
(def handle-viewport-positioning
(letfn [(on-point [dom reference point]
(let [{:keys [x y]} (gpt/subtract point reference)
cx (.-scrollLeft dom)
cy (.-scrollTop dom)]
(set! (.-scrollLeft dom) (- cx x))
(set! (.-scrollTop dom) (- cy y))))]
(ptk/reify ::handle-viewport-positioning
ptk/EffectEvent
(effect [_ state stream]
(let [stoper (rx/filter #(= ::finish-positioning %) stream)
reference @ms/mouse-position
dom (dom/get-element "workspace-viewport")]
(-> (rx/take-until stoper ms/mouse-position)
(rx/subscribe #(on-point dom reference %))))))))
(defn- handle-viewport-positioning
[viewport-ref]
(let [node (mf/ref-val viewport-ref)
stoper (rx/filter #(= ::finish-positioning %) st/stream)
stream (->> ms/mouse-position-delta
(rx/take-until stoper))]
(rx/subscribe stream
(fn [delta]
(let [vbox (.. ^js node -viewBox -baseVal)
zoom (gpt/point @refs/selected-zoom)
delta (gpt/divide delta zoom)]
(st/emit! (dw/update-viewport-position
{:x #(- % (:x delta))
:y #(- % (:y delta))})))))))
;; --- Viewport
@ -125,16 +124,16 @@
[:& shape-wrapper {:shape item
:key (:id item)}]))]))
(mf/defc viewport
[{:keys [page] :as props}]
(let [{:keys [drawing-tool
[{:keys [page local] :as props}]
(let [
{:keys [drawing-tool
zoom
flags
size
edition
tooltip
selected]
:as local} (mf/deref refs/workspace-local)
selected]} local
viewport-ref (mf/use-ref nil)
last-position (mf/use-var nil)
@ -146,16 +145,25 @@
(mf/deps drawing-tool edition)
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
(let [event (.-nativeEvent event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :down ctrl? shift?))
(when (and (not edition)
(= 1 (.-which (.-nativeEvent event))))
(cond
(and (not edition) (= 1 (.-which event)))
(if drawing-tool
(st/emit! (start-drawing drawing-tool))
(st/emit! dw/handle-selection))))))
(st/emit! dw/handle-selection))
(and (not edition)
(= 2 (.-which event)))
(handle-viewport-positioning viewport-ref)
:else
(js/console.log "on-mouse-down" event)))))
on-context-menu
(mf/use-callback
@ -169,11 +177,15 @@
(mf/use-callback
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
(let [event (.-nativeEvent event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :up ctrl? shift?)))))
(st/emit! (ms/->MouseEvent :up ctrl? shift?))
(when (= 2 (.-which event))
(st/emit! ::finish-positioning)))))
on-click
(mf/use-callback
@ -208,7 +220,7 @@
(when-not (.-repeat bevent)
(st/emit! (ms/->KeyboardEvent :down key ctrl? shift?))
(when (kbd/space? event)
(st/emit! handle-viewport-positioning))))))
(handle-viewport-positioning viewport-ref))))))
on-key-up
(mf/use-callback
@ -220,21 +232,33 @@
:shift? shift?
:ctrl? ctrl?}]
(when (kbd/space? event)
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
(st/emit! ::finish-positioning))
(st/emit! (ms/->KeyboardEvent :up key ctrl? shift?)))))
translate-point-to-viewport
(fn [pt]
(let [viewport (mf/ref-val viewport-ref)
vbox (.. ^js viewport -viewBox -baseVal)
brect (.getBoundingClientRect viewport)
brect (gpt/point (d/parse-integer (.-left brect))
(d/parse-integer (.-top brect)))]
(gpt/subtract pt brect)))
(d/parse-integer (.-top brect)))
box (gpt/point (.-x vbox)
(.-y vbox))
]
(-> (gpt/subtract pt brect)
(gpt/divide (gpt/point @refs/selected-zoom))
(gpt/add box))))
on-mouse-move
(fn [event]
(let [pt (gpt/point (.-clientX event) (.-clientY event))
pt (translate-point-to-viewport pt)]
(let [event (.getBrowserEvent event)
pt (gpt/point (.-clientX event) (.-clientY event))
pt (translate-point-to-viewport pt)
delta (gpt/point (.-movementX event)
(.-movementY event))]
(st/emit! (ms/->PointerEvent :delta delta
(kbd/ctrl? event)
(kbd/shift? event)))
(st/emit! (ms/->PointerEvent :viewport pt
(kbd/ctrl? event)
(kbd/shift? event)))))
@ -242,30 +266,20 @@
on-mouse-wheel
(mf/use-callback
(fn [event]
(when (kbd/ctrl? event)
;; Disable browser zoom with ctrl+mouse wheel
(dom/prevent-default event)
(let [event (.getBrowserEvent event)]
(if (pos? (.-deltaY event))
(st/emit! dw/decrease-zoom)
(st/emit! dw/increase-zoom))))))
(dom/prevent-default event)
(dom/stop-propagation event)
on-mount
(fn []
(let [key1 (events/listen js/document EventType.KEYDOWN on-key-down)
key2 (events/listen js/document EventType.KEYUP on-key-up)
dnode (mf/ref-val viewport-ref)
key3 (events/listen dnode EventType.MOUSEMOVE on-mouse-move)
;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895
key4 (events/listen js/window EventType.WHEEL on-mouse-wheel
#js {"passive" false})]
(fn []
(events/unlistenByKey key1)
(events/unlistenByKey key2)
(events/unlistenByKey key3)
(events/unlistenByKey key4)
)))
(if (kbd/ctrl? event)
(let [event (.getBrowserEvent event)
pos @ms/mouse-position]
(if (pos? (.-deltaY event))
(st/emit! (dw/decrease-zoom pos))
(st/emit! (dw/increase-zoom pos))))
(let [event (.getBrowserEvent event)
delta (.-deltaY ^js event)]
(if (kbd/shift? event)
(st/emit! (dw/update-viewport-position {:x #(+ % delta)}))
(st/emit! (dw/update-viewport-position {:y #(+ % delta)})))))))
on-drag-over
;; Should prevent only events that we'll handle on-drop
@ -280,13 +294,54 @@
final-y (- (:y viewport-coord) (/ (:height shape) 2))]
(st/emit! (dw/add-shape (-> shape
(assoc :x final-x)
(assoc :y final-y))))))]
(assoc :y final-y))))))
on-resize
(fn [event]
(let [node (mf/ref-val viewport-ref)
parent (.-parentElement ^js node)]
(st/emit! (dw/update-viewport-size
{:width (.-clientWidth ^js parent)
:height (.-clientHeight ^js parent)}))))
on-mount
(fn []
(let [node (mf/ref-val viewport-ref)
prnt (.-parentElement ^js node)
key1 (events/listen js/document EventType.KEYDOWN on-key-down)
key2 (events/listen js/document EventType.KEYUP on-key-up)
key3 (events/listen node EventType.MOUSEMOVE on-mouse-move)
;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895
key4 (events/listen js/window EventType.WHEEL on-mouse-wheel
#js {"passive" false})
key5 (events/listen js/window EventType.RESIZE on-resize)]
(st/emit! (dw/initialize-viewport
{:width (.-clientWidth ^js prnt)
:height (.-clientHeight ^js prnt)}))
(fn []
(events/unlistenByKey key1)
(events/unlistenByKey key2)
(events/unlistenByKey key3)
(events/unlistenByKey key4)
(events/unlistenByKey key5)
)))
]
(mf/use-effect on-mount)
[:*
[:& coordinates {:zoom zoom}]
[:svg.viewport {:width (* c/viewport-width zoom)
:height (* c/viewport-height zoom)
[:svg.viewport {
:width (:viewport-width size)
:height (:viewport-height size)
:view-box (str/join " " [(:x size)
(:y size)
(:width size)
(:height size)])
:ref viewport-ref
:class (when drawing-tool "drawing")
:on-context-menu on-context-menu
@ -296,9 +351,7 @@
:on-mouse-up on-mouse-up
:on-drag-over on-drag-over
:on-drop on-drop}
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
;; [:& perf/profiler {:label "viewport-frames"}
[:g
[:& frames {:key (:id page)}]
(when (seq selected)
@ -314,7 +367,7 @@
[:& snap-feedback]
(if (contains? flags :grid)
(when (contains? flags :grid)
[:& grid])]
(when tooltip
@ -325,3 +378,4 @@
[:& presence/active-cursors {:page page}]
[:& selection-rect {:data (:selrect local)}]]]))

View file

@ -594,14 +594,6 @@
;; --- Helpers
(defn apply-zoom
[selrect zoom]
(assoc selrect
:x (/ (:x selrect) (:x zoom))
:y (/ (:y selrect) (:y zoom))
:width (/ (:width selrect) (:x zoom))
:height (/ (:height selrect) (:y zoom))))
(defn contained-in?
"Check if a shape is contained in the
provided selection rect."