Skip to content

Commit

Permalink
tweak cell resize handles
Browse files Browse the repository at this point in the history
  • Loading branch information
ruggi committed Jul 15, 2024
1 parent bc45f62 commit ccf3189
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,22 @@ export function gridCellHandle(params: { id: string }): GridCellHandle {
export const GridResizeEdges = ['row-start', 'row-end', 'column-start', 'column-end'] as const
export type GridResizeEdge = (typeof GridResizeEdges)[number]

export type GridResizeEdgeProperties = {
isRow: boolean
isColumn: boolean
isStart: boolean
isEnd: boolean
}

export function gridResizeEdgeProperties(edge: GridResizeEdge): GridResizeEdgeProperties {
return {
isRow: edge === 'row-start' || edge === 'row-end',
isColumn: edge === 'column-start' || edge === 'column-end',
isStart: edge === 'row-start' || edge === 'column-start',
isEnd: edge === 'row-end' || edge === 'column-end',
}
}

export interface GridResizeHandle {
type: 'GRID_RESIZE_HANDLE'
id: string
Expand Down
151 changes: 82 additions & 69 deletions editor/src/components/canvas/controls/grid-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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 { ElementPath } from 'utopia-shared/src/types'
import type { GridCSSNumber } from '../../../components/inspector/common/css-utils'
Expand Down Expand Up @@ -44,12 +45,16 @@ import { Substores, useEditorState, useRefEditorState } from '../../editor/store
import { useRollYourOwnFeatures } from '../../navigator/left-pane/roll-your-own-pane'
import CanvasActions from '../canvas-actions'
import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy-types'
import type { GridResizeEdge } from '../canvas-strategies/interaction-state'
import type {
GridResizeEdge,
GridResizeEdgeProperties,
} from '../canvas-strategies/interaction-state'
import {
GridResizeEdges,
createInteractionViaMouse,
gridAxisHandle,
gridCellHandle,
gridResizeEdgeProperties,
gridResizeHandle,
} from '../canvas-strategies/interaction-state'
import { windowToCanvasCoordinates } from '../dom-lookup'
Expand Down Expand Up @@ -1054,9 +1059,12 @@ export const GridResizeControls = controlForStrategyMemoized<GridResizeControlPr

const isResizing = bounds != null

const [resizingEdge, setResizingEdge] = React.useState<GridResizeEdge | null>(null)

const onMouseUp = React.useCallback(() => {
setBounds(null)
setStartingBounds(null)
setResizingEdge(null)
}, [])

React.useEffect(() => {
Expand All @@ -1072,6 +1080,7 @@ export const GridResizeControls = controlForStrategyMemoized<GridResizeControlPr
(uid: string, edge: GridResizeEdge) => (event: React.MouseEvent) => {
event.stopPropagation()
const frame = zeroRectIfNullOrInfinity(element?.globalFrame ?? null)
setResizingEdge(edge)
setBounds(frame)
setStartingBounds(frame)
const start = windowToCanvasCoordinates(
Expand All @@ -1093,10 +1102,21 @@ export const GridResizeControls = controlForStrategyMemoized<GridResizeControlPr
[canvasOffsetRef, dispatch, element?.globalFrame, scale],
)

const canShowHandles = React.useMemo(() => {
if (isResizing) {
return true
}
if (element?.globalFrame == null || isInfinityRectangle(element.globalFrame)) {
return false
}
return element.globalFrame.width * scale > 40 && element.globalFrame.height > 40
}, [element, scale, isResizing])

if (
element == null ||
element.globalFrame == null ||
isInfinityRectangle(element.globalFrame)
isInfinityRectangle(element.globalFrame) ||
!canShowHandles
) {
return null
}
Expand All @@ -1113,62 +1133,67 @@ export const GridResizeControls = controlForStrategyMemoized<GridResizeControlPr
left: bounds?.x ?? element.globalFrame.x,
width: bounds?.width ?? element.globalFrame.width,
height: bounds?.height ?? element.globalFrame.height,
display: 'grid',
gridTemplateRows: '10px 1fr 10px',
gridTemplateColumns: '10px 1fr 10px',
gridTemplateAreas: "'empty1 rs empty2' 'cs empty3 ce' 'empty4 re empty5'",
backgroundColor: isResizing ? colorTheme.whiteOpacity30.value : 'transparent',
}}
>
{GridResizeEdges.map((edge) => (
<div
key={edge}
style={{
pointerEvents: 'none',
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: 2 / scale,
gridArea: gridEdgeToGridArea(edge),
cursor: gridEdgeToCSSCursor(edge),
}}
>
<div
data-testid={GridResizeEdgeTestId(edge)}
onMouseDown={startResizeInteraction(EP.toUid(element.elementPath), edge)}
style={{
pointerEvents: 'initial',
...gridEdgeToWidthHeight(edge, scale),
backgroundColor: colorTheme.white.value,
boxShadow: `${colorTheme.canvasControlsSizeBoxShadowColor50.value} 0px 0px
${1 / scale}px, ${colorTheme.canvasControlsSizeBoxShadowColor20.value} 0px ${1 / scale}px ${
2 / scale
}px ${1 / scale}px`,
}}
/>
</div>
))}
<div
style={{
position: 'relative',
width: '100%',
height: '100%',
pointerEvents: 'none',
}}
>
{GridResizeEdges.map((edge) => {
const properties = gridResizeEdgeProperties(edge)
const visible = !isResizing || resizingEdge === edge
return (
<div
key={edge}
style={{
visibility: visible ? 'visible' : 'hidden',
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
...gridEdgeToWidthHeight(properties, scale),
}}
>
<div
data-testid={GridResizeEdgeTestId(edge)}
onMouseDown={startResizeInteraction(EP.toUid(element.elementPath), edge)}
style={{
width: properties.isRow
? GRID_RESIZE_HANDLE_SIZES.long
: GRID_RESIZE_HANDLE_SIZES.short,
height: properties.isColumn
? GRID_RESIZE_HANDLE_SIZES.long
: GRID_RESIZE_HANDLE_SIZES.short,
borderRadius: 4,
cursor: gridEdgeToCSSCursor(edge),
pointerEvents: 'initial',
backgroundColor: colorTheme.white.value,
boxShadow: `${colorTheme.canvasControlsSizeBoxShadowColor50.value} 0px 0px
${1 / scale}px, ${
colorTheme.canvasControlsSizeBoxShadowColor20.value
} 0px ${1 / scale}px ${2 / scale}px ${1 / scale}px`,
zoom: 1 / scale,
}}
/>
</div>
)
})}
</div>
</div>
</CanvasOffsetWrapper>
)
},
)

function gridEdgeToGridArea(edge: GridResizeEdge): string {
switch (edge) {
case 'column-end':
return 'ce'
case 'column-start':
return 'cs'
case 'row-end':
return 're'
case 'row-start':
return 'rs'
default:
assertNever(edge)
}
const GRID_RESIZE_HANDLE_SIZES = {
long: 24,
short: 4,
}

function gridEdgeToEdgePosition(edge: GridResizeEdge): EdgePosition {
Expand Down Expand Up @@ -1199,25 +1224,13 @@ function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor {
}
}

function gridEdgeToWidthHeight(
edge: GridResizeEdge,
scale: number,
): {
width: number
height: number
borderRadius: number
} {
const LONG_EDGE = 24 / scale
const SHORT_EDGE = 4 / scale

switch (edge) {
case 'column-end':
case 'column-start':
return { width: SHORT_EDGE, height: LONG_EDGE, borderRadius: SHORT_EDGE / 2 }
case 'row-end':
case 'row-start':
return { width: LONG_EDGE, height: SHORT_EDGE, borderRadius: SHORT_EDGE / 2 }
default:
assertNever(edge)
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,
}
}

0 comments on commit ccf3189

Please sign in to comment.