From 51e19b977eceb8336156e510a5ddde8cef199bb6 Mon Sep 17 00:00:00 2001 From: hazam Date: Sat, 4 Jan 2020 01:27:25 +0500 Subject: [PATCH 1/4] Scroll with mouse wheel --- src/index.tsx | 88 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 9837a1e3..cf6bd680 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -142,22 +142,32 @@ function newElement( strokeColor: strokeColor, backgroundColor: backgroundColor, seed: Math.floor(Math.random() * 2 ** 31), - draw(rc: RoughCanvas, context: CanvasRenderingContext2D) {} + draw( + rc: RoughCanvas, + context: CanvasRenderingContext2D, + sceneState: SceneState + ) {} }; return element; } +type SceneState = { + scrollX: number; + scrollY: number; + // null indicates transparent bg + viewBackgroundColor: string | null; +}; + function renderScene( rc: RoughCanvas, context: CanvasRenderingContext2D, - // null indicates transparent bg - viewBackgroundColor: string | null + sceneState: SceneState ) { if (!context) return; const fillStyle = context.fillStyle; - if (typeof viewBackgroundColor === "string") { - context.fillStyle = viewBackgroundColor; + if (typeof sceneState.viewBackgroundColor === "string") { + context.fillStyle = sceneState.viewBackgroundColor; context.fillRect(-0.5, -0.5, canvas.width, canvas.height); } else { context.clearRect(-0.5, -0.5, canvas.width, canvas.height); @@ -165,7 +175,7 @@ function renderScene( context.fillStyle = fillStyle; elements.forEach(element => { - element.draw(rc, context); + element.draw(rc, context, sceneState); if (element.isSelected) { const margin = 4; @@ -176,8 +186,8 @@ function renderScene( const lineDash = context.getLineDash(); context.setLineDash([8, 4]); context.strokeRect( - elementX1 - margin, - elementY1 - margin, + elementX1 - margin + sceneState.scrollX, + elementY1 - margin + sceneState.scrollY, elementX2 - elementX1 + margin * 2, elementY2 - elementY1 + margin * 2 ); @@ -233,7 +243,11 @@ function exportAsPNG({ // if we're exporting without bg, we need to rerender the scene without it // (it's reset again, below) if (!exportBackground) { - renderScene(rc, context, null); + renderScene(rc, context, { + viewBackgroundColor: null, + scrollX: 0, + scrollY: 0 + }); } // copy our original canvas onto the temp canvas @@ -259,7 +273,7 @@ function exportAsPNG({ // reset transparent bg back to original if (!exportBackground) { - renderScene(rc, context, viewBackgroundColor); + renderScene(rc, context, { viewBackgroundColor, scrollX: 0, scrollY: 0 }); } // create a temporary elem which we'll use to download the image @@ -316,10 +330,15 @@ function getArrowPoints(element: ExcalidrawElement) { function generateDraw(element: ExcalidrawElement) { if (element.type === "selection") { - element.draw = (rc, context) => { + element.draw = (rc, context, { scrollX, scrollY }) => { const fillStyle = context.fillStyle; context.fillStyle = "rgba(0, 0, 255, 0.10)"; - context.fillRect(element.x, element.y, element.width, element.height); + context.fillRect( + element.x + scrollX, + element.y + scrollY, + element.width, + element.height + ); context.fillStyle = fillStyle; }; } else if (element.type === "rectangle") { @@ -329,11 +348,10 @@ function generateDraw(element: ExcalidrawElement) { fill: element.backgroundColor }); }); - - element.draw = (rc, context) => { - context.translate(element.x, element.y); + element.draw = (rc, context, { scrollX, scrollY }) => { + context.translate(element.x + scrollX, element.y + scrollY); rc.draw(shape); - context.translate(-element.x, -element.y); + context.translate(-element.x - scrollX, -element.y - scrollY); }; } else if (element.type === "ellipse") { const shape = withCustomMathRandom(element.seed, () => @@ -345,10 +363,10 @@ function generateDraw(element: ExcalidrawElement) { { stroke: element.strokeColor, fill: element.backgroundColor } ) ); - element.draw = (rc, context) => { - context.translate(element.x, element.y); + element.draw = (rc, contex, { scrollX, scrollY }) => { + context.translate(element.x + scrollX, element.y + scrollY); rc.draw(shape); - context.translate(-element.x, -element.y); + context.translate(-element.x - scrollX, -element.y - scrollY); }; } else if (element.type === "arrow") { const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element); @@ -361,22 +379,22 @@ function generateDraw(element: ExcalidrawElement) { generator.line(x4, y4, x2, y2, { stroke: element.strokeColor }) ]); - element.draw = (rc, context) => { - context.translate(element.x, element.y); + element.draw = (rc, context, { scrollX, scrollY }) => { + context.translate(element.x + scrollX, element.y + scrollY); shapes.forEach(shape => rc.draw(shape)); - context.translate(-element.x, -element.y); + context.translate(-element.x - scrollX, -element.y - scrollY); }; return; } else if (isTextElement(element)) { - element.draw = (rc, context) => { + element.draw = (rc, context, { scrollX, scrollY }) => { const font = context.font; context.font = element.font; const fillStyle = context.fillStyle; context.fillStyle = element.strokeColor; context.fillText( element.text, - element.x, - element.y + element.actualBoundingBoxAscent + element.x + scrollX, + element.y + element.actualBoundingBoxAscent + scrollY ); context.fillStyle = fillStyle; context.font = font; @@ -467,6 +485,8 @@ type AppState = { currentItemStrokeColor: string; currentItemBackgroundColor: string; viewBackgroundColor: string; + scrollX: number; + scrollY: number; }; const KEYS = { @@ -513,7 +533,9 @@ class App extends React.Component<{}, AppState> { exportPadding: 10, currentItemStrokeColor: "#000000", currentItemBackgroundColor: "#ffffff", - viewBackgroundColor: "#ffffff" + viewBackgroundColor: "#ffffff", + scrollX: 0, + scrollY: 0 }; private onKeyDown = (event: KeyboardEvent) => { @@ -630,6 +652,14 @@ class App extends React.Component<{}, AppState> { id="canvas" width={window.innerWidth} height={window.innerHeight - 200} + onWheel={e => { + e.preventDefault(); + const { deltaX, deltaY } = e; + this.setState(state => ({ + scrollX: state.scrollX - deltaX, + scrollY: state.scrollY - deltaY + })); + }} onMouseDown={e => { const x = e.clientX - (e.target as HTMLElement).offsetLeft; const y = e.clientY - (e.target as HTMLElement).offsetTop; @@ -871,7 +901,11 @@ class App extends React.Component<{}, AppState> { } componentDidUpdate() { - renderScene(rc, context, this.state.viewBackgroundColor); + renderScene(rc, context, { + scrollX: this.state.scrollX, + scrollY: this.state.scrollY, + viewBackgroundColor: this.state.viewBackgroundColor + }); save(this.state); } } From 2a0eacbeca48832ba1f54e454e30ac4ddb301466 Mon Sep 17 00:00:00 2001 From: hazam Date: Sat, 4 Jan 2020 02:37:15 +0500 Subject: [PATCH 2/4] Fixed selection and added scrollbars --- src/index.tsx | 70 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index cf6bd680..16a7085a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -158,6 +158,10 @@ type SceneState = { viewBackgroundColor: string | null; }; +const SCROLLBAR_WIDTH = 6; +const SCROLLBAR_MARGIN = 4; +const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; + function renderScene( rc: RoughCanvas, context: CanvasRenderingContext2D, @@ -165,6 +169,9 @@ function renderScene( ) { if (!context) return; + const canvasWidth = context.canvas.width; + const canvasHeight = context.canvas.height; + const fillStyle = context.fillStyle; if (typeof sceneState.viewBackgroundColor === "string") { context.fillStyle = sceneState.viewBackgroundColor; @@ -194,6 +201,43 @@ function renderScene( context.setLineDash(lineDash); } }); + + 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)); + }); + + // horizontal scrollbar + const sceneWidth = canvasWidth + Math.abs(sceneState.scrollX); + const scrollBarWidth = (canvasWidth * canvasWidth) / sceneWidth; + const scrollBarX = sceneState.scrollX > 0 ? 0 : canvasWidth - scrollBarWidth; + context.fillStyle = SCROLLBAR_COLOR; + context.fillRect( + scrollBarX + SCROLLBAR_MARGIN, + canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, + scrollBarWidth - SCROLLBAR_MARGIN * 2, + SCROLLBAR_WIDTH + ); + + // vertical scrollbar + const sceneHeight = canvasHeight + Math.abs(sceneState.scrollY); + const scrollBarHeight = (canvasHeight * canvasHeight) / sceneHeight; + const scrollBarY = + sceneState.scrollY > 0 ? 0 : canvasHeight - scrollBarHeight; + context.fillStyle = SCROLLBAR_COLOR; + context.fillRect( + canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, + scrollBarY + SCROLLBAR_MARGIN, + SCROLLBAR_WIDTH, + scrollBarHeight - SCROLLBAR_WIDTH * 2 + ); } function exportAsPNG({ @@ -661,8 +705,14 @@ class App extends React.Component<{}, AppState> { })); }} onMouseDown={e => { - const x = e.clientX - (e.target as HTMLElement).offsetLeft; - const y = e.clientY - (e.target as HTMLElement).offsetTop; + const x = + e.clientX - + (e.target as HTMLElement).offsetLeft - + this.state.scrollX; + const y = + e.clientY - + (e.target as HTMLElement).offsetTop - + this.state.scrollY; const element = newElement( this.state.elementType, x, @@ -750,8 +800,8 @@ class App extends React.Component<{}, AppState> { if (isDraggingElements) { const selectedElements = elements.filter(el => el.isSelected); if (selectedElements.length) { - const x = e.clientX - target.offsetLeft; - const y = e.clientY - target.offsetTop; + const x = e.clientX - target.offsetLeft - this.state.scrollX; + const y = e.clientY - target.offsetTop - this.state.scrollY; selectedElements.forEach(element => { element.x += x - lastX; element.y += y - lastY; @@ -767,8 +817,16 @@ class App extends React.Component<{}, AppState> { // otherwise we would read a stale one! const draggingElement = this.state.draggingElement; if (!draggingElement) return; - let width = e.clientX - target.offsetLeft - draggingElement.x; - let height = e.clientY - target.offsetTop - draggingElement.y; + let width = + e.clientX - + target.offsetLeft - + draggingElement.x - + this.state.scrollX; + let height = + e.clientY - + target.offsetTop - + draggingElement.y - + this.state.scrollY; draggingElement.width = width; // Make a perfect square or circle when shift is enabled draggingElement.height = e.shiftKey ? width : height; From 1b93888da528b1ac66ab362f2a04da4723a75e99 Mon Sep 17 00:00:00 2001 From: hazam Date: Sat, 4 Jan 2020 02:52:47 +0500 Subject: [PATCH 3/4] Restore fill style --- src/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 16a7085a..6266e121 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -215,10 +215,10 @@ function renderScene( }); // horizontal scrollbar + context.fillStyle = SCROLLBAR_COLOR; const sceneWidth = canvasWidth + Math.abs(sceneState.scrollX); const scrollBarWidth = (canvasWidth * canvasWidth) / sceneWidth; const scrollBarX = sceneState.scrollX > 0 ? 0 : canvasWidth - scrollBarWidth; - context.fillStyle = SCROLLBAR_COLOR; context.fillRect( scrollBarX + SCROLLBAR_MARGIN, canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, @@ -231,13 +231,13 @@ function renderScene( const scrollBarHeight = (canvasHeight * canvasHeight) / sceneHeight; const scrollBarY = sceneState.scrollY > 0 ? 0 : canvasHeight - scrollBarHeight; - context.fillStyle = SCROLLBAR_COLOR; context.fillRect( canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, scrollBarY + SCROLLBAR_MARGIN, SCROLLBAR_WIDTH, scrollBarHeight - SCROLLBAR_WIDTH * 2 ); + context.fillStyle = fillStyle; } function exportAsPNG({ @@ -695,7 +695,7 @@ class App extends React.Component<{}, AppState> { { e.preventDefault(); const { deltaX, deltaY } = e; From 9d65b1cbc108b0b81313009f437a5b5f2d81ab1f Mon Sep 17 00:00:00 2001 From: hazam Date: Sat, 4 Jan 2020 03:03:08 +0500 Subject: [PATCH 4/4] Code cleanup --- src/index.tsx | 72 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 6266e121..af62a54f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -162,6 +162,40 @@ const SCROLLBAR_WIDTH = 6; const SCROLLBAR_MARGIN = 4; const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; +function getScrollbars( + canvasWidth: number, + canvasHeight: number, + scrollX: number, + scrollY: number +) { + // 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 + }; + + // 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 + }; + + return { + horizontal: horizontalScrollBar, + vertical: verticalScrollBar + }; +} + function renderScene( rc: RoughCanvas, context: CanvasRenderingContext2D, @@ -169,9 +203,6 @@ function renderScene( ) { if (!context) return; - const canvasWidth = context.canvas.width; - const canvasHeight = context.canvas.height; - const fillStyle = context.fillStyle; if (typeof sceneState.viewBackgroundColor === "string") { context.fillStyle = sceneState.viewBackgroundColor; @@ -214,28 +245,25 @@ function renderScene( maxY = Math.max(maxY, getElementAbsoluteY2(element)); }); - // horizontal scrollbar - context.fillStyle = SCROLLBAR_COLOR; - const sceneWidth = canvasWidth + Math.abs(sceneState.scrollX); - const scrollBarWidth = (canvasWidth * canvasWidth) / sceneWidth; - const scrollBarX = sceneState.scrollX > 0 ? 0 : canvasWidth - scrollBarWidth; - context.fillRect( - scrollBarX + SCROLLBAR_MARGIN, - canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, - scrollBarWidth - SCROLLBAR_MARGIN * 2, - SCROLLBAR_WIDTH + const scrollBars = getScrollbars( + context.canvas.width, + context.canvas.height, + sceneState.scrollX, + sceneState.scrollY ); - // vertical scrollbar - const sceneHeight = canvasHeight + Math.abs(sceneState.scrollY); - const scrollBarHeight = (canvasHeight * canvasHeight) / sceneHeight; - const scrollBarY = - sceneState.scrollY > 0 ? 0 : canvasHeight - scrollBarHeight; + context.fillStyle = SCROLLBAR_COLOR; context.fillRect( - canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, - scrollBarY + SCROLLBAR_MARGIN, - SCROLLBAR_WIDTH, - scrollBarHeight - SCROLLBAR_WIDTH * 2 + 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.fillStyle = fillStyle; }