From 807c322556e3ebb0a9ab1cd5b9a2f123c9d217b3 Mon Sep 17 00:00:00 2001 From: Matthias Lehner Date: Mon, 2 Oct 2023 17:48:08 +0200 Subject: [PATCH] Add scale factor buttons to the editor component --- public/styles.css | 31 ++++--- src/main/components/canvas/canvas.tsx | 4 +- src/main/components/canvas/editor.tsx | 93 +++++++++++++------ src/main/components/canvas/zoom-pane.tsx | 31 +++++++ .../components/draggable/draggable-layer.tsx | 1 - src/main/utils/clamp.ts | 9 ++ 6 files changed, 123 insertions(+), 46 deletions(-) create mode 100644 src/main/components/canvas/zoom-pane.tsx create mode 100644 src/main/utils/clamp.ts diff --git a/public/styles.css b/public/styles.css index 32d532a9..5be24bba 100644 --- a/public/styles.css +++ b/public/styles.css @@ -145,8 +145,7 @@ aside.sidebar section>div { justify-content: space-between; } -.switch button { - width: 100%; +button { background: var(--apollon-background); color: var(--apollon-primary-contrast); border: 1px solid var(--apollon-gray); @@ -156,6 +155,24 @@ aside.sidebar section>div { outline: none; } +button:hover { + background-color: var(--apollon-gray); + border-color: var(--apollon-gray-variant); +} + +button:active { + background-color: var(--apollon-gray); + border-color: var(--apollon-gray-variant); +} + +.button-rounded { + border-radius: 0.25rem; +} + +.switch button { + width: 100%; +} + .switch button:first-child { border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; @@ -167,16 +184,6 @@ aside.sidebar section>div { border-bottom-right-radius: 0.25rem; } -.switch button:hover { - background-color: var(--apollon-gray); - border-color: var(--apollon-gray-variant); -} - -.switch button:active { - background-color: var(--apollon-gray); - border-color: var(--apollon-gray-variant); -} - .switch button.selected { background-color: var(--apollon-gray); border-color: var(--apollon-gray-variant); diff --git a/src/main/components/canvas/canvas.tsx b/src/main/components/canvas/canvas.tsx index e70ec211..3b2020d7 100644 --- a/src/main/components/canvas/canvas.tsx +++ b/src/main/components/canvas/canvas.tsx @@ -12,7 +12,6 @@ import { UMLElementState } from '../../services/uml-element/uml-element-types'; import { UMLRelationship } from '../../services/uml-relationship/uml-relationship'; import { EditorRepository } from '../../services/editor/editor-repository'; - type OwnProps = {}; type StateProps = { @@ -29,7 +28,7 @@ const enhance = connect( (state) => ({ diagram: state.diagram, isStatic: state.editor.readonly, - elements: state.elements + elements: state.elements, }), null, null, @@ -37,7 +36,6 @@ const enhance = connect( ); export class CanvasComponent extends Component implements Omit { - layer: RefObject = createRef(); origin = (): Point => { diff --git a/src/main/components/canvas/editor.tsx b/src/main/components/canvas/editor.tsx index 0eafefc8..79c73ec5 100644 --- a/src/main/components/canvas/editor.tsx +++ b/src/main/components/canvas/editor.tsx @@ -5,7 +5,9 @@ import { ModelState } from '../store/model-state'; import isMobile from 'is-mobile'; import { UMLElementRepository } from '../../services/uml-element/uml-element-repository'; import { AsyncDispatch } from '../../utils/actions/actions'; -import {EditorRepository} from '../../services/editor/editor-repository'; +import { EditorRepository } from '../../services/editor/editor-repository'; +import { clamp } from '../../utils/clamp'; +import { ZoomPane } from './zoom-pane'; const minScale: number = 0.5; const maxScale: number = 5.0; @@ -16,8 +18,8 @@ const borderWidth: number = 1; const StyledEditor = styled.div<{ zoomFactor?: number }>` display: block; - width: ${100 / minScale}%; - height: ${100 / minScale}%; + width: 100%; + height: 100%; position: relative; min-height: inherit; max-height: inherit; @@ -40,6 +42,7 @@ const StyledEditor = styled.div<{ zoomFactor?: number }>` linear-gradient(to bottom, ${(props) => props.theme.color.gray} 1px, transparent 1px); background-repeat: repeat; background-attachment: local; + transition: transform 500ms; transform-origin: top left; transform: scale(${(props) => props.zoomFactor ?? 1}); `; @@ -84,18 +87,21 @@ const SCROLL_DISTANCE = 5; class EditorComponent extends Component { state = getInitialState(); editor = createRef(); + zoomContainer = createRef(); componentDidMount() { - - const {zoomFactor = 1} = this.props; - - window.addEventListener('wheel', (event) => { - event.preventDefault(); - - if (event.ctrlKey) { - this.props.changeZoomFactor(this.clamp(zoomFactor - event.deltaY * 0.01, minScale, maxScale)); - } - }); + const { zoomFactor = 1 } = this.props; + + window.addEventListener( + 'wheel', + (event) => { + if (event.ctrlKey) { + event.preventDefault(); + this.props.changeZoomFactor(clamp(zoomFactor - event.deltaY * 0.1, minScale, maxScale)); + } + }, + { passive: false }, + ); window.addEventListener('gesturestart', (event) => { event.preventDefault(); @@ -108,9 +114,28 @@ class EditorComponent extends Component { window.addEventListener('gesturechange', (event) => { event.preventDefault(); - this.props.changeZoomFactor( - this.clamp(this.state.gestureStartZoomFactor * (event as any).scale, minScale, maxScale), - ); + + const { zoomFactor = 1 } = this.props; + + const relativeCursorOffsetX = (event as any).clientX - this.editor.current!.getBoundingClientRect().x; + const relativeCursorOffsetY = (event as any).clientY - this.editor.current!.getBoundingClientRect().y; + + const newZoomFactor = clamp(this.state.gestureStartZoomFactor * (event as any).scale, minScale, maxScale); + + // console.log(this.state.gestureStartZoomFactor * relativeCursorOffsetX); + // console.log(this.state.gestureStartZoomFactor * relativeCursorOffsetY - newZoomFactor * relativeCursorOffsetY); + + // TODO: Evaluate if we need to integrate the zoom Factor here additionally as the relative position changes on zoom + + /* + this.zoomContainer.current?.scrollBy({ + left: newZoomFactor * relativeCursorOffsetX - zoomFactor * relativeCursorOffsetX , + top: newZoomFactor * relativeCursorOffsetY - zoomFactor * relativeCursorOffsetY, + behavior: 'smooth' + }); + */ + + this.props.changeZoomFactor(newZoomFactor); }); window.addEventListener('gestureend', function (event) { @@ -137,26 +162,34 @@ class EditorComponent extends Component { const { moving, connecting, reconnecting, zoomFactor = 1.0, ...props } = this.props; if (this.state.isMobile) { - return ( +
+ this.props.changeZoomFactor(zoomFactor)} + min={minScale} + max={maxScale} + /> +
); } else { return ( -
- -
+
+ + this.props.changeZoomFactor(zoomFactor)} + min={minScale} + max={maxScale} + /> +
); } } - clamp = (value: number, min: number, max: number): number => { - return Math.max(min, Math.min(value, max)); - }; - customScrolling = (event: React.TouchEvent) => { - - const {zoomFactor = 1} = this.props; + const { zoomFactor = 1 } = this.props; if (this.editor.current) { const clientRect = this.editor.current.getBoundingClientRect(); @@ -165,15 +198,15 @@ class EditorComponent extends Component { // scroll when on the edge of the element const scrollHorizontally = - (touch.clientX * zoomFactor) < clientRect.x + SCROLL_BORDER + touch.clientX * zoomFactor < clientRect.x + SCROLL_BORDER ? -SCROLL_DISTANCE - : (touch.clientX * zoomFactor) > clientRect.x + clientRect.width - SCROLL_BORDER + : touch.clientX * zoomFactor > clientRect.x + clientRect.width - SCROLL_BORDER ? SCROLL_DISTANCE : 0; const scrollVertically = - (touch.clientY * zoomFactor) < clientRect.y + SCROLL_BORDER + touch.clientY * zoomFactor < clientRect.y + SCROLL_BORDER ? -SCROLL_DISTANCE - : (touch.clientY * zoomFactor)> clientRect.y + clientRect.height - SCROLL_BORDER + : touch.clientY * zoomFactor > clientRect.y + clientRect.height - SCROLL_BORDER ? SCROLL_DISTANCE : 0; this.editor.current.scrollBy(scrollHorizontally, scrollVertically); diff --git a/src/main/components/canvas/zoom-pane.tsx b/src/main/components/canvas/zoom-pane.tsx new file mode 100644 index 00000000..59d581f5 --- /dev/null +++ b/src/main/components/canvas/zoom-pane.tsx @@ -0,0 +1,31 @@ +import React, { FunctionComponent } from 'react'; +import { clamp } from '../../utils/clamp'; + +type Props = { + min?: number; + max?: number; + step?: number; + value: number; + onChange: (value: number) => void; +}; + +export const ZoomPaneComponent: FunctionComponent = (props) => { + const { min = 0.5, max = 5, step = 0.5, value, onChange } = props; + + return ( +
+ + +
+ ); +}; + +export const ZoomPane = ZoomPaneComponent; diff --git a/src/main/components/draggable/draggable-layer.tsx b/src/main/components/draggable/draggable-layer.tsx index bd6b016c..571ebfd6 100644 --- a/src/main/components/draggable/draggable-layer.tsx +++ b/src/main/components/draggable/draggable-layer.tsx @@ -129,7 +129,6 @@ class DraggableLayerComponent extends Component { + return Math.max(min, Math.min(value, max)); +}; \ No newline at end of file