excalidraw/src/scene/scrollbars.ts

124 lines
3.9 KiB
TypeScript
Raw Normal View History

import { ExcalidrawElement } from "../element/types";
import { getCommonBounds } from "../element";
import { FlooredNumber } from "../types";
import { ScrollBars } from "./types";
import { getGlobalCSSVariable } from "../utils";
import { getLanguage } from "../i18n";
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")),
};
const isRTL = getLanguage().rtl;
// 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: isRTL
? Math.max(safeArea.left, SCROLLBAR_MARGIN)
: 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,
2020-01-24 12:04:54 +02:00
scrollBars.vertical,
].map((scrollBar) => {
return (
scrollBar &&
scrollBar.x <= x &&
x <= scrollBar.x + scrollBar.width &&
scrollBar.y <= y &&
y <= scrollBar.y + scrollBar.height
);
});
return {
isOverHorizontalScrollBar,
2020-01-24 12:04:54 +02:00
isOverVerticalScrollBar,
};
}