---
title: Using a controller
---

## 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.

<Warning>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.</Warning>

Initialize a controller like so:

```dart title="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.

```dart title="Disposing a controller"
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
```

Pass the controller to the `TransformableBox` constructor.

```dart title="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.

```dart title="Listening to changes"
  controller.addListener(onControllerChanged);
```
Remove the listener when no longer needed.

```dart title="Removing a listener"
  controller.removeListener(onControllerChanged);
```
### Setting constraints

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

```dart title="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.

```dart title="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:

```dart title="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.

```dart title="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](/flutter_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...

```dart title="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);
```
