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:
parent
581b1912ae
commit
51356c10f5
10 changed files with 433 additions and 279 deletions
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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}]]))
|
||||
|
|
|
@ -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)}]]]))
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
Loading…
Add table
Reference in a new issue