Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 6 additions & 12 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,11 @@ jobs:
with:
channel: stable
cache: true
- name: Install Melos
run: flutter pub global activate melos
- name: Setup Melos
run: melos bootstrap
- name: Get Dependencies
run: melos deps
- name: Install dependencies
run: flutter pub get
- name: Build Example
run: |
cd packages/flutter_box_transform/example
flutter build web --release
working-directory: packages/flutter_box_transform/example
run: flutter build web --release
- name: Deploy example
uses: FirebaseExtended/action-hosting-deploy@v0
with:
Expand All @@ -38,9 +33,8 @@ jobs:
projectId: box-transform-example
target: example
- name: Build Playground
run: |
cd packages/flutter_box_transform/playground
flutter build web --release
working-directory: packages/flutter_box_transform/playground
run: flutter build web --release
- name: Deploy playground
uses: FirebaseExtended/action-hosting-deploy@v0
with:
Expand Down
11 changes: 4 additions & 7 deletions .github/workflows/pr_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: stable
- name: Install Melos
run: flutter pub global activate melos
- name: Bootstrap
run: melos bootstrap
- name: Install dependencies
run: flutter pub get
- name: Build example app
run: |
cd packages/flutter_box_transform/example
flutter build web --release
working-directory: packages/flutter_box_transform/example
run: flutter build web --release
12 changes: 5 additions & 7 deletions .github/workflows/pr_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ jobs:
with:
channel: stable
cache: true
- name: Install Melos
run: flutter pub global activate melos
- name: Bootstrap
run: melos bootstrap
- name: Install dependencies
run: flutter pub get
- name: Check formatting
run: melos check-format
run: dart format --output none --set-exit-if-changed .
- name: Analyze
run: melos analyze
run: dart analyze . --fatal-infos --fatal-warnings
- name: Run tests
run: melos test
run: flutter test
13 changes: 6 additions & 7 deletions .github/workflows/pr_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,15 @@ jobs:
cache: true
- name: Flutter version
run: flutter --version
- name: Install melos
run: dart pub global activate melos
- name: Setup melos
run: melos bootstrap
- name: Install dependencies
run: melos deps
run: flutter pub get
- name: Run tests with coverage
run: melos run lcov
working-directory: packages/box_transform
run: |
flutter pub run coverage:test_with_coverage
flutter pub run coverage:format_coverage --packages=../../.dart_tool/package_config.json --lcov -i coverage/coverage.json -o coverage/lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./packages/box_transform/coverage/lcov.info
name: codecov-box-transform
name: codecov-box-transform
18 changes: 9 additions & 9 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ jobs:
with:
channel: stable
- run: flutter --version
- name: Install Melos
run: flutter pub global activate melos
- name: Bootstrap
run: melos bootstrap
- name: Install dependencies
run: melos deps
run: flutter pub get
- name: Check code formatting
run: melos check-format
run: dart format --output none --set-exit-if-changed .
- name: Analyze code
run: melos analyze
- name: Publish
run: melos publish --yes --no-dry-run
run: dart analyze . --fatal-infos --fatal-warnings
- name: Publish box_transform
working-directory: packages/box_transform
run: dart pub publish --force
- name: Publish flutter_box_transform
working-directory: packages/flutter_box_transform
run: flutter pub publish --force
13 changes: 6 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,15 @@ jobs:
cache: true
- name: Flutter version
run: flutter --version
- name: Install melos
run: dart pub global activate melos
- name: Setup melos
run: melos bootstrap
- name: Install dependencies
run: melos deps
run: flutter pub get
- name: Run tests with coverage
run: melos run lcov
working-directory: packages/box_transform
run: |
flutter pub run coverage:test_with_coverage
flutter pub run coverage:format_coverage --packages=../../.dart_tool/package_config.json --lcov -i coverage/coverage.json -o coverage/lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./packages/box_transform/coverage/lcov.info
name: codecov-box-transform
name: codecov-box-transform
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pubspec_overrides.yaml
**/*.iml
**/coverage
.dart_tool
**/build/

firebase-debug.log

Expand Down
6 changes: 6 additions & 0 deletions .run/Example.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Example" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/packages/flutter_box_transform/example/lib/main.dart" />
<method v="2" />
</configuration>
</component>
7 changes: 0 additions & 7 deletions .run/Test Recorder.run.xml

This file was deleted.

42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,48 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## Unreleased — Rotation support

---

Packages with breaking changes:

- [`box_transform` - `Unreleased`](#box_transform---unreleased): the default `bindingStrategy` on `BoxTransformer.move/resize/rotate` flips from `originalBox` to `boundingBox`. The old behavior is preserved for callers that pass the strategy explicitly. The list-based LP inspection API (`LinearInequality`, `ProjectionResult`, `buildCornerInequalities`, `buildCenterInequalities`, `projectOntoFeasibleRegion`) is removed; production callers go through the flat-buffer hot path (`IneqBuffer` + `*Into` builders + `projectOntoFeasibleRegionFlat`), and tests use a private test helper at `test/lp_inspect.dart`.
- [`flutter_box_transform` - `Unreleased`](#flutter_box_transform---unreleased): the default `bindingStrategy` on `TransformableBox`, `TransformableBoxController`, and `UIBoxTransform.*` flips from `originalBox` to `boundingBox`. Existing apps will see a behavior change at non-zero rotation: the rendered footprint is now the containment surface, not the unrotated logical rect. Top-level rotated-layout helpers (`rotateOffsetAround`, `handleCornerInParent`, `rotatedCornerInWorld`, `anchorInHandle`, `handleTopLeftInWorld`, `sideHandleRectInWorld`, `computeEffectiveContainmentRect`) are relocated as static methods on `RotatedLayout`; the file-private default handle builders are promoted to `HandleBuilders.defaultCorner` / `HandleBuilders.defaultSide`. The top-level `defaultResizeModeResolver()` is relocated to `TransformableBoxController.defaultResizeModeResolver()`.

Packages with other changes:

- [`box_transform` - `Unreleased`](#box_transform---unreleased)
- [`flutter_box_transform` - `Unreleased`](#flutter_box_transform---unreleased)

---

#### `box_transform` - `Unreleased`

- Add first-class rotation support around a box's center via `Box.rotation` and a new `BoxTransformer.rotate(...)` entry point.
- Add `BindingStrategy` enum: `originalBox` keeps the unrotated logical rect inside the clamp; `boundingBox` keeps the rendered rotated polygon (and therefore its axis-aligned bounding box) fully inside.
- **[BREAKING]** Default `bindingStrategy` flipped from `originalBox` to `boundingBox` on `BoxTransformer.move/resize/rotate`. Pass `BindingStrategy.originalBox` explicitly to restore the previous behavior.
- `BoxTransformer.move()`, `.resize()`, `.rotate()` are all rotation-aware. Freeform, scale, symmetric, symmetricScale, side-handle, and force-flip resize paths each honor rotation under both binding strategies.
- Rotation gestures slide-then-freeze: when the requested angle would push the rect outside the clamp, the engine first tries to translate into available slack; if no translation rescues it, the rotation caps at the last feasible angle.
- Force-flip on a rotated rect falls back to natural direction when the flipped state can't fit clamp + constraints. The rect tracks the cursor by clamp-pinning at the natural wall instead of leaking the clamp or freezing.
- Add `feasible` field to `RotateResult` and `ResizeResult`. `false` means the engine could not honor the requested target without leaking; consumers (controllers) use it to hold the last feasible state.
- Solver: dedicated rotated-clamping LP with corner-, side-, and center-anchored inequality builders, an L2 projector with 1D fallback at saturation, and a unified violator-priority loop. `FlatProjection` now exposes `feasible` + `worstResidual`.
- Tests: extensive rotated-resize, rotated-move, rotated-flip, side-handle, scale, symmetric, symmetricScale, and clamp-invariant coverage. New `clamp_invariants_test.dart` asserts engine invariants (clamp containment, side-handle scope, constraint compliance) on recorded playground scenarios.

#### `flutter_box_transform` - `Unreleased`

- **[BREAKING]** Top-level rotated-layout helpers are now static methods on `RotatedLayout` (`rotateOffsetAround`, `handleCornerInParent`, `rotatedCornerInWorld`, `anchorInHandle`, `handleTopLeftInWorld`, `sideHandleRectInWorld`, and `computeEffectiveContainmentRect`). The previous top-level forms are removed.
- **[BREAKING]** Default handle builders moved from file-private functions to public statics on `HandleBuilders` (`HandleBuilders.defaultCorner`, `HandleBuilders.defaultSide`).
- Add `rotation`, `rotatable`, and `bindingStrategy` parameters on `TransformableBox`.
- **[BREAKING]** Default `bindingStrategy` flipped from `originalBox` to `boundingBox` on `TransformableBox`, `TransformableBoxController`, and `UIBoxTransform.move/resize/rotate`. Existing apps that relied on the unrotated-logical-rect semantic must pass `BindingStrategy.originalBox` explicitly.
- Add per-corner rotation gesture: an outer ring around each corner-handle captures rotation when `rotatable: true`. Sized via `rotationHandleGestureSize` (default 64 px). Add rotation callbacks: `onRotationStart` / `onRotationUpdate` / `onRotationEnd` / `onRotationCancel`.
- `TransformableBoxController` adds `rotation`, `initialRotation`, `bindingStrategy`, `onRotateStart` / `onRotateUpdate` / `onRotateEnd` / `onRotateCancel`. `onResizeUpdate` and `onRotateUpdate` skip state writes on `result.feasible == false` and override `result.rect`/`result.rotation` to the controller's last feasible value, so consumers binding visible state via callbacks (`box.rect = result.rect`) stay clamp-pinned at the last feasible position rather than snapping back to gesture-start.
- Side handles render rotated under non-zero rotation and translate gesture coordinates into the box's un-rotated frame so resize tracks the cursor visually.
- Hit-testing gates corner taps to the rotated polygon (not the AABB) so rotated boxes don't capture clicks in their AABB wedges.
- Playground gains a rotation slider, `bindingStrategy` toggle, debug overlays for rotated/unrotated bounds, and a tick-by-tick test recorder that captures full gesture sequences (rotation + bindingStrategy aware) for regression replay.
- Tests: rotated controller gestures, rotated layout, rotated drag hit-gating, rotated gesture integration, clamp-shrink continuity under rotation, and a force-flip fallback regression test.
- **[BREAKING]** `defaultResizeModeResolver()` is now `TransformableBoxController.defaultResizeModeResolver()`. The top-level function has been removed; consumers that referenced it directly (e.g. as a default for `resizeModeResolver`) must update to the qualified static.

## 2025-03-26

---
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Box Transform

[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) [![Build](https://github.com/hyper-designed/box_transform/actions/workflows/build.yml/badge.svg)](https://github.com/hyper-designed/box_transform/actions/workflows/build.yml) [![Tests](https://github.com/hyper-designed/box_transform/workflows/Tests/badge.svg?branch=main)](https://github.com/hyper-designed/box_transform/actions) [![codecov](https://codecov.io/gh/BirjuVachhani/box_transform/branch/main/graph/badge.svg?token=SX5FXDUD7A)](https://codecov.io/gh/BirjuVachhani/box_transform)
[![Build](https://github.com/hyper-designed/box_transform/actions/workflows/build.yml/badge.svg)](https://github.com/hyper-designed/box_transform/actions/workflows/build.yml) [![Tests](https://github.com/hyper-designed/box_transform/workflows/Tests/badge.svg?branch=main)](https://github.com/hyper-designed/box_transform/actions) [![codecov](https://codecov.io/gh/BirjuVachhani/box_transform/branch/main/graph/badge.svg?token=SX5FXDUD7A)](https://codecov.io/gh/BirjuVachhani/box_transform)

[Box Transform][github] provides packages that allows you to programmatically handle box resizing and dragging.
It provides highly flexible, programmatically resizable and draggable boxes that can be used in any Dart or Flutter
Expand All @@ -24,6 +24,7 @@ project.
* 🎨 **Flexible Resizing Modes:** Choose from four different resizing modes for more flexibility in how boxes are resized.
* 📍 **Customizable Anchor Points:** Define resizing corner-handles to anchor different parts of the box when resizing.
* 🎨 **Customizable Handles:** Use default resizing handles or define your own custom handles.
* 🌀 **Rotation Support:** Rotate boxes around their center, with full rotated-aware drag, resize, flip, and clamping. Choose between `BindingStrategy.originalBox` (the unrotated logical rect stays in the clamp; rotated corners may extend outside) and `BindingStrategy.boundingBox` (the rendered axis-aligned footprint stays inside). Rotation gestures slide-then-freeze: when the requested angle would force the rect outside the clamp, the box slides into available slack first, then caps at the last feasible angle. Rotated force-flip also falls back to the natural direction when the flipped state can't fit, so the rect stays clamp-pinned instead of jumping or freezing.
* 🚀 **Easy Integration:** Integrate Box Transform into your Dart or Flutter project with ease.

## Getting Started
Expand Down
79 changes: 57 additions & 22 deletions development.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,85 @@
## Setting up project for development

This project uses [melos](https://melos.invertase.dev/) to manage the monorepo. To get started, clone the repository
and run the following commands:
This project is a [Dart pub workspace](https://dart.dev/tools/pub/workspaces).
A single `pub get` at the repository root resolves dependencies for every
package in the monorepo and links them together. Requires Dart `>=3.6.0`
(Flutter `>=3.27.0`).

1. Install melos. Make sure you have `.pub-cache/bin` in your path.
1. Clone the repository and fetch dependencies for the whole workspace:

```bash
dart pub global activate melos
flutter pub get
```

2. Setup the project with melos.
You can now run the example or playground apps directly. Local changes to
`box_transform` and `flutter_box_transform` are picked up automatically
because they are workspace members.

### Running tests

Run from the repo root to test all packages:

```bash
melos bootstrap
flutter test
```

This will create dependency overrides for all the packages in the monorepo.
> Use `flutter test` (not `dart test`) — the workspace contains Flutter
> packages, so the Dart-only resolver can't be used.

3. Run `pub get` with melos.
### Running tests with coverage report

1. Install genhtml:

```bash
melos run deps
brew install lcov
```

Now you can run the example app. See `melos.yaml` for more commands.

### Running test
2. Run tests and generate report:

```bash
melos run test
cd packages/box_transform
flutter pub run coverage:test_with_coverage
flutter pub run coverage:format_coverage --packages=../../.dart_tool/package_config.json --lcov -i coverage/coverage.json -o coverage/lcov.info
genhtml coverage/lcov.info -o coverage/html
```

### Running tests with coverage report.
3. Open `packages/box_transform/coverage/html/index.html` in your browser to
view the report.

1. Install genhtml:
### Formatting and analysis

Run from the repo root to cover every package:

```bash
brew install lcov
dart format .
dart analyze . --fatal-infos --fatal-warnings
```

2. Run tests and generate report
### Test layout

```bash
melos run cov
```
The test suites use two complementary prefixes:

* `rotated_<feature>_test.dart` covers an existing feature *under rotation*
(e.g. `rotated_freeform_test.dart`, `rotated_side_handle_test.dart`).
* `rotation_<aspect>_test.dart` covers the rotation feature itself
(e.g. `rotation_primitives_test.dart`, `rotation_integration_test.dart`).

`clamp_*_test.dart` files are targeted regression tests for clamp behavior
(`clamp_invariants_test.dart`, `clamp_at_saturation_move_test.dart`); the
older `clamped_*_resizing_test.dart` family covers the legacy
"resize-with-clamping-enabled" suites.

`packages/flutter_box_transform/test` mirrors the same scheme for widget /
controller tests (`rotated_*` for features, `rotation_*` for rotation
itself).

3. Open `coverage/index.html` in browser to view the report.
### Playground test recorder

You can run `melos run open_cov` on macOS to open the report in browser.
The playground has a built-in test recorder accessible from its side panel.
It captures every `onResizeUpdate` tick of a gesture (cursor positions +
rect outputs) along with the active `rotation` and `bindingStrategy`, and
exports a tick-by-tick replay test with per-tick clamp invariants. The
exported file uses `BoxTransformer.resize(...)` directly, so it lives in
`packages/box_transform/test/`. Use it whenever you find a visual bug that
only manifests under continuous gesture motion — a single direct jump from
start to end often won't reproduce the bug the recorder catches.
20 changes: 18 additions & 2 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
[
"Resize Modes",
"/resize_modes"
],
[
"Binding Strategies",
"/binding_strategies"
]
]
],
Expand All @@ -62,6 +66,10 @@
"Resizing",
"/flutter_resizing"
],
[
"Rotating",
"/flutter_rotating"
],
[
"Handles",
"/handles"
Expand All @@ -71,7 +79,7 @@
"/flutter_controller"
],
[
"API Reference \uD83D\uDD17",
"API Reference 🔗",
"https://pub.dev/documentation/flutter_box_transform/latest/"
]
]
Expand All @@ -92,11 +100,19 @@
"/resizing"
],
[
"API Reference \uD83D\uDD17",
"Rotating",
"/rotating"
],
[
"API Reference 🔗",
"https://pub.dev/documentation/box_transform/latest/"
]
]
],
[
"Benchmarks",
"/benchmarks"
],
[
"License",
"/license"
Expand Down
Loading
Loading