Skip to content

Commit

Permalink
Feature/simple layout section (#4316)
Browse files Browse the repository at this point in the history
* a SimplifiedLayoutSubsection is born

* inlining the DimensionRow

* this should not be a ref

* FixedHugDropdown v1

* simplifying useOnSubmitFixedFillHugType, step 1

* useOnSubmitFixedFillHugType simplification, step 2

* GroupConstraintSelect only uses single prop

* leftover delete

* GroupChildPinControl separated

* WidthHeightNumberControl

* ClipContentControl row!

* a ConstraintsSection appeared!

* steps towards a working FrameChildConstraintsSection

* working constraint dropdown

* pindetect multiselect now!

* framePoints are displaying something

* Update constraints-section.tsx

* mouse based pinning works now!

* updating performance tests

* feature(inspector) Basic cut of simplified layout controls.

* feature(inspector) Updates now re-using some strategy code.

* reset the number input on empty input

* moving FrameUpdatingLayoutSection to its own component

* moving ClipContentControl and FrameUpdatingLayoutSection into their own modules

* moving pinnning helpers to dedicated file

* reset the number control if the user tries to input a unit

* Update frame-updating-layout-section.tsx

* updating the perf tests

* feature(inspector) Simplified layout section now better disables itself for multiselect.

* pad the constraints dropdowns so they match the height of the pin control

* oops missing useCallback

* fixing a variable name, adding comment

---------

Co-authored-by: Sean Parsons <[email protected]>
  • Loading branch information
balazsbajorics and seanparsons authored Oct 9, 2023
1 parent d36cc0b commit d782641
Show file tree
Hide file tree
Showing 27 changed files with 1,465 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
zeroRectangle,
} from '../../../../core/shared/math-utils'
import {
getMoveCommandsForSelectedElement,
getInteractionMoveCommandsForSelectedElement,
getMultiselectBounds,
} from './shared-move-strategies-helpers'
import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command'
Expand Down Expand Up @@ -75,7 +75,7 @@ export function keyboardAbsoluteMoveStrategy(
})
})
selectedElements.forEach((selectedElement) => {
const elementResult = getMoveCommandsForSelectedElement(
const elementResult = getInteractionMoveCommandsForSelectedElement(
selectedElement,
keyboardMovement,
canvasState,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template'
import { isJSXElement } from '../../../../core/shared/element-template'
import { MetadataUtils } from '../../../../core/model/element-metadata-utils'
import type { CanvasVector } from '../../../../core/shared/math-utils'
import type { CanvasRectangle, CanvasVector } from '../../../../core/shared/math-utils'
import {
canvasRectangle,
canvasVector,
Expand All @@ -11,6 +12,7 @@ import {
} from '../../../../core/shared/math-utils'
import type { KeyCharacter } from '../../../../utils/keyboard'
import Keyboard from '../../../../utils/keyboard'
import type { AllElementProps } from '../../../editor/store/editor-state'
import { withUnderlyingTarget } from '../../../editor/store/editor-state'
import type { CanvasFrameAndTarget, EdgePosition } from '../../canvas-types'
import { EdgePositionBottom, EdgePositionRight } from '../../canvas-types'
Expand Down Expand Up @@ -38,6 +40,10 @@ import {
} from './shared-keyboard-strategy-helpers'
import { getMultiselectBounds } from './shared-move-strategies-helpers'
import { retargetStrategyToChildrenOfFragmentLikeElements } from './fragment-like-helpers'
import type { ElementPath } from '../../../../core/shared/project-file-types'
import type { ProjectContentTreeRoot } from '../../../../components/assets'
import type { InspectorStrategy } from '../../../../components/inspector/inspector-strategies/inspector-strategy'
import type { ElementPathTrees } from '../../../../core/shared/element-path-tree'

interface VectorAndEdge {
movement: CanvasVector
Expand Down Expand Up @@ -111,6 +117,97 @@ function getFitness(interactionSession: InteractionSession | null): number {

export const ResizeMinimumValue = 1

export interface ChangeBoundsResult {
commands: Array<CanvasCommand>
intendedBounds: Array<CanvasFrameAndTarget>
}

export function changeBounds(
projectContents: ProjectContentTreeRoot,
startingMetadata: ElementInstanceMetadataMap,
selectedElements: Array<ElementPath>,
originalFrame: CanvasRectangle,
originalIntendedBounds: Array<CanvasFrameAndTarget>,
edgePosition: EdgePosition,
movement: CanvasVector,
): ChangeBoundsResult {
let commands: Array<CanvasCommand> = []
let intendedBounds: Array<CanvasFrameAndTarget> = originalIntendedBounds

selectedElements.forEach((selectedElement) => {
const element = withUnderlyingTarget(selectedElement, projectContents, null, (_, e) => e)

const elementMetadata = MetadataUtils.findElementByElementPath(
startingMetadata,
selectedElement,
)
const elementParentBounds =
elementMetadata?.specialSizeMeasurements.immediateParentBounds ?? null
const elementParentFlexDirection =
elementMetadata?.specialSizeMeasurements.parentFlexDirection ?? null
const elementGlobalFrame = nullIfInfinity(elementMetadata?.globalFrame ?? null)

if (element != null && isJSXElement(element)) {
const elementResult = createResizeCommands(
element,
selectedElement,
edgePosition,
movement,
elementGlobalFrame,
elementParentBounds,
elementParentFlexDirection,
)
commands.push(...elementResult.commands)
if (elementResult.intendedBounds != null) {
intendedBounds = addOrMergeIntendedBounds(
intendedBounds,
originalFrame,
elementResult.intendedBounds,
)
}
}
})

return {
commands: commands,
intendedBounds: intendedBounds,
}
}

export function resizeInspectorStrategy(
projectContents: ProjectContentTreeRoot,
originalFrame: CanvasRectangle,
edgePosition: EdgePosition,
movement: CanvasVector,
): InspectorStrategy {
return {
name: 'Resize by pixels',
strategy: (
metadata: ElementInstanceMetadataMap,
selectedElements: Array<ElementPath>,
_elementPathTree: ElementPathTrees,
_allElementProps: AllElementProps,
): Array<CanvasCommand> | null => {
let commands: Array<CanvasCommand> = []
const changeBoundsResult = changeBounds(
projectContents,
metadata,
selectedElements,
originalFrame,
[],
edgePosition,
movement,
)
commands.push(...changeBoundsResult.commands)
commands.push(
pushIntendedBoundsAndUpdateGroups(changeBoundsResult.intendedBounds, 'starting-metadata'),
)
commands.push(setElementsToRerenderCommand(selectedElements))
return commands
},
}
}

export function keyboardAbsoluteResizeStrategy(
canvasState: InteractionCanvasState,
interactionSession: InteractionSession | null,
Expand Down Expand Up @@ -168,44 +265,18 @@ export function keyboardAbsoluteResizeStrategy(
null,
'non-center-based',
)
selectedElements.forEach((selectedElement) => {
const element = withUnderlyingTarget(
selectedElement,
canvasState.projectContents,
null,
(_, e) => e,
)

const elementMetadata = MetadataUtils.findElementByElementPath(
canvasState.startingMetadata,
selectedElement,
)
const elementParentBounds =
elementMetadata?.specialSizeMeasurements.immediateParentBounds ?? null
const elementParentFlexDirection =
elementMetadata?.specialSizeMeasurements.parentFlexDirection ?? null
const elementGlobalFrame = nullIfInfinity(elementMetadata?.globalFrame ?? null)

if (element != null && isJSXElement(element)) {
const elementResult = createResizeCommands(
element,
selectedElement,
movementWithEdge.edge,
movementWithEdge.movement,
elementGlobalFrame,
elementParentBounds,
elementParentFlexDirection,
)
commands.push(...elementResult.commands)
if (elementResult.intendedBounds != null) {
intendedBounds = addOrMergeIntendedBounds(
intendedBounds,
originalFrame,
elementResult.intendedBounds,
)
}
}
})

const changeBoundsResult = changeBounds(
canvasState.projectContents,
canvasState.startingMetadata,
selectedElements,
originalFrame,
intendedBounds,
movementWithEdge.edge,
movementWithEdge.movement,
)
intendedBounds = changeBoundsResult.intendedBounds
commands.push(...changeBoundsResult.commands)
})
const guidelines = getKeyboardStrategyGuidelines(canvasState, interactionSession, newFrame)
commands.push(setSnappingGuidelines('mid-interaction', guidelines))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ import type { AbsolutePin } from './resize-helpers'
import type { FlexDirection } from '../../../inspector/common/css-utils'
import { memoize } from '../../../../core/shared/memoize'
import { is } from '../../../../core/shared/equality-utils'
import type { ProjectContentTreeRoot } from '../../../../components/assets'
import type { InspectorStrategy } from '../../../../components/inspector/inspector-strategies/inspector-strategy'

export interface MoveCommandsOptions {
ignoreLocalFrame?: boolean
Expand All @@ -79,7 +81,7 @@ export const getAdjustMoveCommands =
let commands: Array<AdjustCssLengthProperties> = []
let intendedBounds: Array<CanvasFrameAndTarget> = []
filteredSelectedElements.forEach((selectedElement) => {
const elementResult = getMoveCommandsForSelectedElement(
const elementResult = getInteractionMoveCommandsForSelectedElement(
selectedElement,
snappedDragVector,
canvasState,
Expand Down Expand Up @@ -164,22 +166,20 @@ export function applyMoveCommon(
}

export function getMoveCommandsForSelectedElement(
projectContents: ProjectContentTreeRoot,
startingMetadata: ElementInstanceMetadataMap,
selectedElement: ElementPath,
mappedPath: ElementPath,
drag: CanvasVector,
canvasState: InteractionCanvasState,
interactionSession: InteractionSession,
options?: MoveCommandsOptions,
): {
commands: Array<AdjustCssLengthProperties>
intendedBounds: Array<CanvasFrameAndTarget>
} {
const element: JSXElement | null = getElementFromProjectContents(
selectedElement,
canvasState.projectContents,
)
const element: JSXElement | null = getElementFromProjectContents(selectedElement, projectContents)

const elementMetadata = MetadataUtils.findElementByElementPath(
canvasState.startingMetadata, // TODO should this be using the current metadata?
startingMetadata, // TODO should this be using the current metadata?
selectedElement,
)

Expand All @@ -188,22 +188,16 @@ export function getMoveCommandsForSelectedElement(

const localFrame = options?.ignoreLocalFrame
? null
: MetadataUtils.getLocalFrameFromSpecialSizeMeasurements(
selectedElement,
canvasState.startingMetadata,
)
: MetadataUtils.getLocalFrameFromSpecialSizeMeasurements(selectedElement, startingMetadata)

const globalFrame = nullIfInfinity(
MetadataUtils.getFrameInCanvasCoords(selectedElement, canvasState.startingMetadata),
MetadataUtils.getFrameInCanvasCoords(selectedElement, startingMetadata),
)

if (element == null) {
return { commands: [], intendedBounds: [] }
}

const mappedPath =
interactionSession.updatedTargetPaths[EP.toString(selectedElement)] ?? selectedElement

return createMoveCommandsForElement(
element,
selectedElement,
Expand All @@ -216,6 +210,56 @@ export function getMoveCommandsForSelectedElement(
)
}

export function getInteractionMoveCommandsForSelectedElement(
selectedElement: ElementPath,
drag: CanvasVector,
canvasState: InteractionCanvasState,
interactionSession: InteractionSession,
options?: MoveCommandsOptions,
): {
commands: Array<AdjustCssLengthProperties>
intendedBounds: Array<CanvasFrameAndTarget>
} {
const mappedPath =
interactionSession.updatedTargetPaths[EP.toString(selectedElement)] ?? selectedElement

return getMoveCommandsForSelectedElement(
canvasState.projectContents,
canvasState.startingMetadata,
selectedElement,
mappedPath,
drag,
options,
)
}

export function moveInspectorStrategy(
projectContents: ProjectContentTreeRoot,
movement: CanvasVector,
): InspectorStrategy {
return {
name: 'Move by pixels',
strategy: (metadata, selectedElementPaths, _elementPathTree, _allElementProps) => {
let commands: Array<CanvasCommand> = []
let intendedBounds: Array<CanvasFrameAndTarget> = []
for (const selectedPath of selectedElementPaths) {
const moveCommandsResult = getMoveCommandsForSelectedElement(
projectContents,
metadata,
selectedPath,
selectedPath,
movement,
)
commands.push(...moveCommandsResult.commands)
intendedBounds.push(...moveCommandsResult.intendedBounds)
}
commands.push(pushIntendedBoundsAndUpdateGroups(intendedBounds, 'starting-metadata'))
commands.push(setElementsToRerenderCommand(selectedElementPaths))
return commands
},
}
}

export function createMoveCommandsForElement(
element: JSXElement,
selectedElement: ElementPath,
Expand Down
2 changes: 1 addition & 1 deletion editor/src/components/canvas/canvas-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ function referenceParentValueForProp(prop: LayoutPinnedProp, parentSize: Size):
}
}

function valueToUseForPin(
export function valueToUseForPin(
prop: LayoutPinnedProp,
absoluteValue: number,
pinIsPercentPin: boolean,
Expand Down
34 changes: 18 additions & 16 deletions editor/src/components/inspector/common/css-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,20 @@ export function isFixedSize(value: CSSNumber): boolean {
}
}

export function isCssNumberAndFixedSize(value: unknown): boolean {
if (!isCSSNumber(value)) {
return false
}
return isFixedSize(value)
}

export function isCssNumberAndPercentage(value: unknown): boolean {
if (!isCSSNumber(value)) {
return false
}
return value.unit === '%'
}

function parseCSSNumberUnit(
input: string,
units: Array<CSSNumberUnit>,
Expand Down Expand Up @@ -5170,22 +5184,10 @@ export const trivialDefaultValues: ParsedPropertiesWithNonTrivial = {
},
alignSelf: FlexAlignment.Auto,
position: 'relative',
left: {
value: 0,
unit: 'px',
},
top: {
value: 0,
unit: 'px',
},
right: {
value: 0,
unit: 'px',
},
bottom: {
value: 0,
unit: 'px',
},
left: nontrivial, // nontrivial means we will never treat these props as "do not show if it equals default value"
top: nontrivial,
right: nontrivial,
bottom: nontrivial,
minWidth: {
value: 0,
unit: 'px',
Expand Down
Loading

0 comments on commit d782641

Please sign in to comment.