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}