Skip to content

Commit

Permalink
refactor(grid) Rework SubduedGridGapControl (#6564)
Browse files Browse the repository at this point in the history
- Removed a lot of the contents of `SubduedGridGapControl` and replace
the main contents of the rendering with
`GridPaddingOutlineForDimension`.
- Added `draggedOutlineColor` to `GridPaddingOutlineForDimension` and
`GridRowHighlight`.
- Removed `gridGapControlBoundsFromMetadata`.
  • Loading branch information
seanparsons authored Oct 18, 2024
1 parent f9cea56 commit a53ecb5
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { ElementPath } from '../../../../core/shared/project-file-types'
import { assertNever } from '../../../../core/shared/utils'
import { Modifier } from '../../../../utils/modifiers'
import { when } from '../../../../utils/react-conditionals'
import type { UtopiColor } from '../../../../uuiui'
import { useColorTheme, UtopiaStyles } from '../../../../uuiui'
import { CSSCursor } from '../../../../uuiui-deps'
import type { EditorDispatch } from '../../../editor/action-types'
Expand Down Expand Up @@ -139,7 +140,7 @@ export const GridGapControlComponent = React.memo<GridGapControlProps>((props) =
)
})

const GridPaddingOutlineForDimension = (props: {
export const GridPaddingOutlineForDimension = (props: {
grid: GridData
dimension: 'rows' | 'columns'
onMouseDown: (e: React.MouseEvent<HTMLDivElement>) => void
Expand All @@ -148,6 +149,7 @@ const GridPaddingOutlineForDimension = (props: {
zIndexPriority: boolean
gridGap: CSSNumberWithRenderedValue
elementHovered: boolean
draggedOutlineColor?: UtopiColor
}) => {
const {
grid,
Expand All @@ -158,6 +160,7 @@ const GridPaddingOutlineForDimension = (props: {
onMouseOver,
zIndexPriority,
elementHovered,
draggedOutlineColor,
} = props

let style: CSSProperties = {
Expand Down Expand Up @@ -199,6 +202,7 @@ const GridPaddingOutlineForDimension = (props: {
beingDragged={beingDragged}
onMouseOver={onMouseOver}
elementHovered={elementHovered}
draggedOutlineColor={draggedOutlineColor}
/>
)
})}
Expand All @@ -218,6 +222,7 @@ const GridRowHighlight = (props: {
beingDragged: boolean
onMouseOver: () => void
elementHovered: boolean
draggedOutlineColor?: UtopiColor
}) => {
const {
gapId,
Expand All @@ -231,6 +236,7 @@ const GridRowHighlight = (props: {
beingDragged,
onMouseOver,
elementHovered,
draggedOutlineColor,
} = props

const colorTheme = useColorTheme()
Expand All @@ -242,7 +248,9 @@ const GridRowHighlight = (props: {

const lineWidth = 1 / canvasScale

const outlineColor = beingDragged ? colorTheme.brandNeonOrange.value : 'transparent'
const outlineColor = beingDragged
? (draggedOutlineColor ?? colorTheme.brandNeonOrange).value
: 'transparent'

const [backgroundShown, setBackgroundShown] = React.useState<boolean>(false)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React from 'react'
import { useColorTheme } from '../../../../uuiui'
import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook'
import { useBoundingBox } from '../bounding-box-hooks'
import { Substores, useEditorState } from '../../../editor/store/store-hook'
import { CanvasOffsetWrapper } from '../canvas-offset-wrapper'
import type { Axis } from '../../gap-utils'
import { gridGapControlBoundsFromMetadata, maybeGridGapData } from '../../gap-utils'
import type { ElementPath } from 'utopia-shared/src/types'
import { useGridData } from '../grid-controls-for-strategies'
import { fallbackEmptyValue } from './controls-common'
import type { CanvasRectangle } from '../../../../core/shared/math-utils'
import type { CSSNumber } from '../../../../components/inspector/common/css-utils'
import { unitlessCSSNumberWithRenderedValue } from './controls-common'
import { NO_OP } from '../../../../core/shared/utils'
import * as EP from '../../../../core/shared/element-path'
import { GridPaddingOutlineForDimension } from './grid-gap-control-component'

export interface SubduedGridGapControlProps {
hoveredOrFocused: 'hovered' | 'focused'
Expand All @@ -18,124 +16,49 @@ export interface SubduedGridGapControlProps {

export const SubduedGridGapControl = React.memo<SubduedGridGapControlProps>((props) => {
const { hoveredOrFocused, axis } = props
const colorTheme = useColorTheme()
const targets = useEditorState(
Substores.selectedViews,
(store) => store.editor.selectedViews,
'SubduedGridGapControl selectedViews',
)

const metadata = useRefEditorState((store) => store.editor.jsxMetadata)
const selectedElement = targets.at(0)

const gridRowColumnInfo = useGridData(targets)
const selectedGrid = gridRowColumnInfo.at(0)

const filteredGaps = React.useMemo(() => {
if (selectedElement == null || selectedGrid == null) {
return []
}
const gridGap = maybeGridGapData(metadata.current, selectedElement)
if (gridGap == null) {
return []
}

const gridGapRow = gridGap.row
const gridGapColumn = gridGap.column

const controlBounds = gridGapControlBoundsFromMetadata(selectedGrid, {
row: fallbackEmptyValue(gridGapRow),
column: fallbackEmptyValue(gridGapColumn),
})
return controlBounds.gaps.filter((gap) => gap.axis === axis || axis === 'both')
}, [axis, metadata, selectedElement, selectedGrid])

if (filteredGaps.length === 0 || selectedElement == null) {
if (gridRowColumnInfo.length === 0) {
return null
}

return (
<>
{filteredGaps.map((gap) => (
<GridGapControl
key={gap.gapId}
targets={targets}
selectedElement={selectedElement}
hoveredOrFocused={hoveredOrFocused}
gap={gap}
/>
))}
</>
)
})

function GridGapControl({
targets,
selectedElement,
hoveredOrFocused,
gap,
}: {
targets: Array<ElementPath>
selectedElement: ElementPath
hoveredOrFocused: 'hovered' | 'focused'
gap: {
bounds: CanvasRectangle
gapId: string
gap: CSSNumber
axis: Axis
}
}) {
const metadata = useRefEditorState((store) => store.editor.jsxMetadata)
const scale = useEditorState(
Substores.canvas,
(store) => store.editor.canvas.scale,
'GridGapControl scale',
)
const gridRowColumnInfo = useGridData([selectedElement])

const sideRef = useBoundingBox([selectedElement], (ref, parentBoundingBox) => {
const gridGap = maybeGridGapData(metadata.current, selectedElement)
const selectedGrid = gridRowColumnInfo.at(0)
if (gridGap == null || selectedGrid == null) {
return
}

const controlBounds = gridGapControlBoundsFromMetadata(selectedGrid, {
row: fallbackEmptyValue(gridGap.row),
column: fallbackEmptyValue(gridGap.column),
})

const bound = controlBounds.gaps.find((updatedGap) => updatedGap.gapId === gap.gapId)
if (bound == null) {
return
}

ref.current.style.display = 'block'
ref.current.style.left = `${bound.bounds.x + parentBoundingBox.x}px`
ref.current.style.top = `${bound.bounds.y + parentBoundingBox.y}px`
ref.current.style.height = numberToPxValue(bound.bounds.height)
ref.current.style.width = numberToPxValue(bound.bounds.width)
})

const color = useColorTheme().brandNeonPink.value

const solidOrDashed = hoveredOrFocused === 'focused' ? 'solid' : 'dashed'

return (
<CanvasOffsetWrapper>
<div
ref={sideRef}
style={{
position: 'absolute',
border: `1px ${solidOrDashed} ${color}`,
}}
data-testid={getSubduedGridGaplTestID(hoveredOrFocused)}
/>
{gridRowColumnInfo.map((gridData) => {
return (
<React.Fragment key={`grid-gap-${EP.toString(gridData.elementPath)}`}>
<GridPaddingOutlineForDimension
grid={gridData}
dimension={'rows'}
onMouseDown={NO_OP}
beingDragged={true}
onMouseOver={NO_OP}
zIndexPriority={false}
gridGap={unitlessCSSNumberWithRenderedValue(gridData.rowGap ?? 0)}
elementHovered={hoveredOrFocused === 'hovered' && axis === 'row'}
draggedOutlineColor={colorTheme.brandNeonPink}
/>
<GridPaddingOutlineForDimension
grid={gridData}
dimension={'columns'}
onMouseDown={NO_OP}
beingDragged={true}
onMouseOver={NO_OP}
zIndexPriority={false}
gridGap={unitlessCSSNumberWithRenderedValue(gridData.columnGap ?? 0)}
elementHovered={hoveredOrFocused === 'hovered' && axis === 'column'}
draggedOutlineColor={colorTheme.brandNeonPink}
/>
</React.Fragment>
)
})}
</CanvasOffsetWrapper>
)
}

export function getSubduedGridGaplTestID(hoveredOrFocused: 'hovered' | 'focused'): string {
return `SubduedGridGapControl-${hoveredOrFocused}`
}

const numberToPxValue = (n: number) => n + 'px'
})
110 changes: 0 additions & 110 deletions editor/src/components/canvas/gap-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,116 +176,6 @@ export function gapControlBoundsFromMetadata(
)
}

export function gridGapControlBoundsFromMetadata(
gridRowColumnInfo: GridData,
gapValues: { row: CSSNumber; column: CSSNumber },
): {
gaps: Array<{
bounds: CanvasRectangle
gapId: string
gap: CSSNumber
axis: Axis
}>
rows: number
columns: number
cellBounds: CanvasRectangle
gapValues: { row: CSSNumber; column: CSSNumber }
gridTemplateRows: string
gridTemplateColumns: string
} {
const emptyResult = {
rows: 0,
columns: 0,
gaps: [],
cellBounds: canvasRectangle({ x: 0, y: 0, width: 0, height: 0 }),
gapValues: gapValues,
gridTemplateRows: '1fr',
gridTemplateColumns: '1fr',
}
const grid = gridRowColumnInfo.metadata

if (grid == null) {
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)
const gridTemplateColumns = getNullableAutoOrTemplateBaseString(
gridRowColumnInfo.gridTemplateColumns,
)

const gridCellBounds = grid.specialSizeMeasurements.gridCellGlobalFrames

if (gridCellBounds == null || gridCellBounds.length == 0) {
return emptyResult
}
const allCellsBound = canvasRectangle({
x: gridCellBounds[0][0].x - parentGridBounds.x,
y: gridCellBounds[0][0].y - parentGridBounds.y,
width:
gridCellBounds[0][gridCellBounds[0].length - 1].x +
gridCellBounds[0][gridCellBounds[0].length - 1].width -
gridCellBounds[0][0].x,
height:
gridCellBounds[gridCellBounds.length - 1][0].y +
gridCellBounds[gridCellBounds.length - 1][0].height -
gridCellBounds[0][0].y,
})

// row gaps array
const rowGaps = createArrayWithLength(gridCellBounds.length - 1, (i) => {
// cell i represents the gap between child [i * gridColumns] and child [(i+1) * gridColumns]
const firstChildBounds = gridCellBounds[i][0]
const secondChildBounds = gridCellBounds[i + 1][0]
return {
gapId: `${EP.toString(grid.elementPath)}-row-gap-${i}`,
bounds: canvasRectangle({
x: allCellsBound.x,
y: firstChildBounds.y + firstChildBounds.height - parentGridBounds.y,
width: allCellsBound.width,
height: secondChildBounds.y - firstChildBounds.y - firstChildBounds.height,
}),
gap: gapValues.row,
axis: 'row' as Axis,
}
})

// column gaps array
const columnGaps = createArrayWithLength(gridCellBounds[0].length - 1, (i) => {
// cell i represents the gap between child [i] and child [i + 1]
const firstChildBounds = gridCellBounds[0][i]
const secondChildBounds = gridCellBounds[0][i + 1]
return {
gapId: `${EP.toString(grid.elementPath)}-column-gap-${i}`,
bounds: canvasRectangle({
x: firstChildBounds.x + firstChildBounds.width - parentGridBounds.x,
y: allCellsBound.y,
width: secondChildBounds.x - firstChildBounds.x - firstChildBounds.width,
height: allCellsBound.height,
}),
gap: gapValues.column,
axis: 'column' as Axis,
}
})

return {
gaps: rowGaps.concat(columnGaps),
rows: gridRows,
columns: gridColumns,
gridTemplateRows: gridTemplateRows ?? '',
gridTemplateColumns: gridTemplateColumns ?? '',
cellBounds: allCellsBound,
gapValues: gapValues,
}
}

export interface GridGapData {
row: CSSNumberWithRenderedValue
column: CSSNumberWithRenderedValue
Expand Down

0 comments on commit a53ecb5

Please sign in to comment.