Skip to content

Child positioning offset bug #27

@arthurjwannerjr

Description

@arthurjwannerjr

Describe the bug
I'm using flutter box transform to implement a simple resizable SidePanel class and tracking the panel width with RiverPod. Everything works really great except for one small bug. When I resize the panel past minWidth or maxWidth the resizing stops like you'd expect except that if the widget is rebuilt at this point the child content gets offset horizontally.
Let's say the minWidth is set to 205. If I resize the panel, dragging the border past the minWidth and printing the rect and width inside onResizeUpdate gives
resizeUpdate: Rect.fromLTRB(31.9, -0.0, 236.9, 581.8), width: 205.0
So the actual panel width is correct(205) but the internal rect is based on the last drag offset.
At this point if I rebuild the widget or do a hot restart, the rect gets updated to be the one above, effectively offsetting the transformable box so it no longer aligns with my panel.

SidePanel class code

import 'package:flutter/material.dart';
import 'package:flutter_box_transform/flutter_box_transform.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Provider factory so each instance get's it's own state
final sidePanelWidthProvider =
    StateProvider.family<double, String>((ref, id) => 0.0);

// Note: When Panel is used with in a Column or any other uncontrained context,
//  it will break. Wrap the Panel in Expanded to fix.
class SidePanel extends ConsumerWidget {
  final double defaultWidth;
  final double minWidth;
  final double maxWidth;
  final HandlePosition handlePosition;
  final Widget child;
  final String id; // Unique identifier for this instance

  const SidePanel(
      {required this.defaultWidth,
      required this.minWidth,
      required this.maxWidth,
      required this.handlePosition,
      required this.child,
      required this.id,
      super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Use the provider factory with the unique id
    final widthProvider = sidePanelWidthProvider(id);
    double storedWidth = ref.watch(widthProvider);
    //debugPrint('$id storedWidth: $storedWidth');

    // Initialize with default width if not set
    if (storedWidth == 0.0) {
      //debugPrint('setting default width: $id');
      storedWidth = defaultWidth;
    }

    return LayoutBuilder(builder: (context, constraints) {
      //debugPrint('Constraints: $constraints');

      final Rect updatedRect =
          Rect.fromLTWH(0, 0, storedWidth, constraints.maxHeight);
      //debugPrint('Updated Rect: $updatedRect');

      return SizedBox(
        width: storedWidth,
        height: constraints.maxHeight,
        child: Stack(
          children: [
            TransformableBox(
              constraints: BoxConstraints(
                minWidth: minWidth,
                maxWidth: maxWidth,
              ),
              allowContentFlipping: false,
              allowFlippingWhileResizing: false,
              resizable: true,
              draggable: false,
              visibleHandles: {handlePosition},
              enabledHandles: {handlePosition},
              sideHandleBuilder: (context, handle) {
                return DefaultSideHandle(
                  handle: handle,
                  length: 27,
                  thickness: 9,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(10),
                    color: Theme.of(context).colorScheme.onSurface,
                    border: Border.all(
                        color: Theme.of(context).colorScheme.primary,
                        width: 1.0),
                  ),
                );
              },
              rect: updatedRect,
              onResizeUpdate: (result, event) {
                debugPrint(
                    'resizeUpdate: ${result.rect}, width: ${result.rect.width}');

                ref.read(widthProvider.notifier).state = result.rect.width;
              },
              contentBuilder: (context, rect, flip) {
                //debugPrint('contentBuilder $rect');
                return child;
              },
            ),
          ],
        ),
      );
    });
  }
}

Desktop (please complete the following information):

  • OS: Windows 11

Additional context
Also, this problem only occurs when I create a right side panel with HandlePosition.left:
SidePanel(
id: "outputPanel",
defaultWidth: 220.0,
minWidth: 205.0,
maxWidth: 500.0,
handlePosition: HandlePosition.left,
...
When creating a left side panel with HandlePosition.right, the bug does not occur.
It has something to do with the initial positioning upon rebuild.

I would think that upon rebuild the code

final Rect updatedRect =
Rect.fromLTWH(0, 0, storedWidth, constraints.maxHeight);
...
rect: updatedRect,

would negate the issue somehow, but it's as if it's setting rect from the previous rect that was offset from the drag.

If the panel is resized to not be at maxWidth or minWidth, then rebuilding the widget doesn't cause any issues.

Also, thank you for the great library, it's pretty awesome. Any help or guidance you can provide is very appreciated!

See attached video for demonstration of issue.

Recording.2024-07-02.171029.mp4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions