From 8b7dd1d8622c7b45ad73dac4dee4d72b927f75e7 Mon Sep 17 00:00:00 2001 From: Sean Parsons <217400+seanparsons@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:09:49 +0100 Subject: [PATCH] fix(canvas) Correctly position grid resize controls. (#6044) - Create a common component of `GridResizing`. - Use the new `GridResizing` component in place of the embedded column and row handling. --- .../canvas/controls/grid-controls.tsx | 294 ++++++++++-------- .../controls/select-mode/controls-common.tsx | 4 + 2 files changed, 163 insertions(+), 135 deletions(-) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index c9ac31f2342b..39f2e057f812 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -12,7 +12,7 @@ import { isGridAutoOrTemplateDimensions, type GridAutoOrTemplateBase, } from '../../../core/shared/element-template' -import type { CanvasPoint, CanvasRectangle } from '../../../core/shared/math-utils' +import type { CanvasPoint, CanvasRectangle, CanvasVector } from '../../../core/shared/math-utils' import { canvasPoint, distance, @@ -47,6 +47,8 @@ import { windowToCanvasCoordinates } from '../dom-lookup' import { CanvasOffsetWrapper } from './canvas-offset-wrapper' import { useColorTheme } from '../../../uuiui' import { gridCellTargetId } from '../canvas-strategies/strategies/grid-helpers' +import type { EditorDispatch } from '../../../components/editor/action-types' +import { CanvasLabel } from './select-mode/controls-common' export const GridCellTestId = (elementPath: ElementPath) => `grid-cell-${EP.toString(elementPath)}` @@ -107,6 +109,144 @@ function getLabelForAxis( const SHADOW_SNAP_ANIMATION = 'shadow-snap' +const GridResizingContainerSize = 100 + +export interface GridResizingControlProps { + dimension: GridCSSNumber + dimensionIndex: number + axis: 'row' | 'column' + containingFrame: CanvasRectangle + workingPrefix: number + fromPropsAxisValues: GridAutoOrTemplateBase | null +} + +export const GridResizingControl = React.memo((props: GridResizingControlProps) => { + const canvasOffset = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.roundedCanvasOffset, + 'GridResizingControl canvasOffset', + ) + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridResizingControl scale', + ) + const dispatch = useDispatch() + const colorTheme = useColorTheme() + + const mouseDownHandler = React.useCallback( + (event: React.MouseEvent): void => { + const start = windowToCanvasCoordinates( + scale, + canvasOffset, + windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), + ) + + dispatch([ + CanvasActions.createInteractionSession( + createInteractionViaMouse( + start.canvasPositionRounded, + Modifier.modifiersForEvent(event), + gridAxisHandle(props.axis, props.dimensionIndex), + 'zero-drag-not-permitted', + ), + ), + ]) + event.stopPropagation() + event.preventDefault() + }, + [canvasOffset, dispatch, props.axis, props.dimensionIndex, scale], + ) + + const labelId = `grid-${props.axis}-handle-${props.dimensionIndex}` + const containerId = `${labelId}-container` + + return ( +
+ +
+ ) +}) +GridResizingControl.displayName = 'GridResizingControl' + +export interface GridResizingProps { + axisValues: GridAutoOrTemplateBase | null + fromPropsAxisValues: GridAutoOrTemplateBase | null + containingFrame: CanvasRectangle + axis: 'row' | 'column' + gap: number | null +} + +export const GridResizing = React.memo((props: GridResizingProps) => { + if (props.axisValues == null) { + return null + } else { + switch (props.axisValues.type) { + case 'DIMENSIONS': + let workingPrefix: number = + props.axis === 'column' ? props.containingFrame.x : props.containingFrame.y + return ( + <> + {props.axisValues.dimensions.flatMap((dimension, dimensionIndex) => { + // Assumes pixels currently. + workingPrefix += dimension.value + if (dimensionIndex === 0) { + // Shift by half the gap initially... + workingPrefix += (props.gap ?? 0) / 2 + } else { + // ...Then by the full gap, as it would be half from the prior entry + // and half from the current one. + workingPrefix += props.gap ?? 0 + } + + return ( + + ) + })} + + ) + case 'FALLBACK': + return null + default: + assertNever(props.axisValues) + return null + } + } +}) +GridResizing.displayName = 'GridResizing' + export const GridControls = controlForStrategyMemoized(() => { const dispatch = useDispatch() const controls = useAnimationControls() @@ -575,142 +715,26 @@ export const GridControls = controlForStrategyMemoized(() => { /> ) : null} {grids.flatMap((grid) => { - if (grid.gridTemplateColumns == null) { - return [] - } else { - switch (grid.gridTemplateColumns.type) { - case 'DIMENSIONS': - let workingPrefix: number = grid.frame.x - return grid.gridTemplateColumns.dimensions.flatMap((dimension, dimensionIndex) => { - // Assumes pixels currently. - workingPrefix += dimension.value - if (dimensionIndex !== 0) { - workingPrefix += grid.gap ?? 0 - } - function mouseDownHandler(event: React.MouseEvent): void { - const start = windowToCanvasCoordinates( - scaleRef.current, - canvasOffsetRef.current, - windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), - ) - - dispatch([ - CanvasActions.createInteractionSession( - createInteractionViaMouse( - start.canvasPositionRounded, - Modifier.modifiersForEvent(event), - gridAxisHandle('column', dimensionIndex), - 'zero-drag-not-permitted', - ), - ), - ]) - event.stopPropagation() - event.preventDefault() - } - - const id = `grid-column-handle-${dimensionIndex}` - - return ( -
- {getLabelForAxis( - dimension, - dimensionIndex, - grid.gridTemplateColumnsFromProps, - )} -
- ) - }) - case 'FALLBACK': - return [] - default: - assertNever(grid.gridTemplateColumns) - return [] - } - } + return ( + + ) })} {grids.flatMap((grid) => { - if (grid.gridTemplateRows == null) { - return [] - } else { - switch (grid.gridTemplateRows.type) { - case 'DIMENSIONS': - let workingPrefix: number = grid.frame.y - return grid.gridTemplateRows.dimensions.flatMap((dimension, dimensionIndex) => { - // Assumes pixels currently. - workingPrefix += dimension.value - if (dimensionIndex !== 0) { - workingPrefix += grid.gap ?? 0 - } - function mouseDownHandler(event: React.MouseEvent): void { - const start = windowToCanvasCoordinates( - scaleRef.current, - canvasOffsetRef.current, - windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), - ) - - dispatch([ - CanvasActions.createInteractionSession( - createInteractionViaMouse( - start.canvasPositionRounded, - Modifier.modifiersForEvent(event), - gridAxisHandle('row', dimensionIndex), - 'zero-drag-not-permitted', - ), - ), - ]) - event.stopPropagation() - event.preventDefault() - } - - const id = `grid-row-handle-${dimensionIndex}` - - return ( -
- {getLabelForAxis(dimension, dimensionIndex, grid.gridTemplateRowsFromProps)} -
- ) - }) - case 'FALLBACK': - return [] - default: - assertNever(grid.gridTemplateRows) - return [] - } - } + return ( + + ) })} diff --git a/editor/src/components/canvas/controls/select-mode/controls-common.tsx b/editor/src/components/canvas/controls/select-mode/controls-common.tsx index 56fb2a38a81e..d8842aefb49d 100644 --- a/editor/src/components/canvas/controls/select-mode/controls-common.tsx +++ b/editor/src/components/canvas/controls/select-mode/controls-common.tsx @@ -103,6 +103,8 @@ interface CanvasLabelProps { color: string textColor: string value: string | number + onMouseDown?: React.MouseEventHandler + testId?: string } export const CanvasLabel = React.memo((props: CanvasLabelProps): JSX.Element => { @@ -113,6 +115,7 @@ export const CanvasLabel = React.memo((props: CanvasLabelProps): JSX.Element => const borderRadius = BorderRadius / scale return (
borderRadius: borderRadius, height: ExplicitHeightHacked / scale, }} + onMouseDown={props.onMouseDown} > {value}