Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested properties #23

Merged
merged 7 commits into from
Jul 8, 2020
Merged

Nested properties #23

merged 7 commits into from
Jul 8, 2020

Conversation

alexharri
Copy link
Owner

@alexharri alexharri commented Jul 7, 2020

Changes

Nested properties (property groups)

In CompositionState, the properties are now either a CompositionProperty or CompositionPropertyGroup.

export interface CompositionPropertyGroup {
	type: "group";
	name: PropertyGroupName;
	id: string;
	properties: string[];
	collapsed: boolean;
}

export interface CompositionState {
	compositions: {
		[compositionId: string]: Composition;
	};
	layers: {
		[layerId: string]: CompositionLayer;
	};
	properties: {
		[propertyId: string]: CompositionProperty | CompositionPropertyGroup;
	};
}

The CompositionPropertyGroup's properties references the ids of other properties and property groups. This allows properties to be recursive (nested).

When collapsed, the child properties of the group are not shown.

image

Property IO Nodes

Introduced two new nodes in the Node Editor, property_input and property_input.

image

When added, no property is selected. When the select button is clicked, a context menu appears where you can select any property or property group of the layer.

image

If a property is selected, that property is exposed in the I/O of the node.

image

If a property group is selected, the direct children of the property group are exposed in the I/O.

image

This node behaves similarly to the layer_transform_ nodes, except the property_ nodes are generalized to any property or property group. Since these property nodes cover the layer transform nodes' use case, they have been removed.

image

How these nodes affect the composition timeline/workspace will be discussed in the next sections.

Composition Timeline/Workspace "Computed Values"

When a property_output node's input has a pointer to an output, the computed value is currently reflected in the Composition Timeline and Workspace. However, it is not immediately clear whether the value is the computed value or the raw value of the property.

If the value is computed, we now show the value in the color red.

image

This is based on how After Effects makes the color of the value red if the property has an expression.

image

When the NumberInput's "typing mode" is entered, we show the raw value. We also show the raw value (in blue) immediately on mousedown and when dragging (hard to show with screenshot).

image

This is done via a new prop on NumberInput called showValue. When provided, the showValue prop is considered to be the computed value. We never directly operate on it, we only display it to the user.

We will probably adopt a different way of communicating this in the future. For now, following AE's convention works fine.

Dispatch order issue

Each dispatch causes an immediate rerender for components listening to the state change. This causes an issue when individual dispatches result in invalid state but a group of dispatches result in a valid state.

E.g. A references B and B references A. Two dispatches removing A and then B could throw an error if B's reference to A is invalid for a single render.

This can also be an issue of multiple synchronous dispatches being in the wrong order (e.g. removing a timeline referenced by a property before removing the property's reference to the timeline, which is what happened).

The dispatch in RequestActionParams now accepts a singe action, an array of actions, or a number of actions as separate arguments.

export interface RequestActionParams {
	dispatch: (action: any | any[], ...otherActions: any[]) => void;
	// ...
}

We can do this because every reducer is action/history based, which creates a layer of abstraction we can make use of.

let newState = state.action.state;

for (let i = 0; i < actionBatch.length; i += 1) {
	newState = reducer(newState, actionBatch[i]);
}

This way only one action was dispatched as far as Redux is concerned, and thus we can act with impunity.

dispatch(
	timelineActions.removeTimeline(timelineId),
	compositionActions.setPropertyValue(propertyId, value),
	compositionActions.setPropertyTimelineId(propertyId, ""),
);

Custom Context Menus

You can now open custom Custom Context Menus via openCustomContextMenu.

export interface ContextMenuBaseProps {
	updateRect: (rect: Rect) => void;
}

export interface OpenCustomContextMenuOptions<
	T extends ContextMenuBaseProps = ContextMenuBaseProps
> {
	component: React.ComponentType<T>;
	props: Omit<T, "updateRect">;
	position: Vec2;
	close: () => void;
}

// ...

openCustomContextMenu: createAction("contextMenu/OPEN_CUSTOM", (action) => {
	return (options: OpenCustomContextMenuOptions) => action({ options });
}),

This is a relatively low-level API, but it provides some things that are useful.

  • Global interaction "background layer"
  • Close self when mouse is "too far out"

The global interaction background layer is a fixed div that is overlayed behind the context menu but in front of the whole app. It prevents interaction with the rest of the app when a context menu is open. On mousedown it is closed.

When the mouse is moved CLOSE_MENU_BUFFER px off the rect of the context menu, the context menu is closed.

The rect is expected to be provided by the custom context menu component. This can easily be done via useRefRect.

const Component: React.FC<ContextMenuBaseProps> = ({ updateRect }) => {
	const ref = useRef(null);
	const rect = useRefRect(ref);

	useEffect(() => {
		updateRect(rect!);
	}, [rect]);

	return (
		<div className={s("container")} ref={ref}>
			// ...
		</div>
	);
};

If updateRect is not called by the component, the close self on mouse out functionality is disabled.

Refactor some property types

The names and labels are no longer raw strings. Instead, the name field of properties and property groups are now enums and the label field has been removed.

export enum PropertyGroupName {
	Transform,
	Dimensions,
}

export enum PropertyName {
	// Transform Properties
	Scale,
	PositionX,
	PositionY,
	Rotation,
	Opacity,

	// Other Properties
	Width,
	Height,
}

export interface CompositionPropertyGroup {
	name: PropertyGroupName;
	// ...
}

export interface CompositionProperty {
	name: PropertyName;
	// ...
}

The labels of these groups can be fetched via compositionPropertyUtils.ts's new fns, getLayerPropertyLabel and getLayerPropertyGroupLabel.

Fix up num_input node

The node's default state was not provided. The default state is now { value: 0, type: "value" }

Add to nodeEditorUtils

Added

  • findInputsThatReferenceNodeOutputs
  • removeReferencesToNodeInGraph
  • removeNodeAndReferencesToItInGraph

Add tests for mapUtils

Rudimentary tests were added for mapUtils.

Fix -Infinity bug

When creating some ids, Math.max would be used on the elements on the ids of the elements and then incremented by 1.

When Math.max receives no arguments, it returns -Infinity. This created some obvious issues.

Fixed by always providing 0.

Math.max(0, ...ids)

@alexharri alexharri marked this pull request as ready for review July 8, 2020 23:22
@alexharri alexharri merged commit 6884ed0 into master Jul 8, 2020
@alexharri alexharri changed the title WIP: Nested properties Nested properties Jul 9, 2020
This was referenced Jul 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant