Open AtlasAutocode opened 3 days ago
Hi @AtlasAutocode!
The size(pixel) and flex are independent. The flex is applied to the space not used by "size". So in your example, it isn't working because you only have one flex area.
It's worth remembering that the min in flex is not in pixels.
The problem is that if I don't include a flex then resizing does not work. The dividers do not work.
3 'size' areas => no resizing, moving the divider has no effect. Areas are frozen and cannot be adjusted with the divider.
If I add 1 flex then I get resizing of all 4 areas using the divider - but I then cannot set a minimum size for the flex area.
My app needs 3 'size' areas that have a min size - how can I get the dividers to work?
Yes, I understand that the min in flex is flex but I still need to set the minimum size in pixels. Are you saying that every time the main window changes size, I have to recalculate the min flex?
I think resizing should work with 3 size areas.
I used your configuration in the committed example, and it works as expected: fixed sizes in pixels will not allow resizing depending on the size of your window and whether it has already reached a limit (max
or min
). Pay attention to the max
limit too, for one area to shrink, the other has to stretch.
The flex
is in proportion to the window size like Column
/Row
. Min
follows the same rule. It cannot be in pixels.
If you want to adjust the min
flex
based on pixels, you will have to adjust the calculation using a LayoutBuilder
to know the size of the window. Remember that flex
uses the window size by first removing the fixed pixels used. What is left is used in the value of your factors. This flex
and pixel
scenario can generate inconsistencies, these are handled when they occur (you can see the demo). Example: you define 2 areas with 500 pixels each but your window is smaller.
It seems strange to me that you use all areas in pixels because the window can resize. The pixel is most useful for pinning a single area, such as a side menu.
For now, there is no way to set the flex min in pixels. It's either flex or sized (pixels).
If it were possible to define the min
in pixels for the flex
area, there would have to be 2 options. Like minFlex
and minPixels
. But perhaps it generates new inconsistencies.
Version 2 did allow defining 3 areas whose starting size could be set with min and max values. All 3 areas could be resized, and min size was followed.
It sounds like version 3 has changed to use size areas as fixed (not resizable) areas and min/max are ignored. That is disappointing.
Sized areas are resizable as long as there is space for it in the window. You may have a problem in your code or the window does not have enough space.
With your configuration:
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:multi_split_view/multi_split_view.dart';
void main() => runApp(const MultiSplitViewExampleApp());
class MultiSplitViewExampleApp extends StatelessWidget {
const MultiSplitViewExampleApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MultiSplitViewExample(),
);
}
}
class MultiSplitViewExample extends StatefulWidget {
const MultiSplitViewExample({Key? key}) : super(key: key);
@override
MultiSplitViewExampleState createState() => MultiSplitViewExampleState();
}
class MultiSplitViewExampleState extends State<MultiSplitViewExample> {
final MultiSplitViewController _controller = MultiSplitViewController();
bool _pushDividers = false;
@override
void initState() {
super.initState();
_controller.areas = [
Area(data: _randomColor(), size: 600, min: 100),
Area(data: _randomColor(), size: 150, min: 100, max: 400),
Area(data: _randomColor(), size: 150, min: 100)
];
_controller.addListener(_rebuild);
}
@override
void dispose() {
super.dispose();
_controller.removeListener(_rebuild);
}
void _rebuild() {
setState(() {
// rebuild to update empty text and buttons
});
}
@override
Widget build(BuildContext context) {
Widget buttons = Container(
color: Colors.white,
padding: const EdgeInsets.all(8),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 10,
runSpacing: 10,
children: [
ElevatedButton(
onPressed: _onAddFlexButtonClick,
child: const Text('Add flex')),
ElevatedButton(
onPressed: _onAddSizeButtonClick,
child: const Text('Add size')),
ElevatedButton(
onPressed: _controller.areasCount != 0
? _onRemoveFirstButtonClick
: null,
child: const Text('Remove first')),
Checkbox(
value: _pushDividers,
onChanged: (newValue) => setState(() {
_pushDividers = newValue!;
})),
const Text("Push dividers")
]));
Widget? content;
if (_controller.areasCount != 0) {
MultiSplitView multiSplitView = MultiSplitView(
onDividerDragUpdate: _onDividerDragUpdate,
onDividerTap: _onDividerTap,
onDividerDoubleTap: _onDividerDoubleTap,
controller: _controller,
pushDividers: _pushDividers,
builder: (BuildContext context, Area area) => ColorWidget(
area: area, color: area.data, onRemove: _removeColor));
content = Padding(
padding: const EdgeInsets.all(16),
child: MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerPainter: DividerPainters.grooved2()),
child: multiSplitView));
} else {
content = const Center(child: Text('Empty'));
}
return Scaffold(
appBar: AppBar(title: const Text('Multi Split View Example')),
body: Column(children: [buttons, Expanded(child: content)])
// body: horizontal,
);
}
Color _randomColor() {
Random random = Random();
return Color.fromARGB(255, 155 + random.nextInt(100),
155 + random.nextInt(100), 155 + random.nextInt(100));
}
_onDividerDragUpdate(int index) {
if (kDebugMode) {
// print('drag update: $index');
}
}
_onRemoveFirstButtonClick() {
if (_controller.areasCount != 0) {
_controller.removeAreaAt(0);
}
}
_onDividerTap(int dividerIndex) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
duration: const Duration(seconds: 1),
content: Text("Tap on divider: $dividerIndex"),
));
}
_onDividerDoubleTap(int dividerIndex) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
duration: const Duration(seconds: 1),
content: Text("Double tap on divider: $dividerIndex"),
));
}
_onAddFlexButtonClick() {
_controller.addArea(Area(data: _randomColor()));
}
_onAddSizeButtonClick() {
_controller.addArea(Area(data: _randomColor(), size: 100));
}
void _removeColor(int index) {
_controller.removeAreaAt(index);
}
}
class ColorWidget extends StatelessWidget {
const ColorWidget(
{Key? key,
required this.color,
required this.onRemove,
required this.area})
: super(key: key);
final Color color;
final Area area;
final void Function(int index) onRemove;
@override
Widget build(BuildContext context) {
List<Widget> children = [];
TextStyle textStyle = const TextStyle(fontSize: 10);
if (area.size != null) {
children.add(Text('size: ${area.size!}', style: textStyle));
}
if (area.flex != null) {
children.add(Text('flex: ${area.flex!}', style: textStyle));
}
if (area.min != null) {
children.add(Text('min: ${area.min!}', style: textStyle));
}
if (area.max != null) {
children.add(Text('max: ${area.max!}', style: textStyle));
}
Widget info = Center(
child: Container(
color: const Color.fromARGB(200, 255, 255, 255),
padding: const EdgeInsets.fromLTRB(3, 2, 3, 2),
child: Wrap(
runSpacing: 5,
spacing: 5,
crossAxisAlignment: WrapCrossAlignment.center,
children: children)));
return InkWell(
onTap: () => onRemove(area.index),
child: Container(
color: color,
child: Stack(
children: [const Placeholder(color: Colors.black), info])));
}
}
I copied your code. I get 3 areas of width: 600, 150, 464.28 The dividers do not resize the first 2 areas. I can change the width of the main window and area3 resizes. When it becomes 0, area2 shrinks. The dividers never work.
Sized areas are resizable as long as there is space for it in the window
But not resizable by the user.
The initial size of 464.28 and the shrinking behavior is as expected given the configurable inconsistency handling.
Regarding the divider that doesn't move, it seems to me that it's another problem. Maybe it's a mouse event that isn't even being triggered. Maybe something related to your Flutter or OS version? You could download the source and test it by debugging. Check whether the _onDragDown
and _onDragUpdate
methods are being executed. They are in multi_split_view.dart
. I test with Web and Linux. I don't expect Flutter to need different codes given the OS but it's worth investigating.
You will notice that within _onDragUpdate
, there is a call to DividerUtil.move
which has the logic to calculate the appropriate resizing.
I cloned your repo. Running example code - all areas resizable. Remove the flex area - dividers do not move. Both on Windows and Edge browser.
In all cases _onDrawDown and _onDrawUpdate are called even when the divider does not work. _onDrawDown creates _draggingDivider _onDrawUpdate does nothing because _draggingDivider == null
L185 sets _draggingDivider = null; called because: _lastAreasUpdateHash != controllerHelper.areasUpdateHash forces loss of divider hence divider does not work.
Restoring the flex area, now the UpdateHashes match and dividers work.
Running Dart 3.4.3, Flutter 3.22.2
:thinking:
This line L185 resets the _draggingDivider
in situations such as the window was resized, the theme was changed or some area was added/removed. So it makes sense to cancel the divider drag. But the first time the window opens, this code is executed and _lastAreasUpdateHash
should be set with:
_lastAreasUpdateHash = controllerHelper.areasUpdateHash;
This areasUpdateHash
is just an Object
to indicate that an area has been added or removed. Every method that adds or removes areas creates a new Object
.
I don't understand how on your machine it only works with flex
. The hash is not related to flex
or sized
.
When you don't use flex, the divider drag always goes into the build
method and L185 because of the _lastAreasUpdateHash != controllerHelper.areasUpdateHash
comparison?
It doesn't make sense because then we have:
_lastAreasUpdateHash = controllerHelper.areasUpdateHash;
Could you check if L139 (layout_constraints.dart) is also executed? This could create a new hash and keep this loop. This L139 is the controllerHelper.updateAreas();
inside the adjustAreas
method.
if (_lastAreasUpdateHash != controllerHelper.areasUpdateHash ||
_layoutConstraints == null ||
_layoutConstraints!.containerSize != containerSize ||
_layoutConstraints!.dividerThickness != themeData.dividerThickness) {
_draggingDivider = null;
/// KEEP HASH HERE
_lastAreasUpdateHash = controllerHelper.areasUpdateHash;
_layoutConstraints = LayoutConstraints(
controller: _controller,
containerSize: containerSize,
dividerThickness: themeData.dividerThickness);
/// BUT MAYBE RESET controllerHelper.areasUpdateHash HERE
_layoutConstraints!.adjustAreas(
controllerHelper: controllerHelper,
sizeOverflowPolicy: widget.sizeOverflowPolicy,
sizeUnderflowPolicy: widget.sizeUnderflowPolicy,
minSizeRecoveryPolicy: widget.minSizeRecoveryPolicy);
}
If you enter L139, you need to find out why changed
is TRUE
if (changed) {
controllerHelper.updateAreas();
Future.microtask(() => controllerHelper.notifyListeners());
}
L139 (layout_constraints.dart) is continuously executed
Reason: L117 is true because (totalSize < spaceForAreas && flexCount == 0) L128 adjusts the size by spaceForAreas - totalSize. This has a value: 2.87 e-7 !!! L136 changed = true forces redraw
It appears that the extremely small adjustment has no effect on the reported sizes and forces a continuous loop. There are several places in the function where adjustment is calculated as too tiny.
If I add a statement like
if ( spaceForAreas - totalSize < 1 ) {
return;
}
It now works
As an additional comment SizeUnderflowPolicy.stretchAll is best for my use SizeOverflowPolicy - would be good to have 'shrinkAll'
This wrong value is because of a floating point. :disappointed:
Bug issue: #78 shrinkAll: #77
I am upgrading from version 2.
The following settings result in 3 areas but the dividers do not work to resize the panes.
If I add in a flex area, now the dividers act to resize. The size areas obey the min and max settings. But I cannot set a minimum size for the flex area. Adding min or max to the flex area has no effect.
What am I doing wrong? How do I set a minimum size for a flex area?