diff --git a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts index cc741fbd5515..f1f7627bcb37 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts @@ -31,7 +31,13 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( const selectedElement = selectedElements[0] - if (!MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { + const isGridCell = MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement) + const isGrid = MetadataUtils.isGridLayoutedContainer( + MetadataUtils.findElementByElementPath(canvasState.startingMetadata, selectedElement), + ) + const isGridOrGridCell = isGridCell || isGrid + + if (!isGridOrGridCell) { return null } @@ -65,17 +71,19 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( const control = interactionSession.activeControl const drag = interactionSession.interactionData.drag const dragAmount = control.axis === 'column' ? drag.x : drag.y - const parentPath = EP.parentPath(selectedElement) - const parentSpecialSizeMeasurements = - canvasState.startingMetadata[EP.toString(parentPath)].specialSizeMeasurements + + const gridPath = isGrid ? selectedElement : EP.parentPath(selectedElement) + + const gridSpecialSizeMeasurements = + canvasState.startingMetadata[EP.toString(gridPath)].specialSizeMeasurements const originalValues = control.axis === 'column' - ? parentSpecialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateColumns - : parentSpecialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateRows + ? gridSpecialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateColumns + : gridSpecialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateRows const calculatedValues = control.axis === 'column' - ? parentSpecialSizeMeasurements.containerGridProperties.gridTemplateColumns - : parentSpecialSizeMeasurements.containerGridProperties.gridTemplateRows + ? gridSpecialSizeMeasurements.containerGridProperties.gridTemplateColumns + : gridSpecialSizeMeasurements.containerGridProperties.gridTemplateRows if ( calculatedValues == null || @@ -115,14 +123,14 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( const commands = [ setProperty( 'always', - parentPath, + gridPath, PP.create( 'style', control.axis === 'column' ? 'gridTemplateColumns' : 'gridTemplateRows', ), propertyValueAsString, ), - setElementsToRerenderCommand([parentPath]), + setElementsToRerenderCommand([gridPath]), ] return strategyApplicationResult(commands) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 3a05b4a2b8fd..d44752a6d657 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -1,3 +1,6 @@ +/** @jsxRuntime classic */ +/** @jsx jsx */ +import { jsx } from '@emotion/react' import type { AnimationControls } from 'framer-motion' import { motion, useAnimationControls } from 'framer-motion' import React from 'react' @@ -51,7 +54,7 @@ import { } from '../canvas-strategies/interaction-state' import { windowToCanvasCoordinates } from '../dom-lookup' import { CanvasOffsetWrapper } from './canvas-offset-wrapper' -import { useColorTheme } from '../../../uuiui' +import { useColorTheme, UtopiaStyles } from '../../../uuiui' import { gridCellTargetId } from '../canvas-strategies/strategies/grid-helpers' import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' import type { EdgePosition } from '../canvas-types' @@ -65,6 +68,7 @@ import { import { useCanvasAnimation } from '../ui-jsx-canvas-renderer/animation-context' import { CanvasLabel } from './select-mode/controls-common' import { optionalMap } from '../../../core/shared/optional-utils' +import type { Sides } from 'utopia-api/core' const CELL_ANIMATION_DURATION = 0.15 // seconds @@ -122,16 +126,16 @@ function getLabelForAxis( } const SHADOW_SNAP_ANIMATION = 'shadow-snap' - -const GridResizingContainerSize = 100 +const GRID_RESIZE_HANDLE_CONTAINER_SIZE = 30 // px +const GRID_RESIZE_HANDLE_SIZE = 15 // px export interface GridResizingControlProps { dimension: GridCSSNumber dimensionIndex: number axis: 'row' | 'column' containingFrame: CanvasRectangle - workingPrefix: number fromPropsAxisValues: GridAutoOrTemplateBase | null + padding: number | null } export const GridResizingControl = React.memo((props: GridResizingControlProps) => { @@ -148,13 +152,22 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps) const dispatch = useDispatch() const colorTheme = useColorTheme() + const [resizing, setResizing] = React.useState(false) + const mouseDownHandler = React.useCallback( (event: React.MouseEvent): void => { + function mouseUpHandler() { + setResizing(false) + window.removeEventListener('mouseup', mouseUpHandler) + } + window.addEventListener('mouseup', mouseUpHandler) + const start = windowToCanvasCoordinates( scale, canvasOffset, windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), ) + setResizing(true) dispatch([ CanvasActions.createInteractionSession( @@ -175,34 +188,84 @@ export const GridResizingControl = React.memo((props: GridResizingControlProps) const labelId = `grid-${props.axis}-handle-${props.dimensionIndex}` const containerId = `${labelId}-container` + const shadowSize = React.useMemo(() => { + return props.axis === 'column' + ? props.containingFrame.height + GRID_RESIZE_HANDLE_CONTAINER_SIZE + : props.containingFrame.width + GRID_RESIZE_HANDLE_CONTAINER_SIZE + }, [props.containingFrame, props.axis]) + return (
- + > + {props.axis === 'row' ? '↕' : '↔'} +
+ {when( + resizing, +
+ +
, + )} ) }) @@ -214,50 +277,75 @@ export interface GridResizingProps { containingFrame: CanvasRectangle axis: 'row' | 'column' gap: number | null + padding: Sides | null } export const GridResizing = React.memo((props: GridResizingProps) => { + const canvasScale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'GridResizing canvasScale', + ) 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 - } + } + switch (props.axisValues.type) { + case 'DIMENSIONS': + const size = GRID_RESIZE_HANDLE_CONTAINER_SIZE / canvasScale + return ( +
`${dim.value}${dim.unit}`).join(' ') + : undefined, + gridTemplateRows: + props.axis === 'row' + ? props.axisValues.dimensions.map((dim) => `${dim.value}${dim.unit}`).join(' ') + : undefined, + gap: props.gap ?? 0, + paddingLeft: + props.axis === 'column' && props.padding != null + ? `${props.padding.left}px` + : undefined, + paddingTop: + props.axis === 'row' && props.padding != null ? `${props.padding.top}px` : undefined, + }} + > + {props.axisValues.dimensions.flatMap((dimension, dimensionIndex) => { + return ( + + ) + })} +
+ ) + case 'FALLBACK': + return null + default: + assertNever(props.axisValues) } }) GridResizing.displayName = 'GridResizing' @@ -602,7 +690,7 @@ export const GridControls = controlForStrategyMemoized(() => { > {when( features.Grid.dotgrid, - <> +
{ right: -1, }} /> - , + , )}
) @@ -742,6 +830,7 @@ export const GridControls = controlForStrategyMemoized(() => { containingFrame={grid.frame} axis={'column'} gap={grid.gap} + padding={grid.padding} /> ) })} @@ -754,6 +843,7 @@ export const GridControls = controlForStrategyMemoized(() => { containingFrame={grid.frame} axis={'row'} gap={grid.gap} + padding={grid.padding} /> ) })}