diff --git a/packages/flutter_box_transform/example/lib/main.dart b/packages/flutter_box_transform/example/lib/main.dart index 054d225..bc2ad02 100644 --- a/packages/flutter_box_transform/example/lib/main.dart +++ b/packages/flutter_box_transform/example/lib/main.dart @@ -60,6 +60,13 @@ class MyApp extends StatelessWidget { class PlaygroundModel with ChangeNotifier { Rect rect = Rect.zero; Flip flip = Flip.none; + + bool showSecondImageBox = false; + Rect rect2 = Rect.zero; + Flip flip2 = Flip.none; + + int lastRectAdjusted = 1; // 1 or 2 + Rect clampingRect = Rect.largest; Rect? playgroundArea; late BoxConstraints constraints = const BoxConstraints( @@ -73,6 +80,7 @@ class PlaygroundModel with ChangeNotifier { bool constraintsEnabled = false; bool resizable = true; bool movable = true; + bool hideHandlesWhenNotResizable = true; void reset(BuildContext context) { final Size size = MediaQuery.of(context).size; @@ -85,6 +93,17 @@ class PlaygroundModel with ChangeNotifier { kInitialHeight, ); flip = Flip.none; + + rect2 = Rect.fromLTWH( + (width - kInitialWidth) / 3, + (height - kInitialHeight) / 3, + kInitialWidth, + kInitialHeight, + ); + flip2 = Flip.none; + + lastRectAdjusted = 1; + clampingRect = Rect.fromLTWH( 0, 0, @@ -101,6 +120,7 @@ class PlaygroundModel with ChangeNotifier { movable = true; clampingEnabled = false; constraintsEnabled = false; + hideHandlesWhenNotResizable = true; notifyListeners(); } @@ -108,6 +128,14 @@ class PlaygroundModel with ChangeNotifier { void onRectChanged(UITransformResult result) { rect = result.rect; flip = result is UIResizeResult ? result.flip : flip; + lastRectAdjusted = 1; + notifyListeners(); + } + + void onRect2Changed(UITransformResult result) { + rect2 = result.rect; + flip2 = result is UIResizeResult ? result.flip : flip; + lastRectAdjusted = 2; notifyListeners(); } @@ -150,11 +178,13 @@ class PlaygroundModel with ChangeNotifier { void flipHorizontally() { flip = Flip.fromValue(flip.horizontalValue * -1, flip.verticalValue); + flip2 = Flip.fromValue(flip2.horizontalValue * -1, flip2.verticalValue); notifyListeners(); } void flipVertically() { flip = Flip.fromValue(flip.horizontalValue, flip.verticalValue * -1); + flip2 = Flip.fromValue(flip2.horizontalValue, flip2.verticalValue * -1); notifyListeners(); } @@ -178,6 +208,16 @@ class PlaygroundModel with ChangeNotifier { notifyListeners(); } + void toggleHideHandlesWhenNotResizable(bool enabled) { + hideHandlesWhenNotResizable = enabled; + notifyListeners(); + } + + void toggleShowSecondImageBox(bool enabled) { + showSecondImageBox = enabled; + notifyListeners(); + } + void onClampingRectChanged({ double? left, double? top, @@ -230,7 +270,7 @@ const double kSidePanelWidth = 300; const double kInitialWidth = 400; const double kInitialHeight = 300; const double kStrokeWidth = 1.5; -const Color kGridColor = Color(0x7FC3E8F3); +const Color kGridColor = Color.fromARGB(126, 27, 181, 228); class _PlaygroundState extends State with WidgetsBindingObserver { @override @@ -311,7 +351,17 @@ class _PlaygroundState extends State with WidgetsBindingObserver { ), if (model.clampingEnabled && model.playgroundArea != null) const ClampingRect(), - const ImageBox(), + ImageBox( + rect: model.rect, + flip: model.flip, + imageAsset: 'assets/images/landscape2.jpg', + onChanged: model.onRectChanged), + if (model.showSecondImageBox) + ImageBox( + rect: model.rect2, + flip: model.flip2, + imageAsset: 'assets/images/landscape.jpg', + onChanged: model.onRect2Changed), ], ), ), @@ -323,7 +373,17 @@ class _PlaygroundState extends State with WidgetsBindingObserver { } class ImageBox extends StatefulWidget { - const ImageBox({super.key}); + const ImageBox( + {super.key, + required this.rect, + required this.flip, + required this.imageAsset, + required this.onChanged}); + + final Rect rect; + final Flip flip; + final String imageAsset; + final Function(TransformResult)? onChanged; @override State createState() => _ImageBoxState(); @@ -340,13 +400,14 @@ class _ImageBoxState extends State { final PlaygroundModel model = context.watch(); final Color handleColor = Theme.of(context).colorScheme.primary; return TransformableBox( - key: const ValueKey('image-box'), - rect: model.rect, - flip: model.flip, + key: ValueKey('image-box-${widget.imageAsset}'), + rect: widget.rect, + flip: widget.flip, clampingRect: model.clampingEnabled ? model.clampingRect : null, constraints: model.constraintsEnabled ? model.constraints : null, - onChanged: model.onRectChanged, + onChanged: widget.onChanged, resizable: model.resizable, + hideHandlesWhenNotResizable: model.hideHandlesWhenNotResizable, movable: model.movable, flipChild: model.flipChild, flipWhileResizing: model.flipRectWhileResizing, @@ -373,8 +434,8 @@ class _ImageBoxState extends State { height: rect.height, decoration: BoxDecoration( color: Colors.white, - image: const DecorationImage( - image: AssetImage('assets/images/landscape2.jpg'), + image: DecorationImage( + image: AssetImage(widget.imageAsset), fit: BoxFit.fill, ), border: Border.symmetric( @@ -646,12 +707,70 @@ class ControlPanel extends StatelessWidget { ), ), const Divider(height: 1), + Container( + height: 44, + padding: const EdgeInsets.fromLTRB(16, 0, 6, 0), + alignment: Alignment.centerLeft, + child: Row( + children: [ + Expanded( + child: Text( + 'Hide corner/side controls if not resizable', + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + SizedBox( + height: 20, + child: Transform.scale( + scale: 0.7, + child: Switch( + value: model.hideHandlesWhenNotResizable, + onChanged: (value) => + model.toggleHideHandlesWhenNotResizable(value), + ), + ), + ), + ], + ), + ), + const Divider(height: 1), const FlipControls(), const Divider(height: 1), const ClampingControls(), const Divider(height: 1), const ConstraintsControls(), const Divider(height: 1), + Container( + height: 44, + padding: const EdgeInsets.fromLTRB(16, 0, 6, 0), + alignment: Alignment.centerLeft, + child: Row( + children: [ + Expanded( + child: Text( + 'Add second image', + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + SizedBox( + height: 20, + child: Transform.scale( + scale: 0.7, + child: Switch( + value: model.showSecondImageBox, + onChanged: (value) => + model.toggleShowSecondImageBox(value), + ), + ), + ), + ], + ), + ), + const Divider(height: 1), ], ), ), @@ -665,12 +784,13 @@ class PositionControls extends StatelessWidget { @override Widget build(BuildContext context) { final PlaygroundModel model = context.watch(); - final Rect rect = model.rect; + final Rect rect = model.lastRectAdjusted == 1 ? model.rect : model.rect2; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ - const SectionHeader('POSITION'), + SectionHeader( + 'POSITION${model.lastRectAdjusted == 2 ? " of SECOND IMAGE" : ""}'), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Column( diff --git a/packages/flutter_box_transform/lib/src/transformable_box.dart b/packages/flutter_box_transform/lib/src/transformable_box.dart index f2ef4bf..482ae70 100644 --- a/packages/flutter_box_transform/lib/src/transformable_box.dart +++ b/packages/flutter_box_transform/lib/src/transformable_box.dart @@ -221,6 +221,10 @@ class TransformableBox extends StatefulWidget { /// all resizing operations. final bool resizable; + /// Whether the box should hide the corner/side resize controls when [resizable] is + /// false. + final bool hideHandlesWhenNotResizable; + /// Whether the box is movable or not. Setting this to false will disable /// all moving operations. final bool movable; @@ -263,6 +267,7 @@ class TransformableBox extends StatefulWidget { this.onTerminalHeightReached, this.onTerminalSizeReached, this.resizable = true, + this.hideHandlesWhenNotResizable = true, this.movable = true, this.flipWhileResizing = true, this.flipChild = true, @@ -336,6 +341,7 @@ class _TransformableBoxState extends State { ..constraints = widget.constraints ..resolveResizeModeCallback = widget.resolveResizeModeCallback ..resizable = widget.resizable + ..hideHandlesWhenNotResizable = widget.hideHandlesWhenNotResizable ..movable = widget.movable ..flipWhileResizing = widget.flipWhileResizing ..flipChild = widget.flipChild; @@ -370,6 +376,12 @@ class _TransformableBoxState extends State { controller.resizable = widget.resizable; } + if (oldWidget.hideHandlesWhenNotResizable != + widget.hideHandlesWhenNotResizable) { + controller.hideHandlesWhenNotResizable = + widget.hideHandlesWhenNotResizable; + } + if (oldWidget.movable != widget.movable) { controller.movable = widget.movable; } @@ -482,26 +494,28 @@ class _TransformableBoxState extends State { ), ), ), - for (final handle in HandlePosition.corners) - _CornerHandleWidget( - key: ValueKey(handle), - handlePosition: handle, - handleTapSize: widget.handleTapSize, - builder: widget.cornerHandleBuilder, - onPointerDown: onHandlePanStart, - onPointerUpdate: onHandlePanUpdate, - onPointerUp: onHandlePanEnd, - ), - for (final handle in HandlePosition.sides) - _SideHandleWidget( - key: ValueKey(handle), - handlePosition: handle, - handleTapSize: widget.handleTapSize, - builder: widget.sideHandleBuilder, - onPointerDown: onHandlePanStart, - onPointerUpdate: onHandlePanUpdate, - onPointerUp: onHandlePanEnd, - ), + if (controller.resizable || !controller.hideHandlesWhenNotResizable) + for (final handle in HandlePosition.corners) + _CornerHandleWidget( + key: ValueKey(handle), + handlePosition: handle, + handleTapSize: widget.handleTapSize, + builder: widget.cornerHandleBuilder, + onPointerDown: onHandlePanStart, + onPointerUpdate: onHandlePanUpdate, + onPointerUp: onHandlePanEnd, + ), + if (controller.resizable || !controller.hideHandlesWhenNotResizable) + for (final handle in HandlePosition.sides) + _SideHandleWidget( + key: ValueKey(handle), + handlePosition: handle, + handleTapSize: widget.handleTapSize, + builder: widget.sideHandleBuilder, + onPointerDown: onHandlePanStart, + onPointerUpdate: onHandlePanUpdate, + onPointerUp: onHandlePanEnd, + ), ], ), ); diff --git a/packages/flutter_box_transform/lib/src/transformable_box_controller.dart b/packages/flutter_box_transform/lib/src/transformable_box_controller.dart index 5eafdd4..cd047f0 100644 --- a/packages/flutter_box_transform/lib/src/transformable_box_controller.dart +++ b/packages/flutter_box_transform/lib/src/transformable_box_controller.dart @@ -74,6 +74,10 @@ class TransformableBoxController extends ChangeNotifier { /// all resizing operations. bool resizable = true; + /// Whether the box should hide the corner/side resize controls when [resizable] is + /// false. + bool hideHandlesWhenNotResizable = true; + /// Whether to allow flipping of the box while resizing. If this is set to /// true, the box will flip when the user drags the handles to opposite /// corners of the rect. @@ -143,6 +147,13 @@ class TransformableBoxController extends ChangeNotifier { notifyListeners(); } + /// Whether the box should hide the corner/side resize controls when [resizable] is + /// false. + void setHideHandlesWhenNotResizable(bool hideHandlesWhenNotResizable) { + this.hideHandlesWhenNotResizable = hideHandlesWhenNotResizable; + notifyListeners(); + } + /// Whether to allow flipping of the box while resizing. If this is set to /// true, the box will flip when the user drags the handles to opposite /// corners of the rect.