From 390539090996c60a1b895cd3f3ea5154b439aab7 Mon Sep 17 00:00:00 2001
From: Balazs Bajorics <2226774+balazsbajorics@users.noreply.github.com>
Date: Wed, 9 Oct 2024 17:20:10 +0200
Subject: [PATCH 1/7] grid rulers
---
.../canvas/canvas-component-entry.tsx | 2 +-
.../strategies/resize-grid-strategy.ts | 14 +-
.../canvas/controls/canvas-offset-wrapper.tsx | 23 +-
.../canvas/controls/grid-controls-ruler.tsx | 380 ++++++++++++++++++
.../canvas/controls/grid-controls.tsx | 13 +-
5 files changed, 415 insertions(+), 17 deletions(-)
create mode 100644 editor/src/components/canvas/controls/grid-controls-ruler.tsx
diff --git a/editor/src/components/canvas/canvas-component-entry.tsx b/editor/src/components/canvas/canvas-component-entry.tsx
index 52e1365dea3f..3643767b3f9a 100644
--- a/editor/src/components/canvas/canvas-component-entry.tsx
+++ b/editor/src/components/canvas/canvas-component-entry.tsx
@@ -83,7 +83,7 @@ const CanvasComponentEntryInner = React.memo((props: CanvasComponentEntryProps)
clearRuntimeErrors()
}, [clearRuntimeErrors])
- const containerRef = useApplyCanvasOffsetToStyle(true)
+ const containerRef = useApplyCanvasOffsetToStyle(true, 'xy')
return (
<>
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 ae80e5b24252..3ab3fde1ece5 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
@@ -70,13 +70,13 @@ export const resizeGridStrategy: CanvasStrategyFactory = (
type: 'pointer',
},
controlsToRender: [
- {
- control: GridRowColumnResizingControls,
- props: { target: gridPath },
- key: `grid-row-col-resize-controls-${EP.toString(gridPath)}`,
- show: 'always-visible',
- priority: 'top',
- },
+ // {
+ // control: GridRowColumnResizingControls,
+ // props: { target: gridPath },
+ // key: `grid-row-col-resize-controls-${EP.toString(gridPath)}`,
+ // show: 'always-visible',
+ // priority: 'top',
+ // },
controlsForGridPlaceholders(gridPath),
],
fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_AXIS_HANDLE', 1),
diff --git a/editor/src/components/canvas/controls/canvas-offset-wrapper.tsx b/editor/src/components/canvas/controls/canvas-offset-wrapper.tsx
index a4fb60951eb6..d021cd5ca3c3 100644
--- a/editor/src/components/canvas/controls/canvas-offset-wrapper.tsx
+++ b/editor/src/components/canvas/controls/canvas-offset-wrapper.tsx
@@ -10,8 +10,11 @@ import { isFollowMode } from '../../editor/editor-modes'
import { liveblocksThrottle } from '../../../../liveblocks.config'
export const CanvasOffsetWrapper = React.memo(
- (props: { children?: React.ReactNode; setScaleToo?: boolean }) => {
- const elementRef = useApplyCanvasOffsetToStyle(props.setScaleToo ?? false)
+ (props: { children?: React.ReactNode; setScaleToo?: boolean; limitAxis?: 'x' | 'y' | 'xy' }) => {
+ const elementRef = useApplyCanvasOffsetToStyle(
+ props.setScaleToo ?? false,
+ props.limitAxis ?? 'xy',
+ )
return (
@@ -21,7 +24,10 @@ export const CanvasOffsetWrapper = React.memo(
},
)
-export function useApplyCanvasOffsetToStyle(setScaleToo: boolean): React.RefObject
{
+export function useApplyCanvasOffsetToStyle(
+ setScaleToo: boolean,
+ limitAxis: 'x' | 'y' | 'xy',
+): React.RefObject {
const elementRef = React.useRef(null)
const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset)
const scaleRef = useRefEditorState((store) => store.editor.canvas.scale)
@@ -37,11 +43,18 @@ export function useApplyCanvasOffsetToStyle(setScaleToo: boolean): React.RefObje
const applyCanvasOffset = React.useCallback(
(roundedCanvasOffset: CanvasVector) => {
+ const limitedCanvasOffset =
+ limitAxis === 'x'
+ ? { x: roundedCanvasOffset.x, y: 0 }
+ : limitAxis === 'y'
+ ? { x: 0, y: roundedCanvasOffset.y }
+ : roundedCanvasOffset
+
if (elementRef.current != null) {
elementRef.current.style.setProperty(
'transform',
(setScaleToo && scaleRef.current < 1 ? `scale(${scaleRef.current})` : '') +
- ` translate3d(${roundedCanvasOffset.x}px, ${roundedCanvasOffset.y}px, 0)`,
+ ` translate3d(${limitedCanvasOffset.x}px, ${limitedCanvasOffset.y}px, 0)`,
)
elementRef.current.style.setProperty(
'zoom',
@@ -56,7 +69,7 @@ export function useApplyCanvasOffsetToStyle(setScaleToo: boolean): React.RefObje
}
}
},
- [setScaleToo, scaleRef, isScrollAnimationActiveRef, mode],
+ [setScaleToo, scaleRef, isScrollAnimationActiveRef, mode, limitAxis],
)
useSelectorWithCallback(
diff --git a/editor/src/components/canvas/controls/grid-controls-ruler.tsx b/editor/src/components/canvas/controls/grid-controls-ruler.tsx
new file mode 100644
index 000000000000..4bdc10ff956c
--- /dev/null
+++ b/editor/src/components/canvas/controls/grid-controls-ruler.tsx
@@ -0,0 +1,380 @@
+/** @jsxRuntime classic */
+/** @jsx jsx */
+import { jsx } from '@emotion/react'
+import type { AnimationControls } from 'framer-motion'
+import { motion, useAnimationControls } from 'framer-motion'
+import type { CSSProperties } from 'react'
+import React from 'react'
+import type { Sides } from 'utopia-api/core'
+import type { ElementPath } from 'utopia-shared/src/types'
+import type {
+ GridDimension,
+ GridDiscreteDimension,
+} from '../../../components/inspector/common/css-utils'
+import {
+ isCSSKeyword,
+ isDynamicGridRepeat,
+ isGridCSSRepeat,
+ isStaticGridRepeat,
+ printGridAutoOrTemplateBase,
+ printGridCSSNumber,
+} from '../../../components/inspector/common/css-utils'
+import { MetadataUtils } from '../../../core/model/element-metadata-utils'
+import { mapDropNulls, stripNulls, uniqBy } from '../../../core/shared/array-utils'
+import { defaultEither } from '../../../core/shared/either'
+import * as EP from '../../../core/shared/element-path'
+import type {
+ ElementInstanceMetadata,
+ GridAutoOrTemplateDimensions,
+} from '../../../core/shared/element-template'
+import {
+ isGridAutoOrTemplateDimensions,
+ type GridAutoOrTemplateBase,
+} from '../../../core/shared/element-template'
+import type { CanvasPoint, CanvasRectangle } from '../../../core/shared/math-utils'
+import {
+ canvasPoint,
+ isFiniteRectangle,
+ isInfinityRectangle,
+ nullIfInfinity,
+ pointsEqual,
+ scaleRect,
+ windowPoint,
+ zeroRectangle,
+ zeroRectIfNullOrInfinity,
+} from '../../../core/shared/math-utils'
+import {
+ fromArrayIndex,
+ fromField,
+ fromTypeGuard,
+ notNull,
+} from '../../../core/shared/optics/optic-creators'
+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 { Modifier } from '../../../utils/modifiers'
+import { when } from '../../../utils/react-conditionals'
+import { useColorTheme, UtopiaStyles } from '../../../uuiui'
+import { useDispatch } from '../../editor/store/dispatch-context'
+import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook'
+import CanvasActions from '../canvas-actions'
+import type {
+ ControlWithProps,
+ WhenToShowControl,
+} from '../canvas-strategies/canvas-strategy-types'
+import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy-types'
+import type {
+ GridResizeEdge,
+ GridResizeEdgeProperties,
+} from '../canvas-strategies/interaction-state'
+import {
+ createInteractionViaMouse,
+ gridAxisHandle,
+ gridCellHandle,
+ gridResizeEdgeProperties,
+ GridResizeEdges,
+ gridResizeHandle,
+} from '../canvas-strategies/interaction-state'
+import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers'
+import type { EdgePosition } from '../canvas-types'
+import {
+ CSSCursor,
+ EdgePositionBottom,
+ EdgePositionLeft,
+ EdgePositionRight,
+ EdgePositionTop,
+} from '../canvas-types'
+import { windowToCanvasCoordinates } from '../dom-lookup'
+import type { Axis } from '../gap-utils'
+import { useCanvasAnimation } from '../ui-jsx-canvas-renderer/animation-context'
+import { CanvasOffsetWrapper } from './canvas-offset-wrapper'
+import { CanvasLabel } from './select-mode/controls-common'
+import { useMaybeHighlightElement } from './select-mode/select-mode-hooks'
+import type { GridCellCoordinates } from '../canvas-strategies/strategies/grid-cell-bounds'
+import { gridCellTargetId } from '../canvas-strategies/strategies/grid-cell-bounds'
+import {
+ getGlobalFrameOfGridCell,
+ getGridRelatedIndexes,
+} from '../canvas-strategies/strategies/grid-helpers'
+import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy'
+import {
+ GRID_RESIZE_HANDLE_CONTAINER_SIZE,
+ GRID_RESIZE_HANDLE_SIZE,
+ gridEdgeToCSSCursor,
+ gridEdgeToEdgePosition,
+ gridEdgeToWidthHeight,
+ GridResizeEdgeTestId,
+ GridResizingControl,
+ useGridData,
+} from './grid-controls'
+
+export const GridControlsRuler = React.memo(() => {
+ const selectedElement = useEditorState(
+ Substores.selectedViews,
+ (store) => store.editor.selectedViews?.[0],
+ `GridControlsRuler selectedElement`,
+ )
+ if (selectedElement == null) {
+ return null
+ }
+ return (
+
+ )
+})
+
+interface GridRowColumnResizingControlsProps {
+ target: ElementPath
+}
+
+export const GridRowColumnResizingControls =
+ controlForStrategyMemoized(({ target }) => {
+ const grids = useGridData([target])
+
+ function getStripedAreaLength(template: GridAutoOrTemplateBase | null, gap: number) {
+ if (template?.type !== 'DIMENSIONS') {
+ return null
+ }
+ return template.dimensions.reduce((acc, curr, index) => {
+ if (curr.type === 'NUMBER') {
+ return acc + curr.value.value + (index > 0 ? gap : 0)
+ }
+ return acc
+ }, 0)
+ }
+
+ const scale = useEditorState(
+ Substores.canvas,
+ (store) => store.editor.canvas.scale,
+ 'GridRowColumnResizingControls scale',
+ )
+
+ const gridsWithVisibleResizeControls = React.useMemo(() => {
+ return grids.filter((grid) => {
+ if (
+ grid.gridTemplateColumns?.type !== 'DIMENSIONS' ||
+ grid.gridTemplateRows?.type !== 'DIMENSIONS'
+ ) {
+ return false
+ }
+
+ // returns whether the rendered dimensions are too crowded, as in there are two cols/rows that are closer than the handle sizes
+ function tooCrowded(dimensions: GridDimension[]): boolean {
+ const visualSizes = dimensions.map(
+ (dim) => (dim.type === 'NUMBER' ? dim.value.value : 0) * scale,
+ )
+ return visualSizes.some((dim, index) => {
+ if (index < visualSizes.length - 1) {
+ const next = visualSizes[index + 1]
+ if (dim + next < GRID_RESIZE_HANDLE_SIZE * 2) {
+ return true
+ }
+ }
+ return false
+ })
+ }
+
+ return (
+ !tooCrowded(grid.gridTemplateColumns.dimensions) &&
+ !tooCrowded(grid.gridTemplateRows.dimensions)
+ )
+ })
+ }, [scale, grids])
+
+ return (
+
+
+ {gridsWithVisibleResizeControls.flatMap((grid) => {
+ return (
+
+ )
+ })}
+
+
+ {gridsWithVisibleResizeControls.flatMap((grid) => {
+ return (
+
+ )
+ })}
+
+
+ )
+ })
+
+export interface GridResizingProps {
+ axisValues: GridAutoOrTemplateBase | null
+ fromPropsAxisValues: GridAutoOrTemplateBase | null
+ stripedAreaLength: number | null
+ containingFrame: CanvasRectangle
+ axis: Axis
+ 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',
+ )
+
+ const fromProps = React.useMemo((): GridAutoOrTemplateDimensions | null => {
+ if (props.fromPropsAxisValues?.type !== 'DIMENSIONS') {
+ return null
+ }
+ if (!canResizeGridTemplate(props.fromPropsAxisValues)) {
+ return null
+ }
+ return {
+ type: 'DIMENSIONS',
+ dimensions: props.fromPropsAxisValues.dimensions.reduce(
+ (acc, cur): GridDiscreteDimension[] => {
+ if (isGridCSSRepeat(cur)) {
+ if (isDynamicGridRepeat(cur)) {
+ return acc
+ }
+ let expanded: GridDiscreteDimension[] = []
+ for (let i = 0; i < cur.times; i++) {
+ expanded.push(...cur.value.filter((v) => v.type !== 'REPEAT'))
+ }
+ return acc.concat(...expanded)
+ } else {
+ return acc.concat(cur)
+ }
+ },
+ [] as GridDiscreteDimension[],
+ ),
+ }
+ }, [props.fromPropsAxisValues])
+
+ const resizeLocked = React.useMemo(() => {
+ return fromProps == null || !canResizeGridTemplate(fromProps)
+ }, [fromProps])
+
+ const [resizingIndex, setResizingIndex] = React.useState(null)
+
+ // These are the indexes of the elements that will resize too alongside the one at the index of
+ // `resizingIndex`.
+ const coresizingIndexes: number[] = React.useMemo(() => {
+ if (props.fromPropsAxisValues?.type !== 'DIMENSIONS' || resizingIndex == null) {
+ return []
+ }
+ return getGridRelatedIndexes({
+ template: props.fromPropsAxisValues.dimensions,
+ index: resizingIndex,
+ })
+ }, [props.fromPropsAxisValues, resizingIndex])
+
+ if (props.axisValues == null) {
+ return null
+ }
+ switch (props.axisValues.type) {
+ case 'DIMENSIONS':
+ const size = GRID_RESIZE_HANDLE_CONTAINER_SIZE / canvasScale
+ const dimensions = props.axisValues.dimensions
+
+ return (
+ printGridCSSNumber(dim)).join(' ')
+ : undefined,
+ gridTemplateRows:
+ props.axis === 'row'
+ ? dimensions.map((dim) => printGridCSSNumber(dim)).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,
+ }}
+ >
+ {dimensions.flatMap((dimension, dimensionIndex) => {
+ return (
+
+ )
+ })}
+
+ )
+ case 'FALLBACK':
+ return null
+ default:
+ assertNever(props.axisValues)
+ }
+})
+GridResizing.displayName = 'GridResizing'
diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx
index 0a2ab0de5350..5b9fc8c254c1 100644
--- a/editor/src/components/canvas/controls/grid-controls.tsx
+++ b/editor/src/components/canvas/controls/grid-controls.tsx
@@ -98,6 +98,7 @@ import {
getGridRelatedIndexes,
} from '../canvas-strategies/strategies/grid-helpers'
import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy'
+import { GridControlsRuler } from './grid-controls-ruler'
const CELL_ANIMATION_DURATION = 0.15 // seconds
@@ -150,8 +151,8 @@ function getLabelForAxis(
return gridCSSNumberToLabel(defaultEither(fromDOM, fromPropsAtIndex))
}
-const GRID_RESIZE_HANDLE_CONTAINER_SIZE = 30 // px
-const GRID_RESIZE_HANDLE_SIZE = 15 // px
+export const GRID_RESIZE_HANDLE_CONTAINER_SIZE = 30 // px
+export const GRID_RESIZE_HANDLE_SIZE = 15 // px
export interface GridResizingControlProps {
dimension: GridDimension
@@ -1133,6 +1134,7 @@ export const GridControls = controlForStrategyMemoized(({ tar
return (
+
{grids.map((grid) => {
return
@@ -1784,7 +1786,7 @@ export function gridEdgeToEdgePosition(edge: GridResizeEdge): EdgePosition {
}
}
-function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor {
+export function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor {
switch (edge) {
case 'column-end':
case 'column-start':
@@ -1797,7 +1799,10 @@ function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor {
}
}
-function gridEdgeToWidthHeight(props: GridResizeEdgeProperties, scale: number): CSSProperties {
+export 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%',
From 3d5fa1f0ba5bab0916c7005b28b95ec92b7cfe2c Mon Sep 17 00:00:00 2001
From: Balazs Bajorics <2226774+balazsbajorics@users.noreply.github.com>
Date: Thu, 10 Oct 2024 11:38:16 +0200
Subject: [PATCH 2/7] padding the panel containers
---
editor/src/components/canvas/grid-panels-container.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/editor/src/components/canvas/grid-panels-container.tsx b/editor/src/components/canvas/grid-panels-container.tsx
index 3dedde6253a3..8e05e34bf887 100644
--- a/editor/src/components/canvas/grid-panels-container.tsx
+++ b/editor/src/components/canvas/grid-panels-container.tsx
@@ -106,9 +106,9 @@ export const GridPanelsContainer = React.memo(() => {
gridTemplateColumns: `[col] ${columnWidths[0]}px [col] ${columnWidths[1]}px [canvas] 1fr [col] ${columnWidths[2]}px [col] ${columnWidths[3]}px [end]`,
gridTemplateRows: 'repeat(12, 1fr)',
gridAutoFlow: 'dense',
- paddingTop: GridPanelVerticalGapHalf + GridVerticalExtraPadding,
+ paddingTop: GridPanelVerticalGapHalf + GridVerticalExtraPadding + 20,
paddingBottom: GridPanelVerticalGapHalf + GridVerticalExtraPadding,
- paddingLeft: GridPanelHorizontalGapHalf + GridHorizontalExtraPadding,
+ paddingLeft: GridPanelHorizontalGapHalf + GridHorizontalExtraPadding + 20,
paddingRight: GridPanelHorizontalGapHalf + GridHorizontalExtraPadding,
}}
>
From 17994198d7ae2bd55b7b352abd4d56dfbe319d48 Mon Sep 17 00:00:00 2001
From: Balazs Bajorics <2226774+balazsbajorics@users.noreply.github.com>
Date: Thu, 10 Oct 2024 11:43:06 +0200
Subject: [PATCH 3/7] cleanup
---
.../strategies/resize-grid-strategy.ts | 20 ++---
.../canvas/controls/grid-controls-ruler.tsx | 46 +++++-----
.../canvas/controls/grid-controls.tsx | 88 -------------------
3 files changed, 30 insertions(+), 124 deletions(-)
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 3ab3fde1ece5..ccf88f8ddbd5 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
@@ -8,10 +8,7 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils'
import * as EP from '../../../../core/shared/element-path'
import * as PP from '../../../../core/shared/property-path'
import { setProperty } from '../../commands/set-property-command'
-import {
- controlsForGridPlaceholders,
- GridRowColumnResizingControls,
-} from '../../controls/grid-controls'
+import { controlsForGridPlaceholders } from '../../controls/grid-controls'
import type { CanvasStrategyFactory } from '../canvas-strategies'
import { onlyFitWhenDraggingThisControl } from '../canvas-strategies'
import type { InteractionCanvasState } from '../canvas-strategy-types'
@@ -37,6 +34,7 @@ import type { GridAutoOrTemplateBase } from '../../../../core/shared/element-tem
import { expandGridDimensions, replaceGridTemplateDimensionAtIndex } from './grid-helpers'
import { setCursorCommand } from '../../commands/set-cursor-command'
import { CSSCursor } from '../../canvas-types'
+import { GridRowColumnResizingControls } from '../../controls/grid-controls-ruler'
export const resizeGridStrategy: CanvasStrategyFactory = (
canvasState: InteractionCanvasState,
@@ -70,13 +68,13 @@ export const resizeGridStrategy: CanvasStrategyFactory = (
type: 'pointer',
},
controlsToRender: [
- // {
- // control: GridRowColumnResizingControls,
- // props: { target: gridPath },
- // key: `grid-row-col-resize-controls-${EP.toString(gridPath)}`,
- // show: 'always-visible',
- // priority: 'top',
- // },
+ {
+ control: GridRowColumnResizingControls,
+ props: { target: gridPath },
+ key: `grid-row-col-resize-controls-${EP.toString(gridPath)}`,
+ show: 'always-visible',
+ priority: 'top',
+ },
controlsForGridPlaceholders(gridPath),
],
fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_AXIS_HANDLE', 1),
diff --git a/editor/src/components/canvas/controls/grid-controls-ruler.tsx b/editor/src/components/canvas/controls/grid-controls-ruler.tsx
index 4bdc10ff956c..a6c2ea39d98f 100644
--- a/editor/src/components/canvas/controls/grid-controls-ruler.tsx
+++ b/editor/src/components/canvas/controls/grid-controls-ruler.tsx
@@ -118,31 +118,7 @@ export const GridControlsRuler = React.memo(() => {
if (selectedElement == null) {
return null
}
- return (
-
- )
+ return {/* */}
})
interface GridRowColumnResizingControlsProps {
@@ -205,6 +181,26 @@ export const GridRowColumnResizingControls =
return (
+
+
{gridsWithVisibleResizeControls.flatMap((grid) => {
return (
diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx
index 5b9fc8c254c1..942665a74b3e 100644
--- a/editor/src/components/canvas/controls/grid-controls.tsx
+++ b/editor/src/components/canvas/controls/grid-controls.tsx
@@ -555,94 +555,6 @@ interface GridRowColumnResizingControlsProps {
target: ElementPath
}
-export const GridRowColumnResizingControls =
- controlForStrategyMemoized(({ target }) => {
- const grids = useGridData([target])
-
- function getStripedAreaLength(template: GridAutoOrTemplateBase | null, gap: number) {
- if (template?.type !== 'DIMENSIONS') {
- return null
- }
- return template.dimensions.reduce((acc, curr, index) => {
- if (curr.type === 'NUMBER') {
- return acc + curr.value.value + (index > 0 ? gap : 0)
- }
- return acc
- }, 0)
- }
-
- const scale = useEditorState(
- Substores.canvas,
- (store) => store.editor.canvas.scale,
- 'GridRowColumnResizingControls scale',
- )
-
- const gridsWithVisibleResizeControls = React.useMemo(() => {
- return grids.filter((grid) => {
- if (
- grid.gridTemplateColumns?.type !== 'DIMENSIONS' ||
- grid.gridTemplateRows?.type !== 'DIMENSIONS'
- ) {
- return false
- }
-
- // returns whether the rendered dimensions are too crowded, as in there are two cols/rows that are closer than the handle sizes
- function tooCrowded(dimensions: GridDimension[]): boolean {
- const visualSizes = dimensions.map(
- (dim) => (dim.type === 'NUMBER' ? dim.value.value : 0) * scale,
- )
- return visualSizes.some((dim, index) => {
- if (index < visualSizes.length - 1) {
- const next = visualSizes[index + 1]
- if (dim + next < GRID_RESIZE_HANDLE_SIZE * 2) {
- return true
- }
- }
- return false
- })
- }
-
- return (
- !tooCrowded(grid.gridTemplateColumns.dimensions) &&
- !tooCrowded(grid.gridTemplateRows.dimensions)
- )
- })
- }, [scale, grids])
-
- return (
-
- {gridsWithVisibleResizeControls.flatMap((grid) => {
- return (
-
- )
- })}
- {gridsWithVisibleResizeControls.flatMap((grid) => {
- return (
-
- )
- })}
-
- )
- })
-
export const GridControlsKey = (gridPath: ElementPath) => `grid-controls-${EP.toString(gridPath)}`
export interface GridControlProps {
From 07543724f504d2e265e295f38c034e7a88492b66 Mon Sep 17 00:00:00 2001
From: Balazs Bajorics <2226774+balazsbajorics@users.noreply.github.com>
Date: Thu, 10 Oct 2024 14:34:33 +0200
Subject: [PATCH 4/7] more move
---
.../canvas/controls/grid-controls-ruler-2.tsx | 735 ++++++++++++++++++
.../canvas/controls/grid-controls.tsx | 619 +--------------
2 files changed, 739 insertions(+), 615 deletions(-)
create mode 100644 editor/src/components/canvas/controls/grid-controls-ruler-2.tsx
diff --git a/editor/src/components/canvas/controls/grid-controls-ruler-2.tsx b/editor/src/components/canvas/controls/grid-controls-ruler-2.tsx
new file mode 100644
index 000000000000..6c7cfb282ed4
--- /dev/null
+++ b/editor/src/components/canvas/controls/grid-controls-ruler-2.tsx
@@ -0,0 +1,735 @@
+/** @jsxRuntime classic */
+/** @jsx jsx */
+import { jsx } from '@emotion/react'
+import type { AnimationControls } from 'framer-motion'
+import { motion, useAnimationControls } from 'framer-motion'
+import type { CSSProperties } from 'react'
+import React from 'react'
+import type { Sides } from 'utopia-api/core'
+import type { ElementPath } from 'utopia-shared/src/types'
+import type {
+ GridDimension,
+ GridDiscreteDimension,
+} from '../../../components/inspector/common/css-utils'
+import {
+ isCSSKeyword,
+ isDynamicGridRepeat,
+ isGridCSSRepeat,
+ isStaticGridRepeat,
+ printGridAutoOrTemplateBase,
+ printGridCSSNumber,
+} from '../../../components/inspector/common/css-utils'
+import { MetadataUtils } from '../../../core/model/element-metadata-utils'
+import { mapDropNulls, stripNulls, uniqBy } from '../../../core/shared/array-utils'
+import { defaultEither } from '../../../core/shared/either'
+import * as EP from '../../../core/shared/element-path'
+import type {
+ ElementInstanceMetadata,
+ GridAutoOrTemplateDimensions,
+} from '../../../core/shared/element-template'
+import {
+ isGridAutoOrTemplateDimensions,
+ type GridAutoOrTemplateBase,
+} from '../../../core/shared/element-template'
+import type { CanvasPoint, CanvasRectangle } from '../../../core/shared/math-utils'
+import {
+ canvasPoint,
+ isFiniteRectangle,
+ isInfinityRectangle,
+ nullIfInfinity,
+ pointsEqual,
+ scaleRect,
+ windowPoint,
+ zeroRectangle,
+ zeroRectIfNullOrInfinity,
+} from '../../../core/shared/math-utils'
+import {
+ fromArrayIndex,
+ fromField,
+ fromTypeGuard,
+ notNull,
+} from '../../../core/shared/optics/optic-creators'
+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 { Modifier } from '../../../utils/modifiers'
+import { when } from '../../../utils/react-conditionals'
+import { useColorTheme, UtopiaStyles } from '../../../uuiui'
+import { useDispatch } from '../../editor/store/dispatch-context'
+import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook'
+import CanvasActions from '../canvas-actions'
+import type {
+ ControlWithProps,
+ WhenToShowControl,
+} from '../canvas-strategies/canvas-strategy-types'
+import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy-types'
+import type {
+ GridResizeEdge,
+ GridResizeEdgeProperties,
+} from '../canvas-strategies/interaction-state'
+import {
+ createInteractionViaMouse,
+ gridAxisHandle,
+ gridCellHandle,
+ gridResizeEdgeProperties,
+ GridResizeEdges,
+ gridResizeHandle,
+} from '../canvas-strategies/interaction-state'
+import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers'
+import type { EdgePosition } from '../canvas-types'
+import {
+ CSSCursor,
+ EdgePositionBottom,
+ EdgePositionLeft,
+ EdgePositionRight,
+ EdgePositionTop,
+} from '../canvas-types'
+import { windowToCanvasCoordinates } from '../dom-lookup'
+import type { Axis } from '../gap-utils'
+import { useCanvasAnimation } from '../ui-jsx-canvas-renderer/animation-context'
+import { CanvasOffsetWrapper } from './canvas-offset-wrapper'
+import { CanvasLabel } from './select-mode/controls-common'
+import { useMaybeHighlightElement } from './select-mode/select-mode-hooks'
+import type { GridCellCoordinates } from '../canvas-strategies/strategies/grid-cell-bounds'
+import { gridCellTargetId } from '../canvas-strategies/strategies/grid-cell-bounds'
+import {
+ getGlobalFrameOfGridCell,
+ getGridRelatedIndexes,
+} from '../canvas-strategies/strategies/grid-helpers'
+import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy'
+import type { GridData } from './grid-controls'
+import {
+ getNullableAutoOrTemplateBaseString,
+ GRID_RESIZE_HANDLE_CONTAINER_SIZE,
+ GRID_RESIZE_HANDLE_SIZE,
+ GridCellTestId,
+ gridEdgeToCSSCursor,
+ gridEdgeToEdgePosition,
+ gridEdgeToWidthHeight,
+ GridResizeEdgeTestId,
+ GridResizingControl,
+ useGridData,
+} from './grid-controls'
+
+const CELL_ANIMATION_DURATION = 0.15 // seconds
+
+export interface GridControlProps {
+ grid: GridData
+}
+
+export const GridControl = React.memo(({ grid }) => {
+ const dispatch = useDispatch()
+ const controls = useAnimationControls()
+ const colorTheme = useColorTheme()
+
+ const editorMetadata = useEditorState(
+ Substores.metadata,
+ (store) => store.editor.jsxMetadata,
+ 'GridControl editorMetadata',
+ )
+
+ const interactionLatestMetadata = useEditorState(
+ Substores.canvas,
+ (store) =>
+ store.editor.canvas.interactionSession?.interactionData.type === 'DRAG'
+ ? store.editor.canvas.interactionSession.latestMetadata
+ : null,
+ 'GridControl interactionLatestMetadata',
+ )
+
+ const jsxMetadata = React.useMemo(
+ () => interactionLatestMetadata ?? editorMetadata,
+ [interactionLatestMetadata, editorMetadata],
+ )
+
+ const activelyDraggingOrResizingCell = useEditorState(
+ Substores.canvas,
+ (store) =>
+ store.editor.canvas.interactionSession != null &&
+ store.editor.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE' &&
+ store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' &&
+ store.editor.canvas.interactionSession?.interactionData.modifiers.cmd !== true &&
+ store.editor.canvas.interactionSession?.interactionData.drag != null
+ ? store.editor.canvas.interactionSession.activeControl.id
+ : null,
+ 'GridControl activelyDraggingOrResizingCell',
+ )
+
+ const currentHoveredCell = useEditorState(
+ Substores.canvas,
+ (store) => store.editor.canvas.controls.gridControlData?.targetCell ?? null,
+ 'GridControl currentHoveredCell',
+ )
+
+ const targetsAreCellsWithPositioning = useEditorState(
+ Substores.metadata,
+ (store) =>
+ store.editor.selectedViews.every((elementPath) =>
+ MetadataUtils.isGridCellWithPositioning(store.editor.jsxMetadata, elementPath),
+ ),
+ 'GridControl targetsAreCellsWithPositioning',
+ )
+
+ const anyTargetAbsolute = useEditorState(
+ Substores.metadata,
+ (store) =>
+ store.editor.selectedViews.some((elementPath) =>
+ MetadataUtils.isPositionAbsolute(
+ MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, elementPath),
+ ),
+ ),
+ 'GridControl anyTargetAbsolute',
+ )
+
+ const scale = useEditorState(
+ Substores.canvas,
+ (store) => store.editor.canvas.scale,
+ 'GridControl scale',
+ )
+
+ const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset)
+
+ const startInteractionWithUid = React.useCallback(
+ (params: { uid: string; row: number; column: number; frame: CanvasRectangle }) =>
+ (event: React.MouseEvent) => {
+ setInitialShadowFrame(params.frame)
+
+ const start = windowToCanvasCoordinates(
+ scale,
+ canvasOffsetRef.current,
+ windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }),
+ )
+
+ dispatch([
+ CanvasActions.createInteractionSession(
+ createInteractionViaMouse(
+ start.canvasPositionRounded,
+ Modifier.modifiersForEvent(event),
+ gridCellHandle({ id: params.uid }),
+ 'zero-drag-not-permitted',
+ ),
+ ),
+ ])
+ },
+ [canvasOffsetRef, dispatch, scale],
+ )
+
+ const cells = React.useMemo(() => {
+ const children = MetadataUtils.getChildrenUnordered(jsxMetadata, grid.elementPath)
+ return mapDropNulls((cell, index) => {
+ if (cell == null || cell.globalFrame == null || !isFiniteRectangle(cell.globalFrame)) {
+ return null
+ }
+ const countedRow = Math.floor(index / grid.columns) + 1
+ const countedColumn = Math.floor(index % grid.columns) + 1
+
+ const columnFromProps = cell.specialSizeMeasurements.elementGridProperties.gridColumnStart
+ const rowFromProps = cell.specialSizeMeasurements.elementGridProperties.gridRowStart
+ return {
+ elementPath: cell.elementPath,
+ globalFrame: cell.globalFrame,
+ borderRadius: cell.specialSizeMeasurements.borderRadius,
+ column:
+ columnFromProps == null
+ ? countedColumn
+ : isCSSKeyword(columnFromProps)
+ ? countedColumn
+ : columnFromProps.numericalPosition ?? countedColumn,
+ row:
+ rowFromProps == null
+ ? countedRow
+ : isCSSKeyword(rowFromProps)
+ ? countedRow
+ : rowFromProps.numericalPosition ?? countedRow,
+ index: index,
+ }
+ }, children)
+ }, [grid, jsxMetadata])
+
+ const dragging = useEditorState(
+ Substores.canvas,
+ (store) =>
+ store.editor.canvas.interactionSession != null &&
+ store.editor.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE'
+ ? store.editor.canvas.interactionSession.activeControl.id
+ : null,
+ 'GridControl dragging',
+ )
+
+ const shadow = React.useMemo(() => {
+ return cells.find((cell) => EP.toUid(cell.elementPath) === dragging) ?? null
+ }, [cells, dragging])
+
+ const [initialShadowFrame, setInitialShadowFrame] = React.useState(
+ shadow?.globalFrame ?? null,
+ )
+
+ const interactionData = useEditorState(
+ Substores.canvas,
+ (store) =>
+ store.editor.canvas.interactionSession?.interactionData.type === 'DRAG'
+ ? store.editor.canvas.interactionSession.interactionData
+ : null,
+ 'GridControl interactionData',
+ )
+
+ const { hoveringStart } = useMouseMove(activelyDraggingOrResizingCell)
+
+ // NOTE: this stuff is meant to be temporary, until we settle on the set of interaction pieces we like.
+ // After that, we should get rid of this.
+ const shadowPosition = React.useMemo(() => {
+ const drag = interactionData?.drag
+ const dragStart = interactionData?.dragStart
+ if (
+ initialShadowFrame == null ||
+ interactionData == null ||
+ drag == null ||
+ dragStart == null ||
+ hoveringStart == null ||
+ shadow == null
+ ) {
+ return null
+ }
+
+ const getCoord = (axis: 'x' | 'y', dimension: 'width' | 'height') => {
+ return (
+ shadow.globalFrame[axis] +
+ drag[axis] -
+ (shadow.globalFrame[axis] - dragStart[axis]) -
+ shadow.globalFrame[dimension] *
+ ((dragStart[axis] - initialShadowFrame[axis]) / initialShadowFrame[dimension])
+ )
+ }
+
+ // make sure the shadow is displayed only inside the grid container bounds
+ function wrapCoord(c: number, min: number, max: number, shadowSize: number) {
+ return Math.min(Math.max(c, min), max - shadowSize)
+ }
+
+ return {
+ x: wrapCoord(
+ getCoord('x', 'width') ?? 0,
+ grid.frame.x,
+ grid.frame.x + grid.frame.width,
+ shadow.globalFrame.width,
+ ),
+ y: wrapCoord(
+ getCoord('y', 'height') ?? 0,
+ grid.frame.y,
+ grid.frame.y + grid.frame.height,
+ shadow.globalFrame.height,
+ ),
+ }
+ }, [
+ interactionData,
+ initialShadowFrame,
+ hoveringStart,
+ shadow,
+ grid.frame.x,
+ grid.frame.width,
+ grid.frame.y,
+ grid.frame.height,
+ ])
+
+ const gridPath = optionalMap(EP.parentPath, shadow?.elementPath)
+
+ const targetRootCell = useEditorState(
+ Substores.canvas,
+ (store) => store.editor.canvas.controls.gridControlData?.rootCell ?? null,
+ 'GridControl targetRootCell',
+ )
+
+ useCellAnimation({
+ disabled: anyTargetAbsolute,
+ targetRootCell: targetRootCell,
+ controls: controls,
+ shadowFrame: initialShadowFrame,
+ gridPath: gridPath,
+ })
+
+ const placeholders = Array.from(Array(grid.cells).keys())
+ let style: CSSProperties = {
+ position: 'absolute',
+ top: grid.frame.y - 1, // account for border!
+ left: grid.frame.x - 1, // account for border!
+ width: grid.frame.width,
+ height: grid.frame.height,
+ display: 'grid',
+ gridTemplateColumns: getNullableAutoOrTemplateBaseString(grid.gridTemplateColumns),
+ gridTemplateRows: getNullableAutoOrTemplateBaseString(grid.gridTemplateRows),
+ backgroundColor:
+ activelyDraggingOrResizingCell != null ? colorTheme.primary10.value : 'transparent',
+ border: `1px solid ${
+ activelyDraggingOrResizingCell != null ? colorTheme.primary.value : 'transparent'
+ }`,
+ justifyContent: grid.justifyContent ?? 'initial',
+ alignContent: grid.alignContent ?? 'initial',
+ pointerEvents: 'none',
+ padding:
+ grid.padding == null
+ ? 0
+ : `${grid.padding.top}px ${grid.padding.right}px ${grid.padding.bottom}px ${grid.padding.left}px`,
+ }
+
+ // Gap needs to be set only if the other two are not present or we'll have rendering issues
+ // due to how measurements are calculated.
+ if (grid.rowGap != null && grid.columnGap != null) {
+ style.rowGap = grid.rowGap
+ style.columnGap = grid.columnGap
+ } else {
+ if (grid.gap != null) {
+ style.gap = grid.gap
+ }
+ if (grid.rowGap != null) {
+ style.rowGap = grid.rowGap
+ }
+ if (grid.columnGap != null) {
+ style.columnGap = grid.columnGap
+ }
+ }
+
+ return (
+
+ {/* grid lines */}
+
+
+ {placeholders.map((cell) => {
+ const countedRow = Math.floor(cell / grid.columns) + 1
+ 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
+
+ const borderColor =
+ isActiveCell && targetsAreCellsWithPositioning
+ ? colorTheme.brandNeonPink.value
+ : colorTheme.blackOpacity35.value
+ return (
+
+
+ = grid.rows ||
+ (grid.rowGap != null && grid.rowGap > 0)
+ ? gridPlaceholderBorder(borderColor, scale)
+ : undefined,
+ borderRight:
+ isActiveCell ||
+ countedColumn >= grid.columns ||
+ (grid.columnGap != null && grid.columnGap > 0)
+ ? gridPlaceholderBorder(borderColor, scale)
+ : undefined,
+ }}
+ />
+
+
+
+ )
+ })}
+
+ {/* cell targets */}
+ {cells.map((cell) => {
+ return (
+
+ )
+ })}
+ {/* shadow */}
+ {!anyTargetAbsolute &&
+ shadow != null &&
+ initialShadowFrame != null &&
+ interactionData?.dragStart != null &&
+ interactionData?.drag != null &&
+ hoveringStart != null ? (
+
+ ) : null}
+
+
+ )
+})
+GridControl.displayName = 'GridControl'
+
+export const GridDotOverlay = React.memo(({ dotgridColor }: { dotgridColor: string }) => {
+ return (
+
+
+
+
+
+
+
+ )
+})
+
+function gridKeyFromPath(path: ElementPath): string {
+ return `grid-${EP.toString(path)}`
+}
+
+const placeholderBorderBaseWidth = 2
+
+function gridPlaceholderBorder(color: string, scale: number): string {
+ return `${placeholderBorderBaseWidth / scale}px solid ${color}`
+}
+
+function gridPlaceholderTopOrLeftPosition(scale: number): string {
+ return `${-placeholderBorderBaseWidth / scale}px`
+}
+
+function gridPlaceholderWidthOrHeight(scale: number): string {
+ return `calc(100% + ${(placeholderBorderBaseWidth * 2) / scale}px)`
+}
+
+function useCellAnimation(params: {
+ disabled: boolean
+ gridPath: ElementPath | null
+ shadowFrame: CanvasRectangle | null
+ targetRootCell: GridCellCoordinates | null
+ controls: AnimationControls
+}) {
+ const { gridPath, targetRootCell, controls, shadowFrame, disabled } = params
+
+ const [lastTargetRootCellId, setLastTargetRootCellId] = React.useState(targetRootCell)
+ const [lastSnapPoint, setLastSnapPoint] = React.useState
(shadowFrame)
+
+ const selectedViews = useEditorState(
+ Substores.selectedViews,
+ (store) => store.editor.selectedViews,
+ 'useSnapAnimation selectedViews',
+ )
+
+ const animate = useCanvasAnimation(selectedViews)
+
+ const gridMetadata = useEditorState(
+ Substores.metadata,
+ (store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, gridPath),
+ 'useCellAnimation gridMetadata',
+ )
+
+ const moveFromPoint = React.useMemo(() => {
+ return lastSnapPoint ?? shadowFrame
+ }, [lastSnapPoint, shadowFrame])
+
+ const snapPoint = React.useMemo(() => {
+ if (gridMetadata == null || targetRootCell == null) {
+ return null
+ }
+
+ return getGlobalFrameOfGridCell(gridMetadata, targetRootCell)
+ }, [gridMetadata, targetRootCell])
+
+ React.useEffect(() => {
+ if (disabled) {
+ return
+ }
+
+ if (targetRootCell != null && snapPoint != null && moveFromPoint != null) {
+ const snapPointsDiffer = lastSnapPoint == null || !pointsEqual(snapPoint, lastSnapPoint)
+ const hasMovedToANewCell = lastTargetRootCellId != null
+ const shouldAnimate = snapPointsDiffer && hasMovedToANewCell
+ if (shouldAnimate) {
+ void animate(
+ {
+ scale: [0.97, 1.02, 1], // a very subtle boop
+ x: [moveFromPoint.x - snapPoint.x, 0],
+ y: [moveFromPoint.y - snapPoint.y, 0],
+ },
+ {
+ duration: CELL_ANIMATION_DURATION,
+ type: 'tween',
+ ease: 'easeInOut',
+ },
+ )
+ }
+ }
+ setLastSnapPoint(snapPoint)
+ setLastTargetRootCellId(targetRootCell)
+ }, [
+ targetRootCell,
+ controls,
+ lastSnapPoint,
+ snapPoint,
+ animate,
+ moveFromPoint,
+ lastTargetRootCellId,
+ disabled,
+ ])
+}
+
+function useMouseMove(activelyDraggingOrResizingCell: string | null) {
+ const [hoveringStart, setHoveringStart] = React.useState<{
+ point: CanvasPoint
+ } | null>(null)
+ const [mouseCanvasPosition, setMouseCanvasPosition] = React.useState(
+ canvasPoint({ x: 0, y: 0 }),
+ )
+
+ const canvasScale = useEditorState(
+ Substores.canvasOffset,
+ (store) => store.editor.canvas.scale,
+ 'useHoveringCell canvasScale',
+ )
+
+ const canvasOffset = useEditorState(
+ Substores.canvasOffset,
+ (store) => store.editor.canvas.roundedCanvasOffset,
+ 'useHoveringCell canvasOffset',
+ )
+
+ React.useEffect(() => {
+ function handleMouseMove(e: MouseEvent) {
+ if (activelyDraggingOrResizingCell == null) {
+ setHoveringStart(null)
+ return
+ }
+
+ const newMouseCanvasPosition = windowToCanvasCoordinates(
+ canvasScale,
+ canvasOffset,
+ windowPoint({ x: e.clientX, y: e.clientY }),
+ ).canvasPositionRaw
+ setMouseCanvasPosition(newMouseCanvasPosition)
+
+ setHoveringStart((start) => {
+ if (start == null) {
+ return {
+ point: canvasPoint(newMouseCanvasPosition),
+ }
+ }
+ return start
+ })
+ }
+ window.addEventListener('mousemove', handleMouseMove)
+ return function () {
+ window.removeEventListener('mousemove', handleMouseMove)
+ }
+ }, [activelyDraggingOrResizingCell, canvasOffset, canvasScale])
+
+ return { hoveringStart, mouseCanvasPosition }
+}
diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx
index 942665a74b3e..b835aa15a7c6 100644
--- a/editor/src/components/canvas/controls/grid-controls.tsx
+++ b/editor/src/components/canvas/controls/grid-controls.tsx
@@ -99,8 +99,7 @@ import {
} from '../canvas-strategies/strategies/grid-helpers'
import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy'
import { GridControlsRuler } from './grid-controls-ruler'
-
-const CELL_ANIMATION_DURATION = 0.15 // seconds
+import { GridControl } from './grid-controls-ruler-2'
export const GridCellTestId = (elementPath: ElementPath) => `grid-cell-${EP.toString(elementPath)}`
@@ -557,470 +556,6 @@ interface GridRowColumnResizingControlsProps {
export const GridControlsKey = (gridPath: ElementPath) => `grid-controls-${EP.toString(gridPath)}`
-export interface GridControlProps {
- grid: GridData
-}
-
-export const GridControl = React.memo(({ grid }) => {
- const dispatch = useDispatch()
- const controls = useAnimationControls()
- const colorTheme = useColorTheme()
-
- const editorMetadata = useEditorState(
- Substores.metadata,
- (store) => store.editor.jsxMetadata,
- 'GridControl editorMetadata',
- )
-
- const interactionLatestMetadata = useEditorState(
- Substores.canvas,
- (store) =>
- store.editor.canvas.interactionSession?.interactionData.type === 'DRAG'
- ? store.editor.canvas.interactionSession.latestMetadata
- : null,
- 'GridControl interactionLatestMetadata',
- )
-
- const jsxMetadata = React.useMemo(
- () => interactionLatestMetadata ?? editorMetadata,
- [interactionLatestMetadata, editorMetadata],
- )
-
- const activelyDraggingOrResizingCell = useEditorState(
- Substores.canvas,
- (store) =>
- store.editor.canvas.interactionSession != null &&
- store.editor.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE' &&
- store.editor.canvas.interactionSession?.interactionData.type === 'DRAG' &&
- store.editor.canvas.interactionSession?.interactionData.modifiers.cmd !== true &&
- store.editor.canvas.interactionSession?.interactionData.drag != null
- ? store.editor.canvas.interactionSession.activeControl.id
- : null,
- 'GridControl activelyDraggingOrResizingCell',
- )
-
- const currentHoveredCell = useEditorState(
- Substores.canvas,
- (store) => store.editor.canvas.controls.gridControlData?.targetCell ?? null,
- 'GridControl currentHoveredCell',
- )
-
- const targetsAreCellsWithPositioning = useEditorState(
- Substores.metadata,
- (store) =>
- store.editor.selectedViews.every((elementPath) =>
- MetadataUtils.isGridCellWithPositioning(store.editor.jsxMetadata, elementPath),
- ),
- 'GridControl targetsAreCellsWithPositioning',
- )
-
- const anyTargetAbsolute = useEditorState(
- Substores.metadata,
- (store) =>
- store.editor.selectedViews.some((elementPath) =>
- MetadataUtils.isPositionAbsolute(
- MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, elementPath),
- ),
- ),
- 'GridControl anyTargetAbsolute',
- )
-
- const scale = useEditorState(
- Substores.canvas,
- (store) => store.editor.canvas.scale,
- 'GridControl scale',
- )
-
- const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset)
-
- const startInteractionWithUid = React.useCallback(
- (params: { uid: string; row: number; column: number; frame: CanvasRectangle }) =>
- (event: React.MouseEvent) => {
- setInitialShadowFrame(params.frame)
-
- const start = windowToCanvasCoordinates(
- scale,
- canvasOffsetRef.current,
- windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }),
- )
-
- dispatch([
- CanvasActions.createInteractionSession(
- createInteractionViaMouse(
- start.canvasPositionRounded,
- Modifier.modifiersForEvent(event),
- gridCellHandle({ id: params.uid }),
- 'zero-drag-not-permitted',
- ),
- ),
- ])
- },
- [canvasOffsetRef, dispatch, scale],
- )
-
- const cells = React.useMemo(() => {
- const children = MetadataUtils.getChildrenUnordered(jsxMetadata, grid.elementPath)
- return mapDropNulls((cell, index) => {
- if (cell == null || cell.globalFrame == null || !isFiniteRectangle(cell.globalFrame)) {
- return null
- }
- const countedRow = Math.floor(index / grid.columns) + 1
- const countedColumn = Math.floor(index % grid.columns) + 1
-
- const columnFromProps = cell.specialSizeMeasurements.elementGridProperties.gridColumnStart
- const rowFromProps = cell.specialSizeMeasurements.elementGridProperties.gridRowStart
- return {
- elementPath: cell.elementPath,
- globalFrame: cell.globalFrame,
- borderRadius: cell.specialSizeMeasurements.borderRadius,
- column:
- columnFromProps == null
- ? countedColumn
- : isCSSKeyword(columnFromProps)
- ? countedColumn
- : columnFromProps.numericalPosition ?? countedColumn,
- row:
- rowFromProps == null
- ? countedRow
- : isCSSKeyword(rowFromProps)
- ? countedRow
- : rowFromProps.numericalPosition ?? countedRow,
- index: index,
- }
- }, children)
- }, [grid, jsxMetadata])
-
- const dragging = useEditorState(
- Substores.canvas,
- (store) =>
- store.editor.canvas.interactionSession != null &&
- store.editor.canvas.interactionSession.activeControl.type === 'GRID_CELL_HANDLE'
- ? store.editor.canvas.interactionSession.activeControl.id
- : null,
- 'GridControl dragging',
- )
-
- const shadow = React.useMemo(() => {
- return cells.find((cell) => EP.toUid(cell.elementPath) === dragging) ?? null
- }, [cells, dragging])
-
- const [initialShadowFrame, setInitialShadowFrame] = React.useState(
- shadow?.globalFrame ?? null,
- )
-
- const interactionData = useEditorState(
- Substores.canvas,
- (store) =>
- store.editor.canvas.interactionSession?.interactionData.type === 'DRAG'
- ? store.editor.canvas.interactionSession.interactionData
- : null,
- 'GridControl interactionData',
- )
-
- const { hoveringStart } = useMouseMove(activelyDraggingOrResizingCell)
-
- // NOTE: this stuff is meant to be temporary, until we settle on the set of interaction pieces we like.
- // After that, we should get rid of this.
- const shadowPosition = React.useMemo(() => {
- const drag = interactionData?.drag
- const dragStart = interactionData?.dragStart
- if (
- initialShadowFrame == null ||
- interactionData == null ||
- drag == null ||
- dragStart == null ||
- hoveringStart == null ||
- shadow == null
- ) {
- return null
- }
-
- const getCoord = (axis: 'x' | 'y', dimension: 'width' | 'height') => {
- return (
- shadow.globalFrame[axis] +
- drag[axis] -
- (shadow.globalFrame[axis] - dragStart[axis]) -
- shadow.globalFrame[dimension] *
- ((dragStart[axis] - initialShadowFrame[axis]) / initialShadowFrame[dimension])
- )
- }
-
- // make sure the shadow is displayed only inside the grid container bounds
- function wrapCoord(c: number, min: number, max: number, shadowSize: number) {
- return Math.min(Math.max(c, min), max - shadowSize)
- }
-
- return {
- x: wrapCoord(
- getCoord('x', 'width') ?? 0,
- grid.frame.x,
- grid.frame.x + grid.frame.width,
- shadow.globalFrame.width,
- ),
- y: wrapCoord(
- getCoord('y', 'height') ?? 0,
- grid.frame.y,
- grid.frame.y + grid.frame.height,
- shadow.globalFrame.height,
- ),
- }
- }, [
- interactionData,
- initialShadowFrame,
- hoveringStart,
- shadow,
- grid.frame.x,
- grid.frame.width,
- grid.frame.y,
- grid.frame.height,
- ])
-
- const gridPath = optionalMap(EP.parentPath, shadow?.elementPath)
-
- const targetRootCell = useEditorState(
- Substores.canvas,
- (store) => store.editor.canvas.controls.gridControlData?.rootCell ?? null,
- 'GridControl targetRootCell',
- )
-
- useCellAnimation({
- disabled: anyTargetAbsolute,
- targetRootCell: targetRootCell,
- controls: controls,
- shadowFrame: initialShadowFrame,
- gridPath: gridPath,
- })
-
- const placeholders = Array.from(Array(grid.cells).keys())
- let style: CSSProperties = {
- position: 'absolute',
- top: grid.frame.y - 1, // account for border!
- left: grid.frame.x - 1, // account for border!
- width: grid.frame.width,
- height: grid.frame.height,
- display: 'grid',
- gridTemplateColumns: getNullableAutoOrTemplateBaseString(grid.gridTemplateColumns),
- gridTemplateRows: getNullableAutoOrTemplateBaseString(grid.gridTemplateRows),
- backgroundColor:
- activelyDraggingOrResizingCell != null ? colorTheme.primary10.value : 'transparent',
- border: `1px solid ${
- activelyDraggingOrResizingCell != null ? colorTheme.primary.value : 'transparent'
- }`,
- justifyContent: grid.justifyContent ?? 'initial',
- alignContent: grid.alignContent ?? 'initial',
- pointerEvents: 'none',
- padding:
- grid.padding == null
- ? 0
- : `${grid.padding.top}px ${grid.padding.right}px ${grid.padding.bottom}px ${grid.padding.left}px`,
- }
-
- // Gap needs to be set only if the other two are not present or we'll have rendering issues
- // due to how measurements are calculated.
- if (grid.rowGap != null && grid.columnGap != null) {
- style.rowGap = grid.rowGap
- style.columnGap = grid.columnGap
- } else {
- if (grid.gap != null) {
- style.gap = grid.gap
- }
- if (grid.rowGap != null) {
- style.rowGap = grid.rowGap
- }
- if (grid.columnGap != null) {
- style.columnGap = grid.columnGap
- }
- }
-
- return (
-
- {/* grid lines */}
-
- {placeholders.map((cell) => {
- const countedRow = Math.floor(cell / grid.columns) + 1
- 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
-
- const borderColor =
- isActiveCell && targetsAreCellsWithPositioning
- ? colorTheme.brandNeonPink.value
- : colorTheme.blackOpacity35.value
- return (
-
-
- = grid.rows ||
- (grid.rowGap != null && grid.rowGap > 0)
- ? gridPlaceholderBorder(borderColor, scale)
- : undefined,
- borderRight:
- isActiveCell ||
- countedColumn >= grid.columns ||
- (grid.columnGap != null && grid.columnGap > 0)
- ? gridPlaceholderBorder(borderColor, scale)
- : undefined,
- }}
- />
-
-
-
-
-
-
-
-
-
- )
- })}
-
- {/* cell targets */}
- {cells.map((cell) => {
- return (
-
- )
- })}
- {/* shadow */}
- {!anyTargetAbsolute &&
- shadow != null &&
- initialShadowFrame != null &&
- interactionData?.dragStart != null &&
- interactionData?.drag != null &&
- hoveringStart != null ? (
-
- ) : null}
-
- )
-})
-GridControl.displayName = 'GridControl'
-
export interface GridControlsProps {
targets: ElementPath[]
}
@@ -1047,10 +582,10 @@ export const GridControls = controlForStrategyMemoized
(({ tar
return (
+ {grids.map((grid) => {
+ return
+ })}
- {grids.map((grid) => {
- return
- })}
@@ -1358,134 +893,6 @@ const AbsoluteDistanceIndicators = React.memo(
},
)
-function useCellAnimation(params: {
- disabled: boolean
- gridPath: ElementPath | null
- shadowFrame: CanvasRectangle | null
- targetRootCell: GridCellCoordinates | null
- controls: AnimationControls
-}) {
- const { gridPath, targetRootCell, controls, shadowFrame, disabled } = params
-
- const [lastTargetRootCellId, setLastTargetRootCellId] = React.useState(targetRootCell)
- const [lastSnapPoint, setLastSnapPoint] = React.useState(shadowFrame)
-
- const selectedViews = useEditorState(
- Substores.selectedViews,
- (store) => store.editor.selectedViews,
- 'useSnapAnimation selectedViews',
- )
-
- const animate = useCanvasAnimation(selectedViews)
-
- const gridMetadata = useEditorState(
- Substores.metadata,
- (store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, gridPath),
- 'useCellAnimation gridMetadata',
- )
-
- const moveFromPoint = React.useMemo(() => {
- return lastSnapPoint ?? shadowFrame
- }, [lastSnapPoint, shadowFrame])
-
- const snapPoint = React.useMemo(() => {
- if (gridMetadata == null || targetRootCell == null) {
- return null
- }
-
- return getGlobalFrameOfGridCell(gridMetadata, targetRootCell)
- }, [gridMetadata, targetRootCell])
-
- React.useEffect(() => {
- if (disabled) {
- return
- }
-
- if (targetRootCell != null && snapPoint != null && moveFromPoint != null) {
- const snapPointsDiffer = lastSnapPoint == null || !pointsEqual(snapPoint, lastSnapPoint)
- const hasMovedToANewCell = lastTargetRootCellId != null
- const shouldAnimate = snapPointsDiffer && hasMovedToANewCell
- if (shouldAnimate) {
- void animate(
- {
- scale: [0.97, 1.02, 1], // a very subtle boop
- x: [moveFromPoint.x - snapPoint.x, 0],
- y: [moveFromPoint.y - snapPoint.y, 0],
- },
- {
- duration: CELL_ANIMATION_DURATION,
- type: 'tween',
- ease: 'easeInOut',
- },
- )
- }
- }
- setLastSnapPoint(snapPoint)
- setLastTargetRootCellId(targetRootCell)
- }, [
- targetRootCell,
- controls,
- lastSnapPoint,
- snapPoint,
- animate,
- moveFromPoint,
- lastTargetRootCellId,
- disabled,
- ])
-}
-
-function useMouseMove(activelyDraggingOrResizingCell: string | null) {
- const [hoveringStart, setHoveringStart] = React.useState<{
- point: CanvasPoint
- } | null>(null)
- const [mouseCanvasPosition, setMouseCanvasPosition] = React.useState(
- canvasPoint({ x: 0, y: 0 }),
- )
-
- const canvasScale = useEditorState(
- Substores.canvasOffset,
- (store) => store.editor.canvas.scale,
- 'useHoveringCell canvasScale',
- )
-
- const canvasOffset = useEditorState(
- Substores.canvasOffset,
- (store) => store.editor.canvas.roundedCanvasOffset,
- 'useHoveringCell canvasOffset',
- )
-
- React.useEffect(() => {
- function handleMouseMove(e: MouseEvent) {
- if (activelyDraggingOrResizingCell == null) {
- setHoveringStart(null)
- return
- }
-
- const newMouseCanvasPosition = windowToCanvasCoordinates(
- canvasScale,
- canvasOffset,
- windowPoint({ x: e.clientX, y: e.clientY }),
- ).canvasPositionRaw
- setMouseCanvasPosition(newMouseCanvasPosition)
-
- setHoveringStart((start) => {
- if (start == null) {
- return {
- point: canvasPoint(newMouseCanvasPosition),
- }
- }
- return start
- })
- }
- window.addEventListener('mousemove', handleMouseMove)
- return function () {
- window.removeEventListener('mousemove', handleMouseMove)
- }
- }, [activelyDraggingOrResizingCell, canvasOffset, canvasScale])
-
- return { hoveringStart, mouseCanvasPosition }
-}
-
export const GridResizeEdgeTestId = (edge: GridResizeEdge) => `grid-resize-edge-${edge}`
interface GridResizeControlProps {
@@ -1725,24 +1132,6 @@ export function gridEdgeToWidthHeight(
}
}
-function gridKeyFromPath(path: ElementPath): string {
- return `grid-${EP.toString(path)}`
-}
-
-const placeholderBorderBaseWidth = 2
-
-function gridPlaceholderBorder(color: string, scale: number): string {
- return `${placeholderBorderBaseWidth / scale}px solid ${color}`
-}
-
-function gridPlaceholderTopOrLeftPosition(scale: number): string {
- return `${-placeholderBorderBaseWidth / scale}px`
-}
-
-function gridPlaceholderWidthOrHeight(scale: number): string {
- return `calc(100% + ${(placeholderBorderBaseWidth * 2) / scale}px)`
-}
-
export function controlsForGridPlaceholders(
gridPath: ElementPath,
whenToShow: WhenToShowControl = 'always-visible',
From 6ff5cb1b3fae748230f745e5f32a5f313a2c54a9 Mon Sep 17 00:00:00 2001
From: Balazs Bajorics <2226774+balazsbajorics@users.noreply.github.com>
Date: Thu, 10 Oct 2024 15:15:03 +0200
Subject: [PATCH 5/7] adding little marks for the columns and rows
---
.../canvas/controls/grid-controls-ruler-2.tsx | 80 +++++++++++++++++++
.../canvas/controls/grid-controls-ruler.tsx | 45 ++++++-----
2 files changed, 104 insertions(+), 21 deletions(-)
diff --git a/editor/src/components/canvas/controls/grid-controls-ruler-2.tsx b/editor/src/components/canvas/controls/grid-controls-ruler-2.tsx
index 6c7cfb282ed4..2e1ce0eadc13 100644
--- a/editor/src/components/canvas/controls/grid-controls-ruler-2.tsx
+++ b/editor/src/components/canvas/controls/grid-controls-ruler-2.tsx
@@ -392,6 +392,7 @@ export const GridControl = React.memo(({ grid }) => {
return (
{/* grid lines */}
+
{
+ const targetPath = grid.elementPath
+ const metadata = useEditorState(
+ Substores.metadata,
+ (store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, targetPath),
+ 'GridTrackIndicators metadata',
+ )
+ const columnPositions = mapDropNulls(
+ (cell) => ({ start: cell.x, length: cell.width }),
+ metadata?.specialSizeMeasurements.gridCellGlobalFrames?.[0] ?? [], // this is the first row
+ )
+ const rowPositions = mapDropNulls(
+ (row) => ({ start: row[0].y, length: row[0].height }), // this is the first column
+ metadata?.specialSizeMeasurements.gridCellGlobalFrames ?? [],
+ )
+
+ return (
+
+
+ {columnPositions.map((column, i) => {
+ return (
+
+
+
+
+ )
+ })}
+
+
+ {rowPositions.map((row, i) => {
+ return (
+
+
+
+
+ )
+ })}
+
+
+ )
+})
+GridTrackIndicators.displayName = 'GridTrackIndicators'
diff --git a/editor/src/components/canvas/controls/grid-controls-ruler.tsx b/editor/src/components/canvas/controls/grid-controls-ruler.tsx
index a6c2ea39d98f..a1b69d614dca 100644
--- a/editor/src/components/canvas/controls/grid-controls-ruler.tsx
+++ b/editor/src/components/canvas/controls/grid-controls-ruler.tsx
@@ -118,7 +118,30 @@ export const GridControlsRuler = React.memo(() => {
if (selectedElement == null) {
return null
}
- return
{/* */}
+ return (
+
+
+
+
+ )
})
interface GridRowColumnResizingControlsProps {
@@ -181,26 +204,6 @@ export const GridRowColumnResizingControls =
return (
-
-
{gridsWithVisibleResizeControls.flatMap((grid) => {
return (
From a4eee8edd3c1455e552606f3436858be7e71712b Mon Sep 17 00:00:00 2001
From: Balazs Bajorics <2226774+balazsbajorics@users.noreply.github.com>
Date: Thu, 10 Oct 2024 16:43:14 +0200
Subject: [PATCH 6/7] wip
---
.../strategies/set-grid-gap-strategy.tsx | 1 +
.../controls/select-mode/grid-gap-control.tsx | 128 ++++++++----------
editor/src/components/canvas/gap-utils.ts | 14 +-
3 files changed, 61 insertions(+), 82 deletions(-)
diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx
index 484d19455ef5..f2be4ea58db0 100644
--- a/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx
+++ b/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx
@@ -115,6 +115,7 @@ export const setGridGapStrategy: CanvasStrategyFactory = (
},
key: 'grid-gap-resize-control',
show: 'visible-except-when-other-strategy-is-active',
+ priority: 'top',
})
const maybeIndicatorProps = gridGapValueIndicatorProps(interactionSession, gridGap)
diff --git a/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx b/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx
index adca71944cc3..a053aae84524 100644
--- a/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx
+++ b/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx
@@ -134,21 +134,6 @@ export const GridGapControl = controlForStrategyMemoized((p
return null
}
- const controlRef = useBoundingBox(
- [selectedElement],
- (ref, safeGappedBoundingBox, realBoundingBox) => {
- if (isZeroSizedElement(realBoundingBox)) {
- ref.current.style.display = 'none'
- } else {
- ref.current.style.display = 'block'
- ref.current.style.left = safeGappedBoundingBox.x + 'px'
- ref.current.style.top = safeGappedBoundingBox.y + 'px'
- ref.current.style.width = safeGappedBoundingBox.width + 'px'
- ref.current.style.height = safeGappedBoundingBox.height + 'px'
- }
- },
- )
-
const gridGapRow = updatedGapValueRow ?? gridGap.row
const gridGapColumn = updatedGapValueColumn ?? gridGap.column
@@ -160,59 +145,58 @@ export const GridGapControl = controlForStrategyMemoized((p
})
return (
-
-
- {controlBounds.gaps.map(({ gap, bounds, axis, gapId }) => {
- const gapControlProps = {
- mouseDownHandler: axisMouseDownHandler,
- gapId: gapId,
- bounds: bounds,
- accentColor: accentColor,
- scale: scale,
- isDragging: isDragging,
- axis: axis,
- gapValue: gap,
- internalGrid: {
- gridTemplateRows: controlBounds.gridTemplateRows,
- gridTemplateColumns: controlBounds.gridTemplateColumns,
- gap: axis === 'row' ? controlBounds.gapValues.column : controlBounds.gapValues.row,
- },
- elementHovered: elementHovered,
- handles: axis === 'row' ? controlBounds.columns : controlBounds.rows,
- }
- if (axis === 'row') {
- return (
-
- )
- }
- return (
-
- )
- })}
-
-
+
+ {controlBounds.gaps.map(({ gap, bounds, axis, gapId }) => {
+ const gapControlProps = {
+ mouseDownHandler: axisMouseDownHandler,
+ gapId: gapId,
+ bounds: bounds,
+ accentColor: accentColor,
+ scale: scale,
+ isDragging: isDragging,
+ axis: axis,
+ gapValue: gap,
+ internalGrid: {
+ gridTemplateRows: controlBounds.gridTemplateRows,
+ gridTemplateColumns: controlBounds.gridTemplateColumns,
+ gap: axis === 'row' ? controlBounds.gapValues.column : controlBounds.gapValues.row,
+ },
+ elementHovered: elementHovered,
+ handles: axis === 'row' ? controlBounds.columns : controlBounds.rows,
+ }
+ return (
+
+ {axis === 'row' ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ )
+ })}
+
)
})
@@ -325,8 +309,8 @@ const GapControlSegment = React.memo((props) => {
style={{
pointerEvents: 'all',
position: 'absolute',
- left: bounds.x,
- top: bounds.y,
+ left: axis === 'row' ? 0 : bounds.x,
+ top: axis === 'row' ? bounds.y : 0,
width: bounds.width,
height: bounds.height,
display: 'flex',
@@ -402,10 +386,10 @@ function GridGapHandle({
)
const colorTheme = useColorTheme()
const shouldShowIndicator = !isDragging && indicatorShown === index
- let shouldShowHandle = !isDragging && gapIsHovered
+ let shouldShowHandle = true // !isDragging && gapIsHovered
// show the handle also if the gap is too narrow to hover
if (!gapIsHovered && !backgroundShown) {
- shouldShowHandle = elementHovered && gapValue.value <= GapHandleGapWidthThreshold
+ shouldShowHandle = true // elementHovered && gapValue.value <= GapHandleGapWidthThreshold
}
const handleOpacity = gapIsHovered ? 1 : 0.3
diff --git a/editor/src/components/canvas/gap-utils.ts b/editor/src/components/canvas/gap-utils.ts
index a8cbe955a64c..7eabf7d19dd0 100644
--- a/editor/src/components/canvas/gap-utils.ts
+++ b/editor/src/components/canvas/gap-utils.ts
@@ -207,12 +207,6 @@ export function gridGapControlBoundsFromMetadata(
return emptyResult
}
- const parentGridBounds = grid.globalFrame
-
- if (parentGridBounds == null || isInfinityRectangle(parentGridBounds)) {
- return emptyResult
- }
-
const gridRows = gridRowColumnInfo.rows
const gridColumns = gridRowColumnInfo.columns
const gridTemplateRows = getNullableAutoOrTemplateBaseString(gridRowColumnInfo.gridTemplateRows)
@@ -226,8 +220,8 @@ export function gridGapControlBoundsFromMetadata(
return emptyResult
}
const allCellsBound = canvasRectangle({
- x: gridCellBounds[0][0].x - parentGridBounds.x,
- y: gridCellBounds[0][0].y - parentGridBounds.y,
+ x: gridCellBounds[0][0].x,
+ y: gridCellBounds[0][0].y,
width:
gridCellBounds[0][gridCellBounds[0].length - 1].x +
gridCellBounds[0][gridCellBounds[0].length - 1].width -
@@ -247,7 +241,7 @@ export function gridGapControlBoundsFromMetadata(
gapId: `${EP.toString(grid.elementPath)}-row-gap-${i}`,
bounds: canvasRectangle({
x: allCellsBound.x,
- y: firstChildBounds.y + firstChildBounds.height - parentGridBounds.y,
+ y: firstChildBounds.y + firstChildBounds.height,
width: allCellsBound.width,
height: secondChildBounds.y - firstChildBounds.y - firstChildBounds.height,
}),
@@ -264,7 +258,7 @@ export function gridGapControlBoundsFromMetadata(
return {
gapId: `${EP.toString(grid.elementPath)}-column-gap-${i}`,
bounds: canvasRectangle({
- x: firstChildBounds.x + firstChildBounds.width - parentGridBounds.x,
+ x: firstChildBounds.x + firstChildBounds.width,
y: allCellsBound.y,
width: secondChildBounds.x - firstChildBounds.x - firstChildBounds.width,
height: allCellsBound.height,
From 2aaaef8549e82dc9f552e9e2cec9ec83edb7cb18 Mon Sep 17 00:00:00 2001
From: Balazs Bajorics <2226774+balazsbajorics@users.noreply.github.com>
Date: Thu, 10 Oct 2024 16:58:45 +0200
Subject: [PATCH 7/7] gap in ruler
---
.../controls/select-mode/grid-gap-control.tsx | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx b/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx
index a053aae84524..86d13f5c1d66 100644
--- a/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx
+++ b/editor/src/components/canvas/controls/select-mode/grid-gap-control.tsx
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import type { CanvasRectangle, CanvasVector, Size } from '../../../../core/shared/math-utils'
-import { size, windowPoint } from '../../../../core/shared/math-utils'
+import { canvasRectangle, size, windowPoint } from '../../../../core/shared/math-utils'
import type { ElementPath } from '../../../../core/shared/project-file-types'
import { assertNever } from '../../../../core/shared/utils'
import { Modifier } from '../../../../utils/modifiers'
@@ -25,6 +25,7 @@ import { useBoundingBox } from '../bounding-box-hooks'
import { isZeroSizedElement } from '../outline-utils'
import { createArrayWithLength } from '../../../../core/shared/array-utils'
import { useGridData } from '../grid-controls'
+import { x } from 'tar'
export interface GridGapControlProps {
selectedElement: ElementPath
@@ -147,10 +148,16 @@ export const GridGapControl = controlForStrategyMemoized((p
return (
{controlBounds.gaps.map(({ gap, bounds, axis, gapId }) => {
+ const boundsFixed = canvasRectangle({
+ x: axis === 'row' ? 0 : bounds.x,
+ y: axis === 'row' ? bounds.y : 0,
+ width: axis === 'row' ? 20 : bounds.width,
+ height: axis === 'row' ? bounds.height : 20,
+ })
const gapControlProps = {
mouseDownHandler: axisMouseDownHandler,
gapId: gapId,
- bounds: bounds,
+ bounds: boundsFixed,
accentColor: accentColor,
scale: scale,
isDragging: isDragging,
@@ -309,8 +316,8 @@ const GapControlSegment = React.memo((props) => {
style={{
pointerEvents: 'all',
position: 'absolute',
- left: axis === 'row' ? 0 : bounds.x,
- top: axis === 'row' ? bounds.y : 0,
+ left: bounds.x,
+ top: bounds.y,
width: bounds.width,
height: bounds.height,
display: 'flex',
@@ -330,12 +337,10 @@ const GapControlSegment = React.memo((props) => {
justifyContent: 'center',
placeItems: 'center',
gap: internalGrid.gap.value,
- gridTemplateColumns: axis === 'row' ? internalGrid.gridTemplateColumns : '1fr',
- gridTemplateRows: axis === 'column' ? internalGrid.gridTemplateRows : '1fr',
position: 'relative',
}}
>
- {createArrayWithLength(handles, (i) => (
+ {createArrayWithLength(1, (i) => (