import { ExcalidrawElement } from "../element/types"; import { getCommonBounds } from "../element"; import { FlooredNumber } from "../types"; import { ScrollBars } from "./types"; import { getGlobalCSSVariable } from "../utils"; export const SCROLLBAR_MARGIN = 4; export const SCROLLBAR_WIDTH = 6; export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; export function getScrollBars( elements: readonly ExcalidrawElement[], viewportWidth: number, viewportHeight: number, { scrollX, scrollY, zoom, }: { scrollX: FlooredNumber; scrollY: FlooredNumber; zoom: number; }, ): ScrollBars { // This is the bounding box of all the elements const [ elementsMinX, elementsMinY, elementsMaxX, elementsMaxY, ] = getCommonBounds(elements); // Apply zoom const viewportWidthWithZoom = viewportWidth / zoom; const viewportHeightWithZoom = viewportHeight / zoom; const viewportWidthDiff = viewportWidth - viewportWidthWithZoom; const viewportHeightDiff = viewportHeight - viewportHeightWithZoom; const safeArea = { top: parseInt(getGlobalCSSVariable("sat")), bottom: parseInt(getGlobalCSSVariable("sab")), left: parseInt(getGlobalCSSVariable("sal")), right: parseInt(getGlobalCSSVariable("sar")), }; // The viewport is the rectangle currently visible for the user const viewportMinX = -scrollX + viewportWidthDiff / 2 + safeArea.left; const viewportMinY = -scrollY + viewportHeightDiff / 2 + safeArea.top; const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right; const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom; // The scene is the bounding box of both the elements and viewport const sceneMinX = Math.min(elementsMinX, viewportMinX); const sceneMinY = Math.min(elementsMinY, viewportMinY); const sceneMaxX = Math.max(elementsMaxX, viewportMaxX); const sceneMaxY = Math.max(elementsMaxY, viewportMaxY); // The scrollbar represents where the viewport is in relationship to the scene return { horizontal: viewportMinX === sceneMinX && viewportMaxX === sceneMaxX ? null : { x: Math.max(safeArea.left, SCROLLBAR_MARGIN) + ((viewportMinX - sceneMinX) / (sceneMaxX - sceneMinX)) * viewportWidth, y: viewportHeight - SCROLLBAR_WIDTH - Math.max(SCROLLBAR_MARGIN, safeArea.bottom), width: ((viewportMaxX - viewportMinX) / (sceneMaxX - sceneMinX)) * viewportWidth - Math.max(SCROLLBAR_MARGIN * 2, safeArea.left + safeArea.right), height: SCROLLBAR_WIDTH, }, vertical: viewportMinY === sceneMinY && viewportMaxY === sceneMaxY ? null : { x: viewportWidth - SCROLLBAR_WIDTH - Math.max(safeArea.right, SCROLLBAR_MARGIN), y: ((viewportMinY - sceneMinY) / (sceneMaxY - sceneMinY)) * viewportHeight + Math.max(safeArea.top, SCROLLBAR_MARGIN), width: SCROLLBAR_WIDTH, height: ((viewportMaxY - viewportMinY) / (sceneMaxY - sceneMinY)) * viewportHeight - Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom), }, }; } export function isOverScrollBars(scrollBars: ScrollBars, x: number, y: number) { const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [ scrollBars.horizontal, scrollBars.vertical, ].map(scrollBar => { return ( scrollBar && scrollBar.x <= x && x <= scrollBar.x + scrollBar.width && scrollBar.y <= y && y <= scrollBar.y + scrollBar.height ); }); return { isOverHorizontalScrollBar, isOverVerticalScrollBar, }; }