Skip to content

Commit 9d74ac4

Browse files
Grid Gap Control 2 (#6538)
**Problem:** The orange grid gap controls are sometimes 1px off, they are one frame behind, they show weird delays when the canvas zooms. **Fix:** A lot of the problem came down to `gridGapControlBoundsFromMetadata` trying to calculate the bounding boxes of where the gaps are in the selected Grid. However this is error prone because a lot of these boxes will fall on fractional pixel values, and we were not rounding them the same way Chrome was rounding the real grid. Instead of trying to fix the math, I had an idea to replicate our hack for the grid cell outline controls: take the ComputedStyle.gridTemplateRows / Columns, and create a helper grid which has the correct position and sizing for our gap outlines. The trick is that we want to take the original grid's gap value, and turn those into grid tracks too, setting the helper grid's gap to zero. So if my original grid's ComputedStyle has tracks [5px 10px 15px 5px] and a gap of 13px, we would create a helper grid with the tracks [5px 13px 10px 13px 15px 13px 5px]! Now the only job is to fill it with the elements and make sure to only draw anything in the tracks that correspond to gaps in the original grid (they are always the odd numbered tracks). This means we can offload the entire positioning and rendering to Chrome, and makes our life much simpler going forward. **Commit Details:** - Fixed Hot Reload by creating a new file `grid-gap-control-component.tsx` - Brand new `GridGapControlComponent`, `GridPaddingOutlineForDimension` and `GridRowHighlight` components. - `GridPaddingOutlineForDimension` creates the helper grid with the inlined gaps as tracks - `GridRowHighlight` is responsible for the visual lines, and it recreates the original grid in the given dimension so it can have a handle for each original row - `GridGapHandle` mostly unchanged --------- Co-authored-by: Federico Ruggi <1081051+ruggi@users.noreply.github.com>
1 parent 66fc4c8 commit 9d74ac4

File tree

9 files changed

+597
-558
lines changed

9 files changed

+597
-558
lines changed

editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
import type { InteractionSession } from '../interaction-state'
4141
import { colorTheme } from '../../../../uuiui'
4242
import { activeFrameTargetPath, setActiveFrames } from '../../commands/set-active-frames-command'
43-
import type { GridGapControlProps } from '../../controls/select-mode/grid-gap-control'
43+
import type { GridGapControlProps } from '../../controls/select-mode/grid-gap-control-component'
4444
import { GridGapControl } from '../../controls/select-mode/grid-gap-control'
4545
import type { GridControlsProps } from '../../controls/grid-controls-for-strategies'
4646
import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { CSSProperties } from 'react'
2+
import type { GridMeasurementHelperData } from './grid-controls-for-strategies'
3+
import { getNullableAutoOrTemplateBaseString } from './grid-controls-for-strategies'
4+
5+
export function getGridHelperStyleMatchingTargetGrid(
6+
grid: GridMeasurementHelperData,
7+
): CSSProperties {
8+
let style: CSSProperties = {
9+
position: 'absolute',
10+
top: grid.frame.y,
11+
left: grid.frame.x,
12+
width: grid.frame.width,
13+
height: grid.frame.height,
14+
display: 'grid',
15+
gridTemplateColumns: getNullableAutoOrTemplateBaseString(grid.gridTemplateColumns),
16+
gridTemplateRows: getNullableAutoOrTemplateBaseString(grid.gridTemplateRows),
17+
justifyContent: grid.justifyContent ?? 'initial',
18+
alignContent: grid.alignContent ?? 'initial',
19+
pointerEvents: 'none',
20+
padding:
21+
grid.padding == null
22+
? 0
23+
: `${grid.padding.top}px ${grid.padding.right}px ${grid.padding.bottom}px ${grid.padding.left}px`,
24+
}
25+
26+
// Gap needs to be set only if the other two are not present or we'll have rendering issues
27+
// due to how measurements are calculated.
28+
if (grid.rowGap != null && grid.columnGap != null) {
29+
style.rowGap = grid.rowGap
30+
style.columnGap = grid.columnGap
31+
} else {
32+
if (grid.gap != null) {
33+
style.gap = grid.gap
34+
}
35+
if (grid.rowGap != null) {
36+
style.rowGap = grid.rowGap
37+
}
38+
if (grid.columnGap != null) {
39+
style.columnGap = grid.columnGap
40+
}
41+
}
42+
43+
return style
44+
}

editor/src/components/canvas/controls/grid-controls.tsx

+5-43
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ import {
9292
} from './grid-controls-for-strategies'
9393
import { useMaybeHighlightElement } from './select-mode/select-mode-hooks'
9494
import { useResizeEdges } from './select-mode/use-resize-edges'
95+
import { getGridHelperStyleMatchingTargetGrid } from './grid-controls-helpers'
9596

9697
const CELL_ANIMATION_DURATION = 0.15 // seconds
9798

@@ -774,9 +775,9 @@ const GridControl = React.memo<GridControlProps>(({ grid }) => {
774775
})
775776

776777
const placeholders = range(0, grid.cells)
777-
778-
const style: CSSProperties = {
779-
...getGridControlBaseStyle(grid),
778+
const baseStyle = getGridHelperStyleMatchingTargetGrid(grid)
779+
const style = {
780+
...baseStyle,
780781
backgroundColor:
781782
activelyDraggingOrResizingCell != null ? colorTheme.primary10.value : 'transparent',
782783
outline: `1px solid ${
@@ -940,7 +941,7 @@ const GridMeasurementHelper = React.memo<{ elementPath: ElementPath }>(({ elemen
940941
const placeholders = range(0, gridData.cells)
941942

942943
const style: CSSProperties = {
943-
...getGridControlBaseStyle(gridData),
944+
...getGridHelperStyleMatchingTargetGrid(gridData),
944945
opacity: 1,
945946
}
946947

@@ -1643,42 +1644,3 @@ function useAllGrids(metadata: ElementInstanceMetadataMap) {
16431644
return MetadataUtils.getAllGrids(metadata)
16441645
}, [metadata])
16451646
}
1646-
1647-
function getGridControlBaseStyle(gridData: GridMeasurementHelperData) {
1648-
let style: CSSProperties = {
1649-
position: 'absolute',
1650-
top: gridData.frame.y,
1651-
left: gridData.frame.x,
1652-
width: gridData.frame.width,
1653-
height: gridData.frame.height,
1654-
display: 'grid',
1655-
gridTemplateColumns: getNullableAutoOrTemplateBaseString(gridData.gridTemplateColumns),
1656-
gridTemplateRows: getNullableAutoOrTemplateBaseString(gridData.gridTemplateRows),
1657-
justifyContent: gridData.justifyContent ?? 'initial',
1658-
alignContent: gridData.alignContent ?? 'initial',
1659-
pointerEvents: 'none',
1660-
padding:
1661-
gridData.padding == null
1662-
? 0
1663-
: `${gridData.padding.top}px ${gridData.padding.right}px ${gridData.padding.bottom}px ${gridData.padding.left}px`,
1664-
}
1665-
1666-
// Gap needs to be set only if the other two are not present or we'll have rendering issues
1667-
// due to how measurements are calculated.
1668-
if (gridData.rowGap != null && gridData.columnGap != null) {
1669-
style.rowGap = gridData.rowGap
1670-
style.columnGap = gridData.columnGap
1671-
} else {
1672-
if (gridData.gap != null) {
1673-
style.gap = gridData.gap
1674-
}
1675-
if (gridData.rowGap != null) {
1676-
style.rowGap = gridData.rowGap
1677-
}
1678-
if (gridData.columnGap != null) {
1679-
style.columnGap = gridData.columnGap
1680-
}
1681-
}
1682-
1683-
return style
1684-
}

editor/src/components/canvas/controls/select-mode/controls-common.tsx

+19-10
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,26 @@ export function useHoverWithDelay(
168168
): [React.MouseEventHandler, React.MouseEventHandler] {
169169
const fadeInTimeout = React.useRef<Timeout | null>(null)
170170

171-
const onHoverEnd = () => {
172-
if (fadeInTimeout.current != null) {
173-
clearTimeout(fadeInTimeout.current)
174-
}
175-
fadeInTimeout.current = null
176-
update(false)
177-
}
171+
const onHoverEnd = React.useCallback(
172+
(e: React.MouseEvent) => {
173+
if (fadeInTimeout.current != null) {
174+
clearTimeout(fadeInTimeout.current)
175+
}
176+
fadeInTimeout.current = null
177+
update(false)
178+
},
179+
[update],
180+
)
178181

179-
const onHoverStart = () => {
180-
fadeInTimeout.current = setTimeout(() => update(true), delay)
181-
}
182+
const onHoverStart = React.useCallback(
183+
(e: React.MouseEvent) => {
184+
if (fadeInTimeout.current != null) {
185+
clearTimeout(fadeInTimeout.current)
186+
}
187+
fadeInTimeout.current = setTimeout(() => update(true), delay)
188+
},
189+
[update, delay],
190+
)
182191

183192
return [onHoverStart, onHoverEnd]
184193
}

editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ const GapControlSegment = React.memo<GapControlSegmentProps>((props) => {
427427

428428
function handleDimensions(flexDirection: FlexDirection, scale: number): Size {
429429
if (flexDirection === 'row' || flexDirection === 'row-reverse') {
430-
return size(3 / scale, 12 / scale)
430+
return size(4 / scale, 12 / scale)
431431
}
432432
if (flexDirection === 'column' || flexDirection === 'column-reverse') {
433433
return size(12 / scale, 4 / scale)

0 commit comments

Comments
 (0)