From 7201198f23a9aeab986a9d65faa047933ffe55e4 Mon Sep 17 00:00:00 2001 From: Timur Khazamov Date: Mon, 6 Jan 2020 02:26:01 +0500 Subject: [PATCH] Better scrollbars (#177) * Better scrollbars * Get rid of all unused options --- src/index.tsx | 104 ++++++++++++++++++++++++++++++++--------------- src/roundRect.ts | 37 +++++++++++++++++ 2 files changed, 108 insertions(+), 33 deletions(-) create mode 100644 src/roundRect.ts diff --git a/src/index.tsx b/src/index.tsx index 7ca767c1..b308c6eb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,6 +5,7 @@ import { RoughCanvas } from "roughjs/bin/canvas"; import { SketchPicker } from "react-color"; import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex"; +import { roundRect } from "./roundRect"; import "./styles.scss"; @@ -255,38 +256,72 @@ type SceneState = { }; const SCROLLBAR_WIDTH = 6; +const SCROLLBAR_MIN_SIZE = 15; const SCROLLBAR_MARGIN = 4; const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; const CANVAS_WINDOW_OFFSET_LEFT = 250; const CANVAS_WINDOW_OFFSET_TOP = 0; -function getScrollbars( +function getScrollBars( canvasWidth: number, canvasHeight: number, scrollX: number, scrollY: number ) { + let minX = Infinity; + let maxX = 0; + let minY = Infinity; + let maxY = 0; + + elements.forEach(element => { + minX = Math.min(minX, getElementAbsoluteX1(element)); + maxX = Math.max(maxX, getElementAbsoluteX2(element)); + minY = Math.min(minY, getElementAbsoluteY1(element)); + maxY = Math.max(maxY, getElementAbsoluteY2(element)); + }); + + minX += scrollX; + maxX += scrollX; + minY += scrollY; + maxY += scrollY; + const leftOverflow = Math.max(-minX, 0); + const rightOverflow = Math.max(-(canvasWidth - maxX), 0); + const topOverflow = Math.max(-minY, 0); + const bottomOverflow = Math.max(-(canvasHeight - maxY), 0); + // horizontal scrollbar - const sceneWidth = canvasWidth + Math.abs(scrollX); - const scrollBarWidth = (canvasWidth * canvasWidth) / sceneWidth; - const scrollBarX = scrollX > 0 ? 0 : canvasWidth - scrollBarWidth; - const horizontalScrollBar = { - x: scrollBarX + SCROLLBAR_MARGIN, - y: canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, - width: scrollBarWidth - SCROLLBAR_MARGIN * 2, - height: SCROLLBAR_WIDTH - }; + let horizontalScrollBar = null; + if (leftOverflow || rightOverflow) { + horizontalScrollBar = { + x: Math.min( + leftOverflow + SCROLLBAR_MARGIN, + canvasWidth - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN + ), + y: canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, + width: Math.max( + canvasWidth - rightOverflow - leftOverflow - SCROLLBAR_MARGIN * 2, + SCROLLBAR_MIN_SIZE + ), + height: SCROLLBAR_WIDTH + }; + } // vertical scrollbar - const sceneHeight = canvasHeight + Math.abs(scrollY); - const scrollBarHeight = (canvasHeight * canvasHeight) / sceneHeight; - const scrollBarY = scrollY > 0 ? 0 : canvasHeight - scrollBarHeight; - const verticalScrollBar = { - x: canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, - y: scrollBarY + SCROLLBAR_MARGIN, - width: SCROLLBAR_WIDTH, - height: scrollBarHeight - SCROLLBAR_WIDTH * 2 - }; + let verticalScrollBar = null; + if (topOverflow || bottomOverflow) { + verticalScrollBar = { + x: canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, + y: Math.min( + topOverflow + SCROLLBAR_MARGIN, + canvasHeight - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN + ), + width: SCROLLBAR_WIDTH, + height: Math.max( + canvasHeight - bottomOverflow - topOverflow - SCROLLBAR_WIDTH * 2, + SCROLLBAR_MIN_SIZE + ) + }; + } return { horizontal: horizontalScrollBar, @@ -302,13 +337,14 @@ function isOverScrollBars( scrollX: number, scrollY: number ) { - const scrollBars = getScrollbars(canvasWidth, canvasHeight, scrollX, scrollY); + const scrollBars = getScrollBars(canvasWidth, canvasHeight, scrollX, scrollY); const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [ scrollBars.horizontal, scrollBars.vertical ].map( scrollBar => + scrollBar && scrollBar.x <= x && x <= scrollBar.x + scrollBar.width && scrollBar.y <= y && @@ -467,26 +503,28 @@ function renderScene( }); if (renderScrollbars) { - const scrollBars = getScrollbars( + const scrollBars = getScrollBars( context.canvas.width / window.devicePixelRatio, context.canvas.height / window.devicePixelRatio, sceneState.scrollX, sceneState.scrollY ); + const strokeStyle = context.strokeStyle; context.fillStyle = SCROLLBAR_COLOR; - context.fillRect( - scrollBars.horizontal.x, - scrollBars.horizontal.y, - scrollBars.horizontal.width, - scrollBars.horizontal.height - ); - context.fillRect( - scrollBars.vertical.x, - scrollBars.vertical.y, - scrollBars.vertical.width, - scrollBars.vertical.height - ); + context.strokeStyle = "rgba(255,255,255,0.8)"; + [scrollBars.horizontal, scrollBars.vertical].forEach(scrollBar => { + if (scrollBar) + roundRect( + context, + scrollBar.x, + scrollBar.y, + scrollBar.width, + scrollBar.height, + SCROLLBAR_WIDTH / 2 + ); + }); + context.strokeStyle = strokeStyle; context.fillStyle = fillStyle; } } diff --git a/src/roundRect.ts b/src/roundRect.ts new file mode 100644 index 00000000..bba0e6de --- /dev/null +++ b/src/roundRect.ts @@ -0,0 +1,37 @@ +/** + * https://stackoverflow.com/a/3368118 + * Draws a rounded rectangle using the current state of the canvas. + * @param {CanvasRenderingContext2D} context + * @param {Number} x The top left x coordinate + * @param {Number} y The top left y coordinate + * @param {Number} width The width of the rectangle + * @param {Number} height The height of the rectangle + * @param {Number} radius The corner radius + */ +export function roundRect( + context: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + radius: number +) { + context.beginPath(); + context.moveTo(x + radius, y); + context.lineTo(x + width - radius, y); + context.quadraticCurveTo(x + width, y, x + width, y + radius); + context.lineTo(x + width, y + height - radius); + context.quadraticCurveTo( + x + width, + y + height, + x + width - radius, + y + height + ); + context.lineTo(x + radius, y + height); + context.quadraticCurveTo(x, y + height, x, y + height - radius); + context.lineTo(x, y + radius); + context.quadraticCurveTo(x, y, x + radius, y); + context.closePath(); + context.fill(); + context.stroke(); +}