mirror of
https://github.com/penpot/penpot.git
synced 2026-05-22 00:13:41 +00:00
192 lines
7.0 KiB
Clojure
192 lines
7.0 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.main.data.workspace.viewport
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[app.common.files.helpers :as cfh]
|
|
[app.common.geom.align :as gal]
|
|
[app.common.geom.point :as gpt]
|
|
[app.common.geom.rect :as gpr]
|
|
[app.common.geom.shapes :as gsh]
|
|
[app.common.math :as mth]
|
|
[app.main.data.helpers :as dsh]
|
|
[app.util.mouse :as mse]
|
|
[beicon.v2.core :as rx]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
(defn initialize-viewport
|
|
[{:keys [width height] :as size}]
|
|
|
|
(assert (gpr/rect? size)
|
|
"expected `size` to be a rect instance")
|
|
|
|
(letfn [(update* [{:keys [vport] :as local}]
|
|
(let [wprop (/ (:width vport) width)
|
|
hprop (/ (:height vport) height)]
|
|
(-> local
|
|
(assoc :vport size)
|
|
(update :vbox (fn [vbox]
|
|
(-> vbox
|
|
(update :width #(/ % wprop))
|
|
(update :height #(/ % hprop))
|
|
(gpr/update-rect :size)))))))
|
|
|
|
(initialize [state local]
|
|
(let [page-id (:current-page-id state)
|
|
objects (dsh/lookup-page-objects state page-id)
|
|
shapes (cfh/get-immediate-children objects)
|
|
srect (gsh/shapes->rect shapes)
|
|
local (assoc local :vport size :zoom 1 :zoom-inverse 1 :hide-toolbar false)]
|
|
|
|
(cond
|
|
(or (not (d/num? (:width srect)))
|
|
(not (d/num? (:height srect))))
|
|
(assoc local :vbox (assoc size :x 0 :y 0))
|
|
|
|
(or (> (:width srect) width)
|
|
(> (:height srect) height))
|
|
(let [srect (gal/adjust-to-viewport size srect {:padding 40})
|
|
zoom (/ (:width size) (:width srect))]
|
|
|
|
(-> local
|
|
(assoc :zoom zoom)
|
|
(assoc :zoom-inverse (/ 1 zoom))
|
|
(update :vbox (fn [vbox]
|
|
(-> (merge vbox srect)
|
|
(gpr/make-rect))))))
|
|
|
|
:else
|
|
(let [vx (+ (:x srect)
|
|
(/ (- (:width srect) width) 2))
|
|
vy (+ (:y srect)
|
|
(/ (- (:height srect) height) 2))
|
|
vbox (-> size
|
|
(assoc :x vx)
|
|
(assoc :y vy)
|
|
(gpr/update-rect :position))]
|
|
(assoc local :vbox vbox)))))
|
|
|
|
(setup [state local]
|
|
(if (and (:vbox local) (:vport local))
|
|
(update* local)
|
|
(initialize state local)))]
|
|
|
|
(ptk/reify ::initialize-viewport
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update state :workspace-local
|
|
(fn [local]
|
|
(setup state local)))))))
|
|
|
|
(defn calculate-centered-viewbox
|
|
"Updates the viewbox coordinates for a given center position"
|
|
[local position]
|
|
(let [vbox (:vbox local)
|
|
nw (/ (:width vbox) 2)
|
|
nh (/ (:height vbox) 2)
|
|
nx (- (:x position) nw)
|
|
ny (- (:y position) nh)]
|
|
(update local :vbox assoc :x nx :y ny)))
|
|
|
|
(defn update-viewport-position-center
|
|
[position]
|
|
(assert (gpt/point? position) "expected a point instance for `position` param")
|
|
|
|
(ptk/reify ::update-viewport-position-center
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update state :workspace-local calculate-centered-viewbox position))))
|
|
|
|
(defn update-viewport-position
|
|
[{:keys [x y] :or {x identity y identity}}]
|
|
|
|
(dm/assert!
|
|
"expected function for `x`"
|
|
(fn? x))
|
|
(dm/assert!
|
|
"expected function for `y`"
|
|
(fn? y))
|
|
|
|
(ptk/reify ::update-viewport-position
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update-in state [:workspace-local :vbox]
|
|
(fn [vbox]
|
|
(-> vbox
|
|
(update :x x)
|
|
(update :y y)))))))
|
|
|
|
(defn update-viewport-size
|
|
[resize-type {:keys [width height] :as size}]
|
|
(ptk/reify ::update-viewport-size
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update state :workspace-local
|
|
(fn [{:keys [vport] :as local}]
|
|
(if (or (nil? vport)
|
|
(mth/almost-zero? width)
|
|
(mth/almost-zero? height))
|
|
;; If we have a resize to zero just keep the old value
|
|
local
|
|
(let [wprop (/ (:width vport) width)
|
|
hprop (/ (:height vport) height)
|
|
|
|
vbox (:vbox local)
|
|
vbox-x (:x vbox)
|
|
vbox-y (:y vbox)
|
|
vbox-width (:width vbox)
|
|
vbox-height (:height vbox)
|
|
|
|
vbox-width' (/ vbox-width wprop)
|
|
vbox-height' (/ vbox-height hprop)
|
|
|
|
vbox-x'
|
|
(case resize-type
|
|
:left (+ vbox-x (- vbox-width vbox-width'))
|
|
:right vbox-x
|
|
(+ vbox-x (/ (- vbox-width vbox-width') 2)))
|
|
|
|
vbox-y'
|
|
(case resize-type
|
|
:top (+ vbox-y (- vbox-height vbox-height'))
|
|
:bottom vbox-y
|
|
(+ vbox-y (/ (- vbox-height vbox-height') 2)))]
|
|
(-> local
|
|
(assoc :vport size)
|
|
(assoc-in [:vbox :x] vbox-x')
|
|
(assoc-in [:vbox :y] vbox-y')
|
|
(assoc-in [:vbox :width] vbox-width')
|
|
(assoc-in [:vbox :height] vbox-height')))))))))
|
|
|
|
(defn start-panning []
|
|
(ptk/reify ::start-panning
|
|
ptk/WatchEvent
|
|
(watch [_ state stream]
|
|
(let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning)))
|
|
zoom (get-in state [:workspace-local :zoom])]
|
|
(when-not (get-in state [:workspace-local :panning])
|
|
(rx/concat
|
|
(rx/of #(-> % (assoc-in [:workspace-local :panning] true)))
|
|
(->> stream
|
|
(rx/filter mse/pointer-event?)
|
|
(rx/filter #(= :delta (:source %)))
|
|
(rx/take-until stopper)
|
|
;; Some events are executed in synchronous way like panning with backspace pressed
|
|
(rx/observe-on :af)
|
|
(rx/map (fn [event]
|
|
(let [delta (dm/get-prop event :pt)]
|
|
(update-viewport-position {:x #(- % (/ (:x delta) zoom))
|
|
:y #(- % (/ (:y delta) zoom))})))))))))))
|
|
|
|
(defn finish-panning []
|
|
(ptk/reify ::finish-panning
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(-> state
|
|
(update :workspace-local dissoc :panning)))))
|