Resizing Nodes
Nodes are resized interactively by dragging a resize handle. A handle is a small, draggable object modeled by the IHandle interface. The handle translates a drag gesture into a change on the corresponding model item.
Moving the handles is managed by HandleInputMode, a child input mode of GraphEditorInputMode. The IHandle interface translates the movement into a resize operation.
HandleInputMode handles not only resize operations but also move operations for elements that provide handles, such as bends or ports.
The items that are currently dragged (or to be dragged) can be retrieved via the affectedItems property. All events listed below are dispatched when the affectedItems property is valid. Therefore, it is safe to access this property in event handlers for these events to determine the moved items.
Event | Occurs when |
---|---|
HandleInputMode makes extensive use of the service locator pattern. Its behavior can be influenced significantly by modifying an item’s lookup to return custom implementations of the following interfaces.
The handles collection of a HandleInputMode that is configured by GraphEditorInputMode is updated upon changes in the selection: the lookup of all selected items is queried for implementations of
- IReshapeHandleProvider to provide handles for each supported position. If none is found, the lookup is queried for an
- IHandleProvider to provide a set of arbitrary handles. Additionally, bends of selected edges are directly queried for an
- IHandle, which will be added directly.
IReshapeHandleProvider
An IReshapeHandleProvider provides specialized handles for resize operations. These handles behave differently depending on their position. For example, a handle at the top border of a rectangle is typically used to move that border in a vertical direction. The positions are defined by the HandlePositions enumeration.
IReshapeHandleProvider implementations must implement the following two methods:
- getAvailableHandles(context: IInputModeContext): HandlePositions
- Provides the positions for available handles as an HandlePositions enumeration.
- getHandle(context: IInputModeContext, position: HandlePositions): IHandle
- Returns a handle for the given position.
The node’s lookup can be modified to return an instance of a custom IReshapeHandleProvider implementation. You can do this using the NodeDecorator’s reshapeHandleProvider:
graph.decorator.nodes.reshapeHandleProvider.addWrapperFactory(
(_, provider) => new CustomReshapeHandlerProvider(provider)
)
class CustomReshapeHandlerProvider extends ReshapeHandleProviderBase {
originalProvider
constructor(originalProvider) {
super()
this.originalProvider = originalProvider
}
getAvailableHandles(context) {
// Only show resize handles at the corners of the node
return HandlePositions.CORNERS
}
getHandle(context, position) {
// Delegate the actual handle creation to the original provider
return this.originalProvider.getHandle(context, position)
}
}
graph.decorator.nodes.reshapeHandleProvider.addWrapperFactory(
(_, provider) => new CustomReshapeHandlerProvider(provider!)
)
class CustomReshapeHandlerProvider extends ReshapeHandleProviderBase {
originalProvider: IReshapeHandleProvider
constructor(originalProvider: IReshapeHandleProvider) {
super()
this.originalProvider = originalProvider
}
getAvailableHandles(context: IInputModeContext): HandlePositions {
// Only show resize handles at the corners of the node
return HandlePositions.CORNERS
}
getHandle(context: IInputModeContext, position: HandlePositions): IHandle {
// Delegate the actual handle creation to the original provider
return this.originalProvider.getHandle(context, position)
}
}
The Reshape Handle Provider Configuration and Reshape Handle Provider demos show how to use different IReshapeHandleProvider implementations to control the resize behavior of nodes and ports.
IHandleProvider
An IHandleProvider returns a list of handles for various purposes via its getHandles method.
Any model item’s lookup can be modified to return a custom IHandleProvider implementation. You can do this using the decorator’s handleProvider.
IHandle
IHandle is the interface used to initiate a drag gesture that results in a change to its associated model item. It provides a drag point for a drag operation and translates the drag operation into a change on the model item. This can be a change in the model item’s size, as discussed in this section, a move operation, as handled in the previous section, or even something completely different, as long as it fits the “small handle that can be dragged” concept.
IHandle uses four methods to manage its life cycle:
- initializeDrag(context: IInputModeContext): void
- Initializes the handle at the start of a drag operation. Implementers should allocate the resources they require, remember the initial position, and, if applicable, initialize snapping and orthogonal edge editing.
- handleMove(context: IInputModeContext, originalLocation: Point, newLocation: Point): void
- Called during a move gesture to update the current handle position and change the handled model item.
- cancelDrag(context: IInputModeContext, originalLocation: Point): void
- Called when a move gesture is canceled. Implementers should restore the initial state and free all resources here.
- dragFinished(context: IInputModeContext, originalLocation: Point, newLocation: Point): void
- Called when a move gesture finishes successfully. Implementers should apply the change to the handled model item and free all resources here.
Outside of this cycle, clicking the handle can be processed in handleClick(evt: ClickEventArgs): void.
Besides these methods, IHandle also provides properties that HandleInputMode can use to determine the type of the handle and a preferred cursor to visualize its purpose.
Node resizing is mediated by IHandle implementations that delegate to an IReshapeHandler (see below) in the handleMove method.
IReshapeHandler
An IReshapeHandler manages reshaping, that is, changing the dimensions of an item.
- initializeReshape(context: IInputModeContext): void
- Initializes the reshape handler at the start of a drag operation. Implementers should allocate required resources, remember the initial bounds, and, if applicable, initialize snapping and orthogonal edge editing.
- handleReshape(context: IInputModeContext, originalBounds: Rect, newBounds: Rect): void
- Called during a move gesture to update the current bounds.
- cancelReshape(context: IInputModeContext, originalBounds: Rect): void
- Called when a move gesture is canceled. Implementers should restore the initial bounds and free all resources here.
- reshapeFinished(context: IInputModeContext, originalBounds: Rect, newBounds: Rect): void
- Called when a move gesture has finished successfully. Implementers should set the current bounds and free all resources here.
INodeSizeConstraintProvider
The default implementation of IReshapeHandler queries its node’s lookup for an implementation of INodeSizeConstraintProvider. Its methods can be implemented to restrict resize operations by providing a minimum and a maximum size. Furthermore, it can provide a rectangle which defines an area which must always be enclosed in the bounds to be resized.
The interface’s default implementation, NodeSizeConstraintProvider provides properties to define these values.
A node’s lookup can be decorated to return an implementation of INodeSizeConstraintProvider:
// nodes should not be smaller than 10x10 or larger than 100x100
graph.decorator.nodes.sizeConstraintProvider.addConstant(
new NodeSizeConstraintProvider(
new Size(10, 10),
new Size(100, 100),
Rect.EMPTY
)
)
Note that in certain circumstances, e.g. with grouping, there can already be an instance of INodeSizeConstraintProvider present in a node’s lookup, e.g. for constraining a group node’s size to its contents.
The demo application Size Constraint Provider shows how to implement and use different INodeSizeConstraintProvider implementations.