Using a controller

TransformableBox uses a controller pattern. This is the most flexible pattern and allows you to control the transformable box externally with proper state management.

To create a controller, you can use the TransformableBoxController class. It mirrors a lot of the observed parameters in the constructor excluding contentBuilder, handleBuilder, handleTapSize, allowFlippingWhileResizing, movable, resizable, handleAlignment, enabledHandles, visibleHandles, and allowContentFlipping since these are accessibility and rendering features that are controlled at the Widget level.

When using a controller, you should move these parameters from your TransformableBox to the constructor of the TransformableBoxController: box, flip, clampingRect, constraints, and resizeModeResolver. These are all intrinsically managed by the controller. There must always be only one source of truth, either the TransformableBox or the TransformableBoxController, not both.

Initialize a controller like so:

Initializing a controller
  late final TransformableBoxController controller;

  @override
  void initState() {
    super.initState();
    controller = TransformableBoxController(rect: rect);
  }

Don't forget to dispose your controller when no longer needed.

Disposing a controller
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

Pass the controller to the TransformableBox constructor.

Passing a controller
  TransformableBox(
    controller: controller,
    contentBuilder: (context, rect, flip) {...},
  );

Listening to changes

You can listen to changes in the controller using the addListener method. This will be called whenever the controller changes. You can use the removeListener method to remove the listener.

Listening to changes
  controller.addListener(onControllerChanged);

Remove the listener when no longer needed.

Removing a listener
  controller.removeListener(onControllerChanged);

Setting constraints

You can set constraints on the controller using the setConstraints method.

Setting constraints
  controller.setConstraints(BoxConstraints(
    minWidth: 100,
    minHeight: 100,
    maxWidth: 1000,
    maxHeight: 1000,
  ));

Limiting movements

You can limit the movements of the controller using the setClampingBox method.

Limiting movements
  controller.setClampingRect(Rect.fromLTWH(0, 0, 1000, 1000));

Rotation

The controller carries a rotation field (radians around the box's center) plus an initialRotation snapshot captured at gesture start, and a bindingStrategy (originalBox vs boundingBox). It exposes the rotation gesture lifecycle directly:

Rotation lifecycle on the controller
controller.onRotateStart(localPosition);
final result = controller.onRotateUpdate(localPosition, handle);
controller.onRotateEnd();

onRotateUpdate returns a UIRotateResult with a feasible field. When feasible == false, the engine could not honor the requested angle — the controller does not advance its internal rotation or rect, and overrides result.rect/result.rotation to its last feasible state. Consumers that bind their visible state via callbacks (box.rotation = result.rotation) stay clamp-pinned at the last feasible angle rather than snapping back to gesture-start.

Programmatic rotation control
// Set rotation directly (radians, around the box's center).
controller.setRotation(0.4);

// Toggle the binding strategy at runtime — the controller reconciles the
// current rect against the new strategy by translating into clamp slack
// if needed.
controller.setBindingStrategy(BindingStrategy.boundingBox);

onResizeUpdate follows the same feasible-aware semantic: when the rotated freeform/symmetric resize signals it cannot satisfy clamp + constraints, the controller holds the last feasible state and overrides the result's rect/flip accordingly. See the Rotating page for the full story.

Other things you can do with a controller

The TransformableBoxController almost mirrors the constructor parameters of the TransformableBox. You can do things like setting a rect, flip, constraints, clampingRect, disabling resizing or moving, etc...

Things controller can do
// Change current rect
controller.setRect(rect);

// Change current flip
controller.setFlip(flip);

// Change constraints
controller.setConstraints(constraints);

// Change clamping rect
controller.setClampingRect(clampingRect);

// Disable flipping the rect while resizing
controller.setAllowFlippingWhileResizing(false);

// Change rotation programmatically
controller.setRotation(0.4);

// Change binding strategy
controller.setBindingStrategy(BindingStrategy.boundingBox);