diff --git a/CHANGES.md b/CHANGES.md index 6d5e6b51e8..9377f4d9d5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,8 +6,9 @@ ### :sparkles: New features +- Scroll bars [Taiga #2550](https://tree.taiga.io/project/penpot/task/2550) - Add select layer option to context menu [Taiga #2474](https://tree.taiga.io/project/penpot/us/2474). -- Guides [Taiga #290](https://tree.taiga.io/project/penpot/us/290?milestone=307334) +- Guides [Taiga #290](https://tree.taiga.io/project/penpot/us/290) - Improve file menu by adding semantically groups [Github #1203](https://github.com/penpot/penpot/issues/1203). - Add update components in bulk option in context menu [Taiga #1975](https://tree.taiga.io/project/penpot/us/1975). - Create first E2E tests [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608), [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608) diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index e3767bd000..101ec69bce 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -138,10 +138,10 @@ .coordinates { background-color: $color-dark-bg; border-radius: $br-small; - bottom: -10px; + bottom: 0px; padding-left: 5px; position: fixed; - right: calc(#{$width-settings-bar} + 10px); + right: calc(#{$width-settings-bar} + 14px); text-align: center; width: 125px; white-space: nowrap; diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 86b7441665..1f2860f71a 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -28,6 +28,7 @@ [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.scroll-bars :as scroll-bars] [app.main.ui.workspace.viewport.selection :as selection] [app.main.ui.workspace.viewport.snap-distances :as snap-distances] [app.main.ui.workspace.viewport.snap-points :as snap-points] @@ -250,7 +251,13 @@ :hover (when (not= :frame (:type @hover)) #{(or @frame-hover (:id @hover))}) :edition edition - :zoom zoom}]) + :zoom zoom}] + + [:& scroll-bars/viewport-scrollbars + {:objects base-objects + :zoom zoom + :vbox vbox + :viewport-ref viewport-ref}]) (when show-selection-handlers? [:& selection/selection-handlers diff --git a/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs b/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs new file mode 100644 index 0000000000..d4bff28492 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs @@ -0,0 +1,212 @@ +;; 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) UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.scroll-bars + (:require + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.rect :as gpr] + [app.common.pages.helpers :as cph] + [app.main.data.workspace :as dw] + [app.main.store :as st] + [app.main.ui.workspace.viewport.utils :as utils] + [app.util.dom :as dom] + [rumext.alpha :as mf])) + +(mf/defc viewport-scrollbars + {::mf/wrap [mf/memo]} + [{:keys [objects viewport-ref zoom vbox]}] + + (let [v-scrolling? (mf/use-state false) + h-scrolling? (mf/use-state false) + start-ref (mf/use-ref nil) + v-scrollbar-y-ref (mf/use-ref nil) + h-scrollbar-x-ref (mf/use-ref nil) + v-scrollbar-y-stored (mf/ref-val v-scrollbar-y-ref) + h-scrollbar-x-stored (mf/ref-val h-scrollbar-x-ref) + v-scrollbar-y-padding-ref (mf/use-ref nil) + h-scrollbar-x-padding-ref (mf/use-ref nil) + scrollbar-height-ref (mf/use-ref nil) + scrollbar-width-ref (mf/use-ref nil) + scrollbar-height-stored (mf/ref-val scrollbar-height-ref) + scrollbar-width-stored (mf/ref-val scrollbar-width-ref) + height-factor-ref (mf/use-ref nil) + width-factor-ref (mf/use-ref nil) + vbox-y-ref (mf/use-ref nil) + vbox-x-ref (mf/use-ref nil) + + vbox-x (:x vbox) + vbox-y (:y vbox) + + base-objects-rect + (mf/use-memo + (mf/deps objects) + (fn [] + (let [root-shapes (-> objects cph/get-top-frame :shapes) + shapes (->> root-shapes (mapv #(get objects %)))] + (gsh/selection-rect shapes)))) + + inv-zoom (/ 1 zoom) + vbox-height (- (:height vbox) (* inv-zoom 44)) + vbox-width (- (:width vbox) (* inv-zoom 34)) + + ;; top space hidden because of the scroll + top-offset (-> (- vbox-y (:y base-objects-rect)) + (max 0) + (* vbox-height) + (/ (:height base-objects-rect))) + ;; left space hidden because of the scroll + left-offset (-> (- vbox-x (:x base-objects-rect)) + (max 0) + (* vbox-width) + (/ (:width base-objects-rect))) + + ;; bottom space hidden because of the scroll + bottom-offset (-> (- (:y2 base-objects-rect) (+ vbox-y vbox-height)) + (max 0) + (* vbox-height) + (/ (:height base-objects-rect))) + + ;; right space hidden because of the scroll + right-offset (-> (- (:x2 base-objects-rect) (+ vbox-x vbox-width)) + (max 0) + (* vbox-width) + (/ (:width base-objects-rect))) + + show-v-scroll? (or @v-scrolling? (> top-offset 0) (> bottom-offset 0)) + show-h-scroll? (or @h-scrolling? (> left-offset 0) (> right-offset 0)) + + v-scrollbar-x (+ vbox-x (:width vbox) (* inv-zoom -32)) + v-scrollbar-y (+ vbox-y top-offset) + + h-scrollbar-x (+ vbox-x left-offset) + h-scrollbar-y (+ vbox-y (:height vbox) (* inv-zoom -40)) + + scrollbar-height (-> (- (+ vbox-y vbox-height) bottom-offset v-scrollbar-y)) + scrollbar-height (-> (cond + @v-scrolling? scrollbar-height-stored + :else scrollbar-height) + (max (* inv-zoom 100))) + + scrollbar-width (-> (- (+ vbox-x vbox-width) right-offset h-scrollbar-x)) + scrollbar-width (-> (cond + @h-scrolling? scrollbar-width-stored + :else scrollbar-width) + (max (* inv-zoom 100))) + + v-scrollbar-y (-> (cond + @v-scrolling? (- v-scrollbar-y-stored (- (- vbox-y (mf/ref-val vbox-y-ref)))) + :else v-scrollbar-y) + (max (+ vbox-y (* inv-zoom 26)))) + + v-scrollbar-y (if (> (+ v-scrollbar-y scrollbar-height) (+ vbox-y vbox-height)) ;; the scroll bar is stick to the bottom + (-> (+ vbox-y vbox-height) + (- scrollbar-height)) + v-scrollbar-y) + + h-scrollbar-x (-> (cond + @h-scrolling? (- h-scrollbar-x-stored (- (- vbox-x (mf/ref-val vbox-x-ref)))) + :else h-scrollbar-x) + (max (+ vbox-x (* inv-zoom 26)))) + + h-scrollbar-x (if (> (+ h-scrollbar-x scrollbar-width) (+ vbox-x vbox-width)) ;; the scroll bar is stick to the right + (-> (+ vbox-x vbox-width) + (- scrollbar-width)) + h-scrollbar-x) + + on-mouse-move + (mf/use-callback + (mf/deps zoom v-scrolling?) + (fn [event axis] + (when-let [_ (or @v-scrolling? @h-scrolling?)] + (let [viewport (mf/ref-val viewport-ref) + start-pt (mf/ref-val start-ref) + current-pt (dom/get-client-position event) + current-pt-viewport (utils/translate-point-to-viewport-raw viewport zoom current-pt) + y-delta (/ (* (mf/ref-val height-factor-ref) (- (:y current-pt) (:y start-pt))) zoom) + x-delta (/ (* (mf/ref-val width-factor-ref) (- (:x current-pt) (:x start-pt))) zoom) + new-v-scrollbar-y (-> current-pt-viewport + (:y) + (+ (mf/ref-val v-scrollbar-y-padding-ref))) + new-h-scrollbar-x (-> current-pt-viewport + (:x) + (+ (mf/ref-val h-scrollbar-x-padding-ref))) + viewport-update (-> {} + (cond-> (= axis :y) (assoc :y #(+ % y-delta))) + (cond-> (= axis :x) (assoc :x #(+ % x-delta))))] + (mf/set-ref-val! vbox-y-ref vbox-y) + (mf/set-ref-val! vbox-x-ref vbox-x) + (st/emit! (dw/update-viewport-position viewport-update)) + (mf/set-ref-val! v-scrollbar-y-ref new-v-scrollbar-y) + (mf/set-ref-val! h-scrollbar-x-ref new-h-scrollbar-x) + (mf/set-ref-val! start-ref current-pt))))) + + on-mouse-down + (mf/use-callback + (mf/deps v-scrollbar-y scrollbar-height) + (fn [event axis] + (let [viewport (mf/ref-val viewport-ref) + start-pt (dom/get-client-position event) + new-v-scrollbar-y (-> (utils/translate-point-to-viewport-raw viewport zoom start-pt) :y) + new-h-scrollbar-x (-> (utils/translate-point-to-viewport-raw viewport zoom start-pt) :x) + v-scrollbar-y-padding (- v-scrollbar-y new-v-scrollbar-y) + h-scrollbar-x-padding (- h-scrollbar-x new-h-scrollbar-x) + vbox-rect {:x vbox-x + :y vbox-y + :x1 vbox-x + :y1 vbox-y + :x2 (+ vbox-x (:width vbox)) + :y2 (+ vbox-y (:height vbox)) + :width (:width vbox) + :height (:height vbox)} + containing-rect (gpr/join-selrects [base-objects-rect vbox-rect]) + height-factor (/ (:height containing-rect) vbox-height) + width-factor (/ (:width containing-rect) vbox-width)] + (mf/set-ref-val! start-ref start-pt) + (mf/set-ref-val! v-scrollbar-y-padding-ref v-scrollbar-y-padding) + (mf/set-ref-val! h-scrollbar-x-padding v-scrollbar-y-padding) + (mf/set-ref-val! v-scrollbar-y-ref (+ new-v-scrollbar-y v-scrollbar-y-padding)) + (mf/set-ref-val! h-scrollbar-x-ref (+ new-h-scrollbar-x h-scrollbar-x-padding)) + (mf/set-ref-val! vbox-y-ref vbox-y) + (mf/set-ref-val! vbox-x-ref vbox-x) + (mf/set-ref-val! scrollbar-height-ref scrollbar-height) + (mf/set-ref-val! scrollbar-width-ref scrollbar-width) + (mf/set-ref-val! height-factor-ref height-factor) + (mf/set-ref-val! width-factor-ref width-factor) + (reset! v-scrolling? (= axis :y)) + (reset! h-scrolling? (= axis :x))))) + + on-mouse-up + (mf/use-callback + (mf/deps) + (fn [_] + (reset! v-scrolling? false) + (reset! h-scrolling? false)))] + + [* + (when show-v-scroll? + [:g.v-scroll + [:rect {:on-mouse-move #(on-mouse-move % :y) + :on-mouse-down #(on-mouse-down % :y) + :on-mouse-up #(on-mouse-up % :y) + :width (* inv-zoom 7) + :rx (* inv-zoom 3) + :ry (* inv-zoom 3) + :height scrollbar-height + :fill-opacity 0.4 + :x v-scrollbar-x + :y v-scrollbar-y}]]) + (when show-h-scroll? + [:g.h-scroll + [:rect {:on-mouse-move #(on-mouse-move % :x) + :on-mouse-down #(on-mouse-down % :x) + :on-mouse-up #(on-mouse-up % :x) + :width scrollbar-width + :rx (* inv-zoom 3) + :ry (* inv-zoom 3) + :height (* inv-zoom 7) + :fill-opacity 0.4 + :x h-scrollbar-x + :y h-scrollbar-y}]])])) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 088d175494..b7c6ff280d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -153,7 +153,7 @@ (:width vbox 0) (:height vbox 0)])) -(defn translate-point-to-viewport [viewport zoom pt] +(defn translate-point-to-viewport-raw [viewport zoom pt] (let [vbox (.. ^js viewport -viewBox -baseVal) brect (dom/get-bounding-rect viewport) brect (gpt/point (d/parse-integer (:left brect)) @@ -162,8 +162,11 @@ zoom (gpt/point zoom)] (-> (gpt/subtract pt brect) (gpt/divide zoom) - (gpt/add box) - (gpt/round 0)))) + (gpt/add box)))) + +(defn translate-point-to-viewport [viewport zoom pt] + (-> (translate-point-to-viewport-raw viewport zoom pt) + (gpt/round 0))) (defn get-cursor [cursor] (case cursor