diff --git a/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx index 9c38e0755315..5135bee92379 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx @@ -15,7 +15,7 @@ import * as PP from '../../../../core/shared/property-path' import { styleStringInArray } from '../../../../utils/common-constants' import { trueUpGroupElementChanged } from '../../../editor/store/editor-state' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' -import { isFixedHugFillModeApplied } from '../../../inspector/inspector-common' +import { isFillOrStretchModeApplied } from '../../../inspector/inspector-common' import type { EdgePosition } from '../../canvas-types' import { oppositeEdgePosition } from '../../canvas-types' import { @@ -84,10 +84,7 @@ export function basicResizeStrategy( const elementParentBounds = metadata?.specialSizeMeasurements.immediateParentBounds ?? null const isGridCell = MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement) - if ( - isGridCell && - isFixedHugFillModeApplied(canvasState.startingMetadata, selectedElement, 'fill') - ) { + if (isGridCell && isFillOrStretchModeApplied(canvasState.startingMetadata, selectedElement)) { return null } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx index a5c28b4d6af5..94ec2157177d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx @@ -15,12 +15,13 @@ import { offsetPoint, } from '../../../../core/shared/math-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' -import { GridResizeEdgeTestId } from '../../controls/grid-controls-for-strategies' import { mouseDragFromPointToPoint } from '../../event-helpers.test-utils' import type { EditorRenderResult } from '../../ui-jsx.test-utils' import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' import type { GridResizeEdge } from '../interaction-state' import { gridCellTargetId } from './grid-cell-bounds' +import { ResizePointTestId } from '../../controls/select-mode/absolute-resize-control' +import { gridEdgeToEdgePosition } from '../../controls/grid-controls-for-strategies' async function runCellResizeTest( editor: EditorRenderResult, @@ -30,7 +31,9 @@ async function runCellResizeTest( ) { await selectComponentsForTest(editor, [elementPathToDrag]) - const resizeControl = editor.renderedDOM.getByTestId(GridResizeEdgeTestId(edge)) + const resizeControl = editor.renderedDOM.getByTestId( + ResizePointTestId(gridEdgeToEdgePosition(edge)), + ) const targetGridCell = editor.renderedDOM.getByTestId(dragToCellTestId) await mouseDragFromPointToPoint( @@ -61,7 +64,9 @@ async function runCellResizeTestWithDragVector( ) { await selectComponentsForTest(editor, [elementPathToDrag]) - const resizeControl = editor.renderedDOM.getByTestId(GridResizeEdgeTestId(edge)) + const resizeControl = editor.renderedDOM.getByTestId( + ResizePointTestId(gridEdgeToEdgePosition(edge)), + ) const resizeControlCenter = getRectCenter( localRectangle({ @@ -82,15 +87,6 @@ async function runCellResizeTestWithDragVector( } describe('grid resize element strategy', () => { - it('cannot resize non-filling cells', async () => { - const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') - - await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/grid-child-not-filling')]) - - const resizeControl = editor.renderedDOM.queryByTestId(GridResizeEdgeTestId('column-end')) - expect(resizeControl).toBeNull() - }) - describe('column-end', () => { it('can enlarge element', async () => { const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index 29b20a5ab66e..fbd27c0778ba 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -7,7 +7,7 @@ import { rectangleIntersection, } from '../../../../core/shared/math-utils' import { isCSSKeyword } from '../../../inspector/common/css-utils' -import { isFixedHugFillModeApplied } from '../../../inspector/inspector-common' +import { isFillOrStretchModeApplied } from '../../../inspector/inspector-common' import { controlsForGridPlaceholders, gridEdgeToEdgePosition, @@ -52,10 +52,7 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return null } - const isFillOrStretchContainer = - isFixedHugFillModeApplied(canvasState.startingMetadata, selectedElement, 'fill') || - isFixedHugFillModeApplied(canvasState.startingMetadata, selectedElement, 'stretch') - if (!isFillOrStretchContainer) { + if (!isFillOrStretchModeApplied(canvasState.startingMetadata, selectedElement)) { return null } diff --git a/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx b/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx index 41e56da794a5..c1e6beda11d3 100644 --- a/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx +++ b/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx @@ -20,7 +20,6 @@ import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy import type { GridResizeEdge } from '../canvas-strategies/interaction-state' import type { EdgePosition } from '../canvas-types' import { - CSSCursor, EdgePositionBottom, EdgePositionLeft, EdgePositionRight, @@ -31,6 +30,7 @@ import { GridResizeControlsComponent, GridRowColumnResizingControlsComponent, } from './grid-controls' +import { isEdgePositionOnSide } from '../canvas-utils' export const GridCellTestId = (elementPath: ElementPath) => `grid-cell-${EP.toString(elementPath)}` @@ -167,8 +167,6 @@ export interface GridControlsProps { export const GridControls = controlForStrategyMemoized(GridControlsComponent) -export const GridResizeEdgeTestId = (edge: GridResizeEdge) => `grid-resize-edge-${edge}` - interface GridResizeControlProps { target: ElementPath } @@ -192,6 +190,22 @@ export function gridEdgeToEdgePosition(edge: GridResizeEdge): EdgePosition { } } +export function edgePositionToGridResizeEdge(position: EdgePosition): GridResizeEdge | null { + if (!isEdgePositionOnSide(position)) { + return null + } else if (position.x === 0) { + return 'column-start' + } else if (position.x === 1) { + return 'column-end' + } else if (position.y === 0) { + return 'row-start' + } else if (position.y === 1) { + return 'row-end' + } else { + return null + } +} + export function controlsForGridPlaceholders( gridPath: ElementPath, whenToShow: WhenToShowControl = 'always-visible', diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 1fee7f42f12e..c3f9486c031a 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -36,7 +36,7 @@ import { import { toFirst } from '../../../core/shared/optics/optic-utilities' import type { Optic } from '../../../core/shared/optics/optics' import { optionalMap } from '../../../core/shared/optional-utils' -import { assertNever } from '../../../core/shared/utils' +import { assertNever, NO_OP } from '../../../core/shared/utils' import { Modifier } from '../../../utils/modifiers' import { when } from '../../../utils/react-conditionals' import { useColorTheme, UtopiaStyles } from '../../../uuiui' @@ -50,16 +50,11 @@ import { printGridCSSNumber, } from '../../inspector/common/css-utils' import CanvasActions from '../canvas-actions' -import type { - GridResizeEdge, - GridResizeEdgeProperties, -} from '../canvas-strategies/interaction-state' +import type { GridResizeEdge } from '../canvas-strategies/interaction-state' import { createInteractionViaMouse, gridAxisHandle, gridCellHandle, - gridResizeEdgeProperties, - GridResizeEdges, gridResizeHandle, } from '../canvas-strategies/interaction-state' import type { GridCellCoordinates } from '../canvas-strategies/strategies/grid-cell-bounds' @@ -70,6 +65,7 @@ import { } from '../canvas-strategies/strategies/grid-helpers' import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy' import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' +import type { EdgePosition } from '../canvas-types' import { CSSCursor } from '../canvas-types' import { windowToCanvasCoordinates } from '../dom-lookup' import type { Axis } from '../gap-utils' @@ -77,13 +73,14 @@ import { useCanvasAnimation } from '../ui-jsx-canvas-renderer/animation-context' import { CanvasOffsetWrapper } from './canvas-offset-wrapper' import type { GridControlsProps, GridData } from './grid-controls-for-strategies' import { + edgePositionToGridResizeEdge, getNullableAutoOrTemplateBaseString, GridCellTestId, gridEdgeToEdgePosition, - GridResizeEdgeTestId, useGridData, } from './grid-controls-for-strategies' import { useMaybeHighlightElement } from './select-mode/select-mode-hooks' +import { useResizeEdges } from './select-mode/use-resize-edges' const CELL_ANIMATION_DURATION = 0.15 // seconds @@ -820,8 +817,6 @@ const GridControl = React.memo(({ grid }) => { const countedColumn = Math.floor(cell % grid.columns) + 1 const id = gridCellTargetId(grid.elementPath, countedRow, countedColumn) const borderID = `${id}-border` - const dotgridColor = - activelyDraggingOrResizingCell != null ? colorTheme.blackOpacity35.value : 'transparent' const isActiveCell = countedColumn === currentHoveredCell?.column && countedRow === currentHoveredCell?.row @@ -1455,12 +1450,9 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) const isResizing = bounds != null - const [resizingEdge, setResizingEdge] = React.useState(null) - const onMouseUp = React.useCallback(() => { setBounds(null) setStartingBounds(null) - setResizingEdge(null) }, []) React.useEffect(() => { @@ -1476,7 +1468,6 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) (uid: string, edge: GridResizeEdge) => (event: React.MouseEvent) => { event.stopPropagation() const frame = zeroRectIfNullOrInfinity(element?.globalFrame ?? null) - setResizingEdge(edge) setBounds(frame) setStartingBounds(frame) const start = windowToCanvasCoordinates( @@ -1509,6 +1500,34 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) return scaledFrame.width * scale > 30 && scaledFrame.height > 30 }, [element, scale, isResizing]) + const onEdgeMouseDown = React.useCallback( + (position: EdgePosition) => (e: React.MouseEvent) => { + if (element == null) { + return + } + + const edge = edgePositionToGridResizeEdge(position) + if (edge == null) { + return + } + + startResizeInteraction(EP.toUid(element.elementPath), edge)(e) + }, + [element, startResizeInteraction], + ) + + const resizeEdges = useResizeEdges([target], { + onEdgeDoubleClick: () => NO_OP, + onEdgeMouseMove: NO_OP, + onEdgeMouseDown: onEdgeMouseDown, + cursors: { + top: CSSCursor.RowResize, + bottom: CSSCursor.RowResize, + left: CSSCursor.ColResize, + right: CSSCursor.ColResize, + }, + }) + if ( element == null || element.globalFrame == null || @@ -1541,57 +1560,16 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) pointerEvents: 'none', }} > - {GridResizeEdges.map((edge) => { - const properties = gridResizeEdgeProperties(edge) - const visible = !isResizing || resizingEdge === edge - return ( -
-
-
- ) - })} + {resizeEdges.top} + {resizeEdges.left} + {resizeEdges.bottom} + {resizeEdges.right}
) } -const GRID_RESIZE_HANDLE_SIZES = { - long: 24, - short: 4, -} - function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor { switch (edge) { case 'column-end': @@ -1605,17 +1583,6 @@ function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor { } } -function gridEdgeToWidthHeight(props: GridResizeEdgeProperties, scale: number): CSSProperties { - return { - width: props.isColumn ? (GRID_RESIZE_HANDLE_SIZES.short * 4) / scale : '100%', - height: props.isRow ? (GRID_RESIZE_HANDLE_SIZES.short * 4) / scale : '100%', - top: props.isStart ? 0 : undefined, - left: props.isStart ? 0 : undefined, - right: props.isEnd ? 0 : undefined, - bottom: props.isEnd ? 0 : undefined, - } -} - function gridKeyFromPath(path: ElementPath): string { return `grid-${EP.toString(path)}` } diff --git a/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx b/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx index c8db573bd4af..366453bbf8ad 100644 --- a/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx +++ b/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx @@ -18,7 +18,7 @@ import { useColorTheme } from '../../../../uuiui' import type { EditorDispatch } from '../../../editor/action-types' import { applyCommandsAction } from '../../../editor/actions/action-creators' import { useDispatch } from '../../../editor/store/dispatch-context' -import { AllElementProps, getMetadata } from '../../../editor/store/editor-state' +import { getMetadata } from '../../../editor/store/editor-state' import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' import type { FixedHugFill } from '../../../inspector/inspector-common' import { @@ -34,14 +34,13 @@ import { createInteractionViaMouse } from '../../canvas-strategies/interaction-s import type { EdgePosition } from '../../canvas-types' import { CSSCursor } from '../../canvas-types' import { getAllTargetsUnderAreaAABB, windowToCanvasCoordinates } from '../../dom-lookup' -import { SmallElementSize, useBoundingBox } from '../bounding-box-hooks' +import { useBoundingBox } from '../bounding-box-hooks' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' import { isZeroSizedElement } from '../outline-utils' import { useMaybeHighlightElement } from './select-mode-hooks' import { isEdgePositionEqualTo } from '../../canvas-utils' import { treatElementAsGroupLike } from '../../canvas-strategies/strategies/group-helpers' -import { treatElementAsFragmentLike } from '../../canvas-strategies/strategies/fragment-like-helpers' -import { ElementPathTrees } from '../../../../core/shared/element-path-tree' +import { useResizeEdges } from './use-resize-edges' export const AbsoluteResizeControlTestId = (targets: Array): string => `${targets.map(EP.toString).sort()}-absolute-resize-control` @@ -54,17 +53,16 @@ interface AbsoluteResizeControlProps { export const SizeLabelID = 'SizeLabel' export const SizeLabelTestId = 'SizeLabelTestId' -function shouldUseSmallElementResizeControl(size: number, scale: number): boolean { - return size <= SmallElementSize / scale -} - export const AbsoluteResizeControl = controlForStrategyMemoized( ({ targets, pathsWereReplaced }: AbsoluteResizeControlProps) => { - const scale = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.scale, - 'AbsoluteResizeControl scale', - ) + const dispatch = useDispatch() + + const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) + const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) + const selectedElementsRef = useRefEditorState((store) => store.editor.selectedViews) + const elementPathTreeRef = useRefEditorState((store) => store.editor.elementPathTree) + + const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() const controlRef = useBoundingBox(targets, (ref, safeGappedBoundingBox, realBoundingBox) => { if (isZeroSizedElement(realBoundingBox)) { @@ -78,54 +76,6 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( } }) - const leftRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) - const lineSize = ResizeMouseAreaSize / scale - const width = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = `${-lineSize / 2}px` - const offsetTop = `0px` - - ref.current.style.width = `${width}px` - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - ref.current.style.height = boundingBox.height + 'px' - }) - const topRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) - const lineSize = ResizeMouseAreaSize / scale - const height = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = `0px` - const offsetTop = `${-lineSize / 2}px` - - ref.current.style.width = boundingBox.width + 'px' - ref.current.style.height = height + 'px' - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - }) - const rightRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) - const lineSize = ResizeMouseAreaSize / scale - const width = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = isSmallElement ? `0px` : `${-lineSize / 2}px` - const offsetTop = `0px` - - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - ref.current.style.left = boundingBox.width + 'px' - ref.current.style.width = width + 'px' - ref.current.style.height = boundingBox.height + 'px' - }) - - const bottomRef = useBoundingBox(targets, (ref, boundingBox) => { - const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) - const lineSize = ResizeMouseAreaSize / scale - const height = isSmallElement ? lineSize / 2 : lineSize - const offsetLeft = `0px` - const offsetTop = isSmallElement ? `0px` : `${-lineSize / 2}px` - - ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` - ref.current.style.top = boundingBox.height + 'px' - ref.current.style.width = boundingBox.width + 'px' - ref.current.style.height = height + 'px' - }) - const topLeftRef = useBoundingBox(targets, NO_OP) const topRightRef = useBoundingBox(targets, (ref, boundingBox) => { ref.current.style.left = boundingBox.width + 'px' @@ -144,6 +94,47 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( ref.current.style.width = boundingBox.width + 'px' }) + const scale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'AbsoluteResizeControl scale', + ) + const onEdgeMouseDown = React.useCallback( + (position: EdgePosition) => (event: React.MouseEvent) => { + startResizeInteraction(event, dispatch, position, canvasOffsetRef.current, scale) + }, + [dispatch, canvasOffsetRef, scale], + ) + + const onEdgeMouseMove = React.useCallback( + (event: React.MouseEvent) => { + maybeClearHighlightsOnHoverEnd() + event.stopPropagation() + }, + [maybeClearHighlightsOnHoverEnd], + ) + + const onEdgeDoubleClick = React.useCallback( + (direction: 'horizontal' | 'vertical') => () => { + executeFirstApplicableStrategy( + dispatch, + setPropHugStrategies( + metadataRef.current, + selectedElementsRef.current, + elementPathTreeRef.current, + invert(direction), + ), + ) + }, + [dispatch, metadataRef, elementPathTreeRef, selectedElementsRef], + ) + + const resizeEdges = useResizeEdges(targets, { + onEdgeMouseDown, + onEdgeMouseMove, + onEdgeDoubleClick, + }) + return (
- - - - + {resizeEdges.top} + {resizeEdges.left} + {resizeEdges.bottom} + {resizeEdges.right} ((props, ref) => { - const scale = useEditorState( - Substores.canvasOffset, - (store) => store.editor.canvas.scale, - 'ResizeEdge scale', - ) - const dispatch = useDispatch() - const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) - const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) - const selectedElementsRef = useRefEditorState((store) => store.editor.selectedViews) - const elementPathTreeRef = useRefEditorState((store) => store.editor.elementPathTree) - - const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() - - const onEdgeMouseDown = React.useCallback( - (event: React.MouseEvent) => { - startResizeInteraction(event, dispatch, props.position, canvasOffsetRef.current, scale) - }, - [dispatch, props.position, canvasOffsetRef, scale], - ) - - const onMouseMove = React.useCallback( - (event: React.MouseEvent) => { - maybeClearHighlightsOnHoverEnd() - event.stopPropagation() - }, - [maybeClearHighlightsOnHoverEnd], - ) - - const onEdgeDblClick = React.useCallback(() => { - executeFirstApplicableStrategy( - dispatch, - setPropHugStrategies( - metadataRef.current, - selectedElementsRef.current, - elementPathTreeRef.current, - invert(props.direction), - ), - ) - }, [dispatch, metadataRef, props.direction, elementPathTreeRef, selectedElementsRef]) - - return ( -
- ) - }), -) - const sizeLabel = (state: FixedHugFill['type'], actualSize: number): string => { switch (state) { case 'fill': diff --git a/editor/src/components/canvas/controls/select-mode/resize-edge.tsx b/editor/src/components/canvas/controls/select-mode/resize-edge.tsx new file mode 100644 index 000000000000..6e913ffe61f2 --- /dev/null +++ b/editor/src/components/canvas/controls/select-mode/resize-edge.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import type { CSSCursor, EdgePosition } from '../../canvas-types' +import { ResizePointTestId } from './absolute-resize-control' + +interface ResizeEdgeProps { + cursor: CSSCursor + direction: 'horizontal' | 'vertical' + position: EdgePosition + onMouseDown: (e: React.MouseEvent) => void + onMouseMove: (e: React.MouseEvent) => void + onDoubleClick: (e: React.MouseEvent) => void +} + +export const ResizeEdge = React.memo( + React.forwardRef((props, ref) => { + return ( +
+ ) + }), +) +ResizeEdge.displayName = 'ResizeEdge' diff --git a/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx b/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx new file mode 100644 index 000000000000..34831303a362 --- /dev/null +++ b/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx @@ -0,0 +1,133 @@ +import React from 'react' +import type { ElementPath } from '../../../../core/shared/project-file-types' +import { Substores, useEditorState } from '../../../editor/store/store-hook' +import type { EdgePosition } from '../../canvas-types' +import { CSSCursor } from '../../canvas-types' +import { SmallElementSize, useBoundingBox } from '../bounding-box-hooks' +import { ResizeEdge } from './resize-edge' + +const RESIZE_MOUSE_AREA_SIZE = 10 + +export function useResizeEdges( + targets: ElementPath[], + params: { + onEdgeMouseDown: (position: EdgePosition) => (e: React.MouseEvent) => void + onEdgeMouseMove: (e: React.MouseEvent) => void + onEdgeDoubleClick: ( + direction: 'horizontal' | 'vertical', + ) => (e: React.MouseEvent) => void + cursors?: { + top?: CSSCursor + left?: CSSCursor + bottom?: CSSCursor + right?: CSSCursor + } + }, +) { + const scale = useEditorState( + Substores.canvasOffset, + (store) => store.editor.canvas.scale, + 'useResizeEdges scale', + ) + + const topRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const height = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = `0px` + const offsetTop = `${-lineSize / 2}px` + + ref.current.style.width = boundingBox.width + 'px' + ref.current.style.height = height + 'px' + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + }) + + const leftRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const width = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = `${-lineSize / 2}px` + const offsetTop = `0px` + + ref.current.style.width = `${width}px` + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + ref.current.style.height = boundingBox.height + 'px' + }) + + const bottomRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.height, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const height = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = `0px` + const offsetTop = isSmallElement ? `0px` : `${-lineSize / 2}px` + + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + ref.current.style.top = boundingBox.height + 'px' + ref.current.style.width = boundingBox.width + 'px' + ref.current.style.height = height + 'px' + }) + + const rightRef = useBoundingBox(targets, (ref, boundingBox) => { + const isSmallElement = shouldUseSmallElementResizeControl(boundingBox.width, scale) + const lineSize = RESIZE_MOUSE_AREA_SIZE / scale + const width = isSmallElement ? lineSize / 2 : lineSize + const offsetLeft = isSmallElement ? `0px` : `${-lineSize / 2}px` + const offsetTop = `0px` + + ref.current.style.transform = `translate(${offsetLeft}, ${offsetTop})` + ref.current.style.left = boundingBox.width + 'px' + ref.current.style.width = width + 'px' + ref.current.style.height = boundingBox.height + 'px' + }) + + return { + top: ( + + ), + left: ( + + ), + bottom: ( + + ), + right: ( + + ), + } +} + +function shouldUseSmallElementResizeControl(size: number, scale: number): boolean { + return size <= SmallElementSize / scale +} diff --git a/editor/src/components/inspector/inspector-common.ts b/editor/src/components/inspector/inspector-common.ts index 32eeadabddf6..2d6cadf9f718 100644 --- a/editor/src/components/inspector/inspector-common.ts +++ b/editor/src/components/inspector/inspector-common.ts @@ -911,6 +911,16 @@ export function isFixedHugFillModeApplied( ) } +export function isFillOrStretchModeApplied( + metadata: ElementInstanceMetadataMap, + element: ElementPath, +): boolean { + return ( + isFixedHugFillModeApplied(metadata, element, 'fill') || + isFixedHugFillModeApplied(metadata, element, 'stretch') + ) +} + export function isFixedHugFillModeAppliedOnAnySide( metadata: ElementInstanceMetadataMap, element: ElementPath, diff --git a/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap b/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap index 4635c7a78aab..18108e135a96 100644 --- a/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap +++ b/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap @@ -54,10 +54,10 @@ Array [ "/UtopiaSpiedExoticType(Symbol(react.fragment))/UtopiaSpiedExoticType(Symbol(react.fragment))//Symbol(react.memo)()", "/UtopiaSpiedExoticType(Symbol(react.fragment))/UtopiaSpiedExoticType(Symbol(react.fragment))//UtopiaSpiedExoticType(Symbol(react.fragment))", "/UtopiaSpiedExoticType(Symbol(react.fragment))/Symbol(react.memo)()//Symbol(react.memo)()", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))", "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))", "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))", @@ -66,10 +66,10 @@ Array [ "/Symbol(react.memo)()///div:data-testid='utopia-storyboard-uid/scene-aaa/app-entity:parent/ccc-absolute-resize-control'", "/Symbol(react.memo)()///Symbol(react.memo)()", "////div", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-1-0.5'", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-0.5-1'", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-0-0.5'", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-0.5-0'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-0.5-0'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-0-0.5'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-0.5-1'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-1-0.5'", "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))/Symbol(react.forward_ref)(ResizePoint)/div", "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))/Symbol(react.forward_ref)(ResizePoint)/div:data-testid='resize-control-0-0'", "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))/Symbol(react.forward_ref)(ResizePoint)/div", @@ -834,10 +834,10 @@ Array [ "/UtopiaSpiedExoticType(Symbol(react.fragment))/UtopiaSpiedExoticType(Symbol(react.fragment))//Symbol(react.memo)()", "/UtopiaSpiedExoticType(Symbol(react.fragment))/UtopiaSpiedExoticType(Symbol(react.fragment))//UtopiaSpiedExoticType(Symbol(react.fragment))", "/UtopiaSpiedExoticType(Symbol(react.fragment))/Symbol(react.memo)()//Symbol(react.memo)()", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", - "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)())", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", + "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))", "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))", "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))", "/Symbol(react.memo)()///Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))", @@ -846,10 +846,10 @@ Array [ "/Symbol(react.memo)()///div:data-testid='utopia-storyboard-uid/scene-aaa/app-entity:parent/ccc-absolute-resize-control'", "/Symbol(react.memo)()///Symbol(react.memo)()", "////div", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-1-0.5'", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-0.5-1'", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-0-0.5'", - "/div/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/div:data-testid='resize-control-0.5-0'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-0.5-0'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-0-0.5'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-0.5-1'", + "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizeEdge))/Symbol(react.forward_ref)(ResizeEdge)/div:data-testid='resize-control-1-0.5'", "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))/Symbol(react.forward_ref)(ResizePoint)/div", "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))/Symbol(react.forward_ref)(ResizePoint)/div:data-testid='resize-control-0-0'", "/div/Symbol(react.memo)(Symbol(react.forward_ref)(ResizePoint))/Symbol(react.forward_ref)(ResizePoint)/div",