Skip to content

Commit

Permalink
Use stretch instead of fill for grid children (#6424)
Browse files Browse the repository at this point in the history
**Problem:**

The inspector should show `Stretch` instead of `Fill` for grid children,
and set `alignSelf`/`justifySelf` in those cases.

**Fix:**

1. Set `alignSelf`/`justifySelf` to `stretch` if `Stretch` is selected
in the inspector dropdown for grid children
2. Show `Stretch` instead of `Fill` for label for grid children (note: I
opted for just changing the label here instead of having a different
strategy altogether, since in my mind it's still fulfilling the goal of
"filling" the parent – but lmk if this is the wrong take)

Fixes #6423
  • Loading branch information
ruggi authored Sep 30, 2024
1 parent 7f1f470 commit 46a961e
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isInfinityRectangle,
offsetPoint,
} from '../../../../core/shared/math-utils'
import * as PP from '../../../../core/shared/property-path'
import { styleStringInArray } from '../../../../utils/common-constants'
import { trueUpGroupElementChanged } from '../../../editor/store/editor-state'
import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks'
Expand All @@ -20,13 +21,16 @@ import { oppositeEdgePosition } from '../../canvas-types'
import {
isEdgePositionACorner,
isEdgePositionAHorizontalEdge,
isEdgePositionAVerticalEdge,
pickPointOnRect,
} from '../../canvas-utils'
import type { LengthPropertyToAdjust } from '../../commands/adjust-css-length-command'
import {
adjustCssLengthProperties,
lengthPropertyToAdjust,
} from '../../commands/adjust-css-length-command'
import type { CanvasCommand } from '../../commands/commands'
import { deleteProperties } from '../../commands/delete-properties-command'
import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command'
import { queueTrueUpElement } from '../../commands/queue-true-up-command'
import { setCursorCommand } from '../../commands/set-cursor-command'
Expand Down Expand Up @@ -219,21 +223,31 @@ export function basicResizeStrategy(

const elementsToRerender = [...selectedElements, ...gridsToRerender]

return strategyApplicationResult(
[
adjustCssLengthProperties('always', selectedElement, null, resizeProperties),
updateHighlightedViews('mid-interaction', []),
setCursorCommand(pickCursorFromEdgePosition(edgePosition)),
pushIntendedBoundsAndUpdateGroups(
[{ target: selectedElement, frame: resizedBounds }],
'starting-metadata',
),
...groupChildren.map((c) =>
queueTrueUpElement([trueUpGroupElementChanged(c.elementPath)]),
),
],
elementsToRerender,
)
let commands: CanvasCommand[] = [
adjustCssLengthProperties('always', selectedElement, null, resizeProperties),
updateHighlightedViews('mid-interaction', []),
setCursorCommand(pickCursorFromEdgePosition(edgePosition)),
pushIntendedBoundsAndUpdateGroups(
[{ target: selectedElement, frame: resizedBounds }],
'starting-metadata',
),
...groupChildren.map((c) =>
queueTrueUpElement([trueUpGroupElementChanged(c.elementPath)]),
),
]

if (isEdgePositionAHorizontalEdge(edgePosition)) {
commands.push(
deleteProperties('always', selectedElement, [PP.create('style', 'justifySelf')]),
)
}
if (isEdgePositionAVerticalEdge(edgePosition)) {
commands.push(
deleteProperties('always', selectedElement, [PP.create('style', 'alignSelf')]),
)
}

return strategyApplicationResult(commands, elementsToRerender)
} else {
return strategyApplicationResult(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ const sizeLabel = (state: FixedHugFill['type'], actualSize: number): string => {
case 'detected':
case 'computed':
return `${actualSize}`
case 'stretch':
return 'Stretch'
default:
assertNever(state)
}
Expand Down
79 changes: 63 additions & 16 deletions editor/src/components/editor/actions/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import localforage from 'localforage'
import { imagePathURL } from '../../../common/server'
import { roundAttributeLayoutValues } from '../../../core/layout/layout-utils'
import {
findElementAtPath,
findJSXElementAtPath,
getSimpleAttributeAtPath,
getZIndexOrderedViewsWithoutDirectChildren,
MetadataUtils,
} from '../../../core/model/element-metadata-utils'
Expand Down Expand Up @@ -102,6 +101,7 @@ import type {
LocalRectangle,
Size,
CanvasVector,
MaybeInfinityCanvasRectangle,
} from '../../../core/shared/math-utils'
import {
canvasRectangle,
Expand Down Expand Up @@ -157,7 +157,7 @@ import { assertNever, fastForEach, getProjectLockedKey, identity } from '../../.
import { emptyImports, mergeImports } from '../../../core/workers/common/project-file-utils'
import type { UtopiaTsWorkers } from '../../../core/workers/common/worker-types'
import type { IndexPosition } from '../../../utils/utils'
import Utils, { absolute } from '../../../utils/utils'
import Utils from '../../../utils/utils'
import type { ProjectContentTreeRoot } from '../../assets'
import {
isProjectContentFile,
Expand Down Expand Up @@ -430,7 +430,7 @@ import {
import { loadStoredState } from '../stored-state'
import { applyMigrations } from './migrations/migrations'

import { boundsInFile, defaultConfig } from 'utopia-vscode-common'
import { defaultConfig } from 'utopia-vscode-common'
import { reorderElement } from '../../../components/canvas/commands/reorder-element-command'
import type { BuiltInDependencies } from '../../../core/es-modules/package-manager/built-in-dependencies-list'
import { fetchNodeModules } from '../../../core/es-modules/package-manager/fetch-packages'
Expand Down Expand Up @@ -495,7 +495,6 @@ import {
clearImageFileBlob,
enableInsertModeForJSXElement,
finishCheckpointTimer,
insertAsChildTarget,
insertJSXElement,
openCodeEditorFile,
replaceMappedElement,
Expand Down Expand Up @@ -524,7 +523,6 @@ import { styleStringInArray } from '../../../utils/common-constants'
import { collapseTextElements } from '../../../components/text-editor/text-handling'
import { LayoutPropertyList, StyleProperties } from '../../inspector/common/css-utils'
import {
getFromPropOrFlagComment,
isUtopiaPropOrCommentFlag,
makeUtopiaFlagComment,
removePropOrFlagComment,
Expand All @@ -543,7 +541,10 @@ import {
replaceWithElementsWrappedInFragmentBehaviour,
} from '../store/insertion-path'
import { getConditionalCaseCorrespondingToBranchPath } from '../../../core/model/conditionals'
import { deleteProperties } from '../../canvas/commands/delete-properties-command'
import {
deleteProperties,
deleteValuesAtPath,
} from '../../canvas/commands/delete-properties-command'
import { treatElementAsFragmentLike } from '../../canvas/canvas-strategies/strategies/fragment-like-helpers'
import {
fixParentContainingBlockSettings,
Expand Down Expand Up @@ -619,11 +620,12 @@ import {
import type { FixUIDsState } from '../../../core/workers/parser-printer/uid-fix'
import { fixTopLevelElementsUIDs } from '../../../core/workers/parser-printer/uid-fix'
import { nextSelectedTab } from '../../navigator/left-pane/left-pane-utils'
import { getDefaultedRemixRootDir, getRemixRootDir } from '../store/remix-derived-data'
import { getDefaultedRemixRootDir } from '../store/remix-derived-data'
import { isReplaceKeepChildrenAndStyleTarget } from '../../navigator/navigator-item/component-picker-context-menu'
import { canCondenseJSXElementChild } from '../../../utils/can-condense'
import { getNavigatorTargetsFromEditorState } from '../../navigator/navigator-utils'
import { applyValuesAtPath } from '../../canvas/commands/adjust-number-command'
import { styleP } from '../../inspector/inspector-common'

export const MIN_CODE_PANE_REOPEN_WIDTH = 100

Expand Down Expand Up @@ -6589,10 +6591,54 @@ function removeErrorMessagesForFile(editor: EditorState, filename: string): Edit
function alignFlexOrGridChildren(editor: EditorState, views: ElementPath[], alignment: Alignment) {
let workingEditorState = { ...editor }
for (const view of views) {
function apply(prop: 'alignSelf' | 'justifySelf', value: string) {
return applyValuesAtPath(workingEditorState, view, [
// When updating alongside the given alignment, also update the opposite one so that it makes sense:
// For example, if alignment is 'alignSelf', delete the 'justifySelf' if currently set to stretch and, if so,
// set the explicit height of the element (and vice versa for 'justifySelf').
function updateOpposite(
editorState: EditorState,
frame: MaybeInfinityCanvasRectangle | null,
target: 'alignSelf' | 'justifySelf',
dimension: 'width' | 'height',
) {
let working = { ...editorState }

working = deleteValuesAtPath(working, view, [styleP(target)]).editorStateWithChanges

working = applyValuesAtPath(working, view, [
{
path: styleP(dimension),
value: jsExpressionValue(zeroRectIfNullOrInfinity(frame)[dimension], emptyComments),
},
]).editorStateWithChanges

return working
}

function apply(editorState: EditorState, prop: 'alignSelf' | 'justifySelf', value: string) {
const element = MetadataUtils.findElementByElementPath(editor.jsxMetadata, view)
if (element == null || isLeft(element.element) || !isJSXElement(element.element.value)) {
return workingEditorState
}

let working = editorState

working = applyValuesAtPath(working, view, [
{ path: PP.create('style', prop), value: jsExpressionValue(value, emptyComments) },
]).editorStateWithChanges

const alignSelfStretch =
getSimpleAttributeAtPath(right(element.element.value.props), styleP('alignSelf')).value ===
'stretch'
const justifySelfStretch =
getSimpleAttributeAtPath(right(element.element.value.props), styleP('justifySelf'))
.value === 'stretch'

if (prop === 'alignSelf' && justifySelfStretch) {
working = updateOpposite(working, element.globalFrame, 'justifySelf', 'height')
} else if (prop === 'justifySelf' && alignSelfStretch) {
working = updateOpposite(working, element.globalFrame, 'alignSelf', 'width')
}
return working
}

const { align, justify } = MetadataUtils.getRelativeAlignJustify(
Expand All @@ -6602,27 +6648,28 @@ function alignFlexOrGridChildren(editor: EditorState, views: ElementPath[], alig

switch (alignment) {
case 'bottom':
workingEditorState = apply(align, 'end')
workingEditorState = apply(workingEditorState, align, 'end')
break
case 'top':
workingEditorState = apply(align, 'start')
workingEditorState = apply(workingEditorState, align, 'start')
break
case 'vcenter':
workingEditorState = apply(align, 'center')
workingEditorState = apply(workingEditorState, align, 'center')
break
case 'hcenter':
workingEditorState = apply(justify, 'center')
workingEditorState = apply(workingEditorState, justify, 'center')
break
case 'left':
workingEditorState = apply(justify, 'start')
workingEditorState = apply(workingEditorState, justify, 'start')
break
case 'right':
workingEditorState = apply(justify, 'end')
workingEditorState = apply(workingEditorState, justify, 'end')
break
default:
assertNever(alignment)
}
}

return workingEditorState
}

Expand Down
7 changes: 7 additions & 0 deletions editor/src/components/inspector/fill-hug-fixed-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const CollapsedLabel = 'Collapsed' as const
export const HugGroupContentsLabel = 'Hug' as const
export const ComputedLabel = 'Computed' as const
export const DetectedLabel = 'Detected' as const
export const StretchLabel = 'Stretch' as const

export function selectOptionLabel(mode: FixedHugFillMode): string {
switch (mode) {
Expand All @@ -101,6 +102,8 @@ export function selectOptionLabel(mode: FixedHugFillMode): string {
return ComputedLabel
case 'detected':
return DetectedLabel
case 'stretch':
return StretchLabel
default:
assertNever(mode)
}
Expand All @@ -126,6 +129,8 @@ export function selectOptionIconType(
return `fixed-${dimension}`
case 'detected':
return `fixed-${dimension}`
case 'stretch':
return `fill-${dimension}`
default:
assertNever(mode)
}
Expand Down Expand Up @@ -396,6 +401,7 @@ function strategyForChangingFillFixedHugType(
): Array<InspectorStrategy> {
switch (mode) {
case 'fill':
case 'stretch':
return setPropFillStrategies(metadata, selectedElements, axis, 'default', otherAxisSetToFill)
case 'hug':
case 'squeeze':
Expand Down Expand Up @@ -427,6 +433,7 @@ function pickFixedValue(value: FixedHugFill): CSSNumber | undefined {
case 'fill':
case 'hug-group':
return value.value
case 'stretch':
case 'hug':
case 'squeeze':
case 'collapsed':
Expand Down
50 changes: 47 additions & 3 deletions editor/src/components/inspector/inspector-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ export type FixedHugFill =
| { type: 'computed'; value: CSSNumber }
| { type: 'detected'; value: CSSNumber }
| { type: 'scaled'; value: CSSNumber }
| { type: 'stretch' }

export type FixedHugFillMode = FixedHugFill['type']

Expand All @@ -666,6 +667,30 @@ export function detectFillHugFixedState(
return { fixedHugFill: null, controlStatus: 'off' }
}

const width = foldEither(
() => null,
(value) => defaultEither(null, parseCSSNumber(value, 'Unitless')),
getSimpleAttributeAtPath(right(element.element.value.props), PP.create('style', 'width')),
)
const height = foldEither(
() => null,
(value) => defaultEither(null, parseCSSNumber(value, 'Unitless')),
getSimpleAttributeAtPath(right(element.element.value.props), PP.create('style', 'height')),
)

if (MetadataUtils.isGridCell(metadata, elementPath)) {
if (
(element.specialSizeMeasurements.alignSelf === 'stretch' &&
axis === 'horizontal' &&
width == null) ||
(element.specialSizeMeasurements.justifySelf === 'stretch' &&
axis === 'vertical' &&
height == null)
) {
return { fixedHugFill: { type: 'stretch' }, controlStatus: 'detected' }
}
}

const flexGrowLonghand = foldEither(
() => null,
(value) => defaultEither(null, parseCSSNumber(value, 'Unitless')),
Expand Down Expand Up @@ -1071,7 +1096,11 @@ export function getFixedFillHugOptionsForElement(
(!isGroup && basicHugContentsApplicableForContainer(metadata, pathTrees, selectedView))
? 'hug'
: null,
fillContainerApplicable(metadata, selectedView) ? 'fill' : null,
fillContainerApplicable(metadata, selectedView)
? MetadataUtils.isGridCell(metadata, selectedView)
? 'stretch'
: 'fill'
: null,
]),
)
}
Expand Down Expand Up @@ -1198,6 +1227,20 @@ export function removeExtraPinsWhenSettingSize(
)
}

export function removeAlignJustifySelf(
axis: Axis,
elementMetadata: ElementInstanceMetadata | null,
): Array<CanvasCommand> {
if (elementMetadata == null) {
return []
}
return [
deleteProperties('always', elementMetadata.elementPath, [
styleP(axis === 'horizontal' ? 'alignSelf' : 'justifySelf'),
]),
]
}

export function isFixedHugFillEqual(
a: { fixedHugFill: FixedHugFill | null; controlStatus: ControlStatus },
b: { fixedHugFill: FixedHugFill | null; controlStatus: ControlStatus },
Expand Down Expand Up @@ -1230,9 +1273,10 @@ export function isFixedHugFillEqual(
a.fixedHugFill.value.value === b.fixedHugFill.value.value &&
a.fixedHugFill.value.unit === b.fixedHugFill.value.unit
)
case 'stretch':
return a.fixedHugFill.type === b.fixedHugFill.type
default:
const _exhaustiveCheck: never = a.fixedHugFill
throw new Error(`Unknown type in FixedHugFill ${JSON.stringify(a.fixedHugFill)}`)
assertNever(a.fixedHugFill)
}
}

Expand Down
Loading

0 comments on commit 46a961e

Please sign in to comment.