fix(resize): tighten drag scope, simplify divider, clamp width, handle RTL

- build panel subtree outside the width-reactive Obx
- replace redundant DraggableDivider with MouseRegion and 12px grab strip
- clamp rendered width via LayoutBuilder so a wide panel can't overflow a narrow window
- flip drag delta by Directionality so resizing isn't inverted in RTL

Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
This commit is contained in:
StealUrKill 2026-06-19 11:18:53 -05:00
commit fad445ca16
3 changed files with 95 additions and 74 deletions

View file

@ -76,38 +76,43 @@ class _AddressBookState extends State<AddressBook> {
});
Widget _buildAddressBookLandscape() {
return Row(
children: [
Offstage(
offstage: hideAbTagsPanel.value,
child: Obx(() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).colorScheme.background)),
width: _tagsPanel.width.value,
height: double.infinity,
child: Column(
children: [
_buildAbDropdown(),
_buildTagHeader().marginOnly(
left: 8.0,
right: gFFI.abModel.legacyMode.value ? 8.0 : 0,
top: gFFI.abModel.legacyMode.value ? 8.0 : 0),
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
child: _buildTags(),
),
),
_buildAbPermission(),
],
),
))),
if (!hideAbTagsPanel.value) _tagsPanel.buildDivider(),
_buildPeersViews()
],
final panel = Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border:
Border.all(color: Theme.of(context).colorScheme.background)),
height: double.infinity,
child: Column(
children: [
_buildAbDropdown(),
_buildTagHeader().marginOnly(
left: 8.0,
right: gFFI.abModel.legacyMode.value ? 8.0 : 0,
top: gFFI.abModel.legacyMode.value ? 8.0 : 0),
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
child: _buildTags(),
),
),
_buildAbPermission(),
],
),
);
return LayoutBuilder(
builder: (context, constraints) => Row(
children: [
Offstage(
offstage: hideAbTagsPanel.value,
child: Obx(() => SizedBox(
width: _tagsPanel.effectiveWidth(constraints.maxWidth),
child: panel,
))),
if (!hideAbTagsPanel.value) _tagsPanel.buildDivider(context),
_buildPeersViews()
],
),
);
}

View file

@ -63,39 +63,42 @@ class _MyGroupState extends State<MyGroup> {
}
Widget _buildLandscape() {
return Row(
children: [
Obx(
() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).colorScheme.background)),
width: _devicesPanel.width.value,
height: double.infinity,
child: Column(
children: [
_buildLeftHeader(),
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
child: _buildLeftList(),
),
)
],
final panel = Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border:
Border.all(color: Theme.of(context).colorScheme.background)),
height: double.infinity,
child: Column(
children: [
_buildLeftHeader(),
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
child: _buildLeftList(),
),
),
),
_devicesPanel.buildDivider(),
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: MyGroupPeerView(
menuPadding: widget.menuPadding,
)
],
),
);
return LayoutBuilder(
builder: (context, constraints) => Row(
children: [
Obx(() => SizedBox(
width: _devicesPanel.effectiveWidth(constraints.maxWidth),
child: panel,
)),
)
],
_devicesPanel.buildDivider(context),
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: MyGroupPeerView(
menuPadding: widget.menuPadding,
)),
)
],
),
);
}

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/widgets/dragable_divider.dart';
import 'dart:math';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
@ -14,6 +14,9 @@ class ResizablePanelController {
final double maxWidth;
late final RxDouble width;
static const double dividerHitWidth = 12.0;
static const double minContentWidth = 120.0;
ResizablePanelController({
required this.optionKey,
required this.defaultWidth,
@ -29,21 +32,31 @@ class ResizablePanelController {
width.value = (width.value + dx).clamp(minWidth, maxWidth).toDouble();
}
double effectiveWidth(double available) {
if (!available.isFinite) return width.value;
final maxAllowed = available - dividerHitWidth - minContentWidth;
return width.value.clamp(minWidth, max(minWidth, maxAllowed)).toDouble();
}
void _persist() {
bind.mainSetLocalOption(
key: optionKey, value: width.value.toStringAsFixed(0));
}
Widget buildDivider() {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onHorizontalDragUpdate: (details) => _onDrag(details.delta.dx),
onHorizontalDragEnd: (_) => _persist(),
onHorizontalDragCancel: _persist,
child: DraggableDivider(
axis: Axis.vertical,
padding: const EdgeInsets.symmetric(horizontal: 4.0),
color: Colors.transparent,
Widget buildDivider(BuildContext context) {
final sign = Directionality.of(context) == TextDirection.rtl ? -1.0 : 1.0;
return MouseRegion(
cursor: SystemMouseCursors.resizeLeftRight,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onHorizontalDragUpdate: (details) => _onDrag(sign * details.delta.dx),
onHorizontalDragEnd: (_) => _persist(),
onHorizontalDragCancel: _persist,
child: Container(
width: dividerHitWidth,
height: double.infinity,
color: Colors.transparent,
),
),
);
}