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
42 changes: 34 additions & 8 deletions lib/src/resizable_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class _ResizableContainerState extends State<ResizableContainer>
return Stack(
fit: StackFit.expand,
children: [
_buildOffstageMeasureLayout(),
_buildOffstageMeasureLayout(constraints),
_flexFromFullSizes(
constraints: constraints,
sizes: _animation.currentSizes!,
Expand All @@ -234,7 +234,7 @@ class _ResizableContainerState extends State<ResizableContainer>

case HideAnimationPhase.idle:
if (controller.needsLayout) {
return _buildLayout(_scheduleSetRenderedSizes);
return _buildLayout(_scheduleSetRenderedSizes, constraints);
}
return _flexFromFullSizes(
constraints: constraints,
Expand All @@ -243,18 +243,24 @@ class _ResizableContainerState extends State<ResizableContainer>
}
}

Widget _buildLayout(ValueChanged<List<double>> onComplete) {
Widget _buildLayout(
ValueChanged<List<double>> onComplete,
BoxConstraints constraints,
) {
return ResizableLayout(
direction: widget.direction,
onComplete: onComplete,
sizes: controller.sizes,
resizableChildren: widget.children,
hiddenIndices: controller.hiddenIndices,
children: _buildLayoutChildren((i) => widget.children[i].child),
children: _buildLayoutChildren(
(i) => widget.children[i].child,
constraints,
),
);
}

Widget _buildOffstageMeasureLayout() {
Widget _buildOffstageMeasureLayout(BoxConstraints constraints) {
// Run the layout offstage with placeholder children so the real widgets
// aren't inflated twice. Any [ResizableSizeShrink] entry is replaced with
// a fixed-pixel size based on the controller's last-rendered value — the
Expand All @@ -275,7 +281,10 @@ class _ResizableContainerState extends State<ResizableContainer>
sizes: overrideSizes,
resizableChildren: widget.children,
hiddenIndices: controller.hiddenIndices,
children: _buildLayoutChildren((_) => const SizedBox.shrink()),
children: _buildLayoutChildren(
(_) => const SizedBox.shrink(),
constraints,
),
),
);
}
Expand All @@ -284,14 +293,24 @@ class _ResizableContainerState extends State<ResizableContainer>
/// expects. [childBuilder] supplies the widget rendered for each child
/// slot — the real child for the live layout, a placeholder for the
/// offstage measurement.
List<Widget> _buildLayoutChildren(Widget Function(int index) childBuilder) {
List<Widget> _buildLayoutChildren(
Widget Function(int index) childBuilder,
BoxConstraints constraints,
) {
final crossAxis =
widget.direction == Axis.horizontal ? Axis.vertical : Axis.horizontal;
final crossAxisMax = constraints.maxForDirection(crossAxis);
return [
for (var i = 0; i < widget.children.length; i++) ...[
childBuilder(i),
if (i < widget.children.length - 1)
ResizableContainerDivider.placeholder(
config: widget.children[i].divider,
direction: widget.direction,
crossAxisSize: resolveDividerCrossAxisSize(
widget.children[i].divider.length,
crossAxisMax,
),
),
],
];
Expand Down Expand Up @@ -382,9 +401,16 @@ class _ResizableContainerState extends State<ResizableContainer>
if (size == 0) {
return const SizedBox.shrink();
}
final config = widget.children[dividerIndex].divider;
final crossAxis =
widget.direction == Axis.horizontal ? Axis.vertical : Axis.horizontal;
final divider = ResizableContainerDivider(
config: widget.children[dividerIndex].divider,
config: config,
direction: widget.direction,
crossAxisSize: resolveDividerCrossAxisSize(
config.length,
constraints.maxForDirection(crossAxis),
),
onResizeUpdate: (delta) => manager.adjustChildSize(
index: dividerIndex,
delta: delta,
Expand Down
134 changes: 65 additions & 69 deletions lib/src/resizable_container_divider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,33 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_resizable_container/flutter_resizable_container.dart';
import 'package:flutter_resizable_container/src/divider_painter.dart';
import 'package:flutter_resizable_container/src/resizable_divider.dart';
import 'package:flutter_resizable_container/src/resizable_size.dart';

class ResizableContainerDivider extends StatefulWidget {
const ResizableContainerDivider({
super.key,
required this.direction,
required this.config,
required this.crossAxisSize,
required void Function(double) this.onResizeUpdate,
});

const ResizableContainerDivider.placeholder({
super.key,
required this.config,
required this.direction,
required this.crossAxisSize,
}) : onResizeUpdate = null;

final Axis direction;
final void Function(double)? onResizeUpdate;
final ResizableDivider config;

/// The resolved cross-axis paint dimension for the divider, computed
/// upstream by applying [ResizableDivider.length] against the parent's
/// cross-axis max.
final double crossAxisSize;

@override
State<ResizableContainerDivider> createState() =>
_ResizableContainerDividerState();
Expand All @@ -36,53 +42,54 @@ class _ResizableContainerDividerState extends State<ResizableContainerDivider> {

@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
final width = _getWidth(constraints.maxWidth);
final height = _getHeight(constraints.maxHeight);

return Align(
alignment: switch (widget.config.crossAxisAlignment) {
CrossAxisAlignment.start => switch (widget.direction) {
Axis.horizontal => Alignment.topCenter,
Axis.vertical => Alignment.centerLeft,
},
CrossAxisAlignment.end => switch (widget.direction) {
Axis.horizontal => Alignment.bottomCenter,
Axis.vertical => Alignment.bottomRight,
},
_ => Alignment.center,
},
child: MouseRegion(
cursor: _getCursor(),
onEnter: _onEnter,
onExit: _onExit,
child: GestureDetector(
onVerticalDragStart: _onVerticalDragStart,
onVerticalDragUpdate: _onVerticalDragUpdate,
onVerticalDragEnd: _onVerticalDragEnd,
onHorizontalDragStart: _onHorizontalDragStart,
onHorizontalDragUpdate: _getOnHorizontalDragUpdate(
Directionality.of(context),
),
onHorizontalDragEnd: _onHorizontalDragEnd,
onTapDown: _onTapDown,
onTapUp: _onTapUp,
child: CustomPaint(
size: Size(width, height),
painter: DividerPainter(
direction: widget.direction,
color: widget.config.color ?? Theme.of(context).dividerColor,
thickness: widget.config.thickness,
crossAxisAlignment: widget.config.crossAxisAlignment,
length: widget.config.length,
mainAxisAlignment: widget.config.mainAxisAlignment,
padding: widget.config.padding,
),
final mainAxisSize = widget.config.thickness + widget.config.padding;
final size = switch (widget.direction) {
Axis.horizontal => Size(mainAxisSize, widget.crossAxisSize),
Axis.vertical => Size(widget.crossAxisSize, mainAxisSize),
};

return Align(
alignment: switch (widget.config.crossAxisAlignment) {
CrossAxisAlignment.start => switch (widget.direction) {
Axis.horizontal => Alignment.topCenter,
Axis.vertical => Alignment.centerLeft,
},
CrossAxisAlignment.end => switch (widget.direction) {
Axis.horizontal => Alignment.bottomCenter,
Axis.vertical => Alignment.bottomRight,
},
_ => Alignment.center,
},
child: MouseRegion(
cursor: _getCursor(),
onEnter: _onEnter,
onExit: _onExit,
child: GestureDetector(
onVerticalDragStart: _onVerticalDragStart,
onVerticalDragUpdate: _onVerticalDragUpdate,
onVerticalDragEnd: _onVerticalDragEnd,
onHorizontalDragStart: _onHorizontalDragStart,
onHorizontalDragUpdate: _getOnHorizontalDragUpdate(
Directionality.of(context),
),
onHorizontalDragEnd: _onHorizontalDragEnd,
onTapDown: _onTapDown,
onTapUp: _onTapUp,
child: CustomPaint(
size: size,
painter: DividerPainter(
direction: widget.direction,
color: widget.config.color ?? Theme.of(context).dividerColor,
thickness: widget.config.thickness,
crossAxisAlignment: widget.config.crossAxisAlignment,
length: widget.config.length,
mainAxisAlignment: widget.config.mainAxisAlignment,
padding: widget.config.padding,
),
),
),
);
});
),
);
}

MouseCursor _getCursor() {
Expand All @@ -93,30 +100,6 @@ class _ResizableContainerDividerState extends State<ResizableContainerDivider> {
};
}

double _getHeight(double maxHeight) {
return switch (widget.direction) {
Axis.horizontal => switch (widget.config.length) {
ResizableSizePixels(:final pixels) => min(pixels, maxHeight),
ResizableSizeExpand() => maxHeight,
ResizableSizeRatio(:final ratio) => maxHeight * ratio,
ResizableSizeShrink() => 0.0,
},
Axis.vertical => widget.config.thickness + widget.config.padding,
};
}

double _getWidth(double maxWidth) {
return switch (widget.direction) {
Axis.horizontal => widget.config.thickness + widget.config.padding,
Axis.vertical => switch (widget.config.length) {
ResizableSizePixels(:final pixels) => min(pixels, maxWidth),
ResizableSizeExpand() => maxWidth,
ResizableSizeRatio(:final ratio) => maxWidth * ratio,
ResizableSizeShrink() => 0.0,
},
};
}

void _onEnter(PointerEnterEvent _) {
setState(() => isHovered = true);
widget.config.onHoverEnter?.call();
Expand Down Expand Up @@ -195,3 +178,16 @@ class _ResizableContainerDividerState extends State<ResizableContainerDivider> {
widget.config.onTapUp?.call();
}
}

/// Resolves [length] against the available cross-axis [max].
///
/// Mirrors the per-arm semantics that previously lived inside the divider's
/// `LayoutBuilder`-driven `_getWidth` / `_getHeight` helpers.
double resolveDividerCrossAxisSize(ResizableSize length, double max) {
return switch (length) {
ResizableSizePixels(:final pixels) => min(pixels, max),
ResizableSizeExpand() => max,
ResizableSizeRatio(:final ratio) => max * ratio,
ResizableSizeShrink() => 0.0,
};
}
39 changes: 39 additions & 0 deletions test/resolve_divider_cross_axis_size_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter_resizable_container/flutter_resizable_container.dart';
import 'package:flutter_resizable_container/src/resizable_container_divider.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group(resolveDividerCrossAxisSize, () {
test('expand returns the full cross-axis max', () {
expect(
resolveDividerCrossAxisSize(const ResizableSize.expand(), 200),
200,
);
});

test('ratio scales the cross-axis max', () {
expect(
resolveDividerCrossAxisSize(const ResizableSize.ratio(0.25), 200),
50,
);
});

test('pixels clamps to the cross-axis max', () {
expect(
resolveDividerCrossAxisSize(const ResizableSize.pixels(50), 200),
50,
);
expect(
resolveDividerCrossAxisSize(const ResizableSize.pixels(500), 200),
200,
);
});

test('shrink resolves to zero', () {
expect(
resolveDividerCrossAxisSize(const ResizableSize.shrink(), 200),
0,
);
});
});
}