Skip to content

Commit 8eb8988

Browse files
feat: Add decorations for web support
1 parent c018429 commit 8eb8988

10 files changed

Lines changed: 202 additions & 14 deletions

File tree

flutter-lib/lib/src/custom/toolbar_composite.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import '../gen/toolbar.dart';
99
import '../gen/toolitem.dart';
1010
import '../gen/widgets.dart';
1111
import '../impl/composite_evolve.dart';
12+
import '../impl/decorations_evolve.dart';
1213
import '../nolayout.dart';
1314
import '../theme/theme_extensions/toolbar_theme_extension.dart';
1415
import '../theme/theme_extensions/toolitem_theme_extension.dart';
@@ -128,14 +129,15 @@ class MainToolbarCompositeImpl extends CompositeImpl<ToolbarComposite, VComposit
128129
);
129130
}
130131

131-
return Container(
132+
final toolbarBody = Container(
132133
height: toolbarHeight,
133134
decoration: BoxDecoration(color: backgroundColor),
134135
child: ToolbarAreaMarker(
135136
active: true,
136137
child: Row(
137138
crossAxisAlignment: CrossAxisAlignment.stretch,
138139
children: [
140+
const VerticalMenuButton(atStart: true),
139141
Expanded(
140142
child: Stack(
141143
key: _stackKey,
@@ -144,20 +146,23 @@ class MainToolbarCompositeImpl extends CompositeImpl<ToolbarComposite, VComposit
144146
),
145147
),
146148
if (isRootToolbar) ToolbarOptionalControlsRow(useBoundsLayout: true),
149+
const VerticalMenuButton(atStart: false),
147150
],
148151
),
149152
),
150153
);
154+
return toolbarBody;
151155
}
152156

153157
final widgets = visibleChildren.map((child) => buildMapWidgetFromValue(child)).toList();
154-
return Container(
158+
final toolbarBody = Container(
155159
decoration: BoxDecoration(color: backgroundColor),
156160
child: ToolbarAreaMarker(
157161
active: true,
158162
child: Row(
159163
crossAxisAlignment: CrossAxisAlignment.center,
160164
children: [
165+
const VerticalMenuButton(atStart: true),
161166
Expanded(
162167
child: ClipRect(
163168
child: Align(
@@ -171,10 +176,12 @@ class MainToolbarCompositeImpl extends CompositeImpl<ToolbarComposite, VComposit
171176
),
172177
),
173178
if (isRootToolbar) ToolbarOptionalControlsRow(useBoundsLayout: true),
179+
const VerticalMenuButton(atStart: false),
174180
],
175181
),
176182
),
177183
);
184+
return toolbarBody;
178185
}
179186

180187
static bool _isToolbarSeparatorCanvas(VCanvas child) {

flutter-lib/lib/src/impl/config_flags.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class ConfigFlags {
1919
bool? use_special_dropdown_button;
2020
bool? preserve_icon_colors;
2121
bool? show_scaling_control;
22+
String? decorations_align;
2223

2324
factory ConfigFlags.fromJson(Map<String, dynamic> json) =>
2425
_$ConfigFlagsFromJson(json);

flutter-lib/lib/src/impl/config_flags.g.dart

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,131 @@
1-
import 'package:flutter/widgets.dart';
1+
import 'package:flutter/material.dart';
22
import '../gen/decorations.dart';
3+
import '../gen/menu.dart';
4+
import '../gen/menuitem.dart';
35
import '../gen/widget.dart';
46
import '../impl/canvas_evolve.dart';
7+
import '../impl/menu_evolve.dart';
8+
import '../impl/widget_config.dart';
9+
import '../theme/theme_extensions/menu_theme_extension.dart';
10+
11+
class DecorationsMenuData extends InheritedWidget {
12+
final VMenu? menuBar;
13+
final bool isAtStart;
14+
15+
const DecorationsMenuData({
16+
super.key,
17+
required this.menuBar,
18+
required this.isAtStart,
19+
required super.child,
20+
});
21+
22+
static DecorationsMenuData? of(BuildContext context) =>
23+
context.dependOnInheritedWidgetOfExactType<DecorationsMenuData>();
24+
25+
@override
26+
bool updateShouldNotify(DecorationsMenuData old) =>
27+
menuBar != old.menuBar || isAtStart != old.isAtStart;
28+
}
29+
30+
class VerticalMenuButton extends StatefulWidget {
31+
final bool atStart;
32+
33+
const VerticalMenuButton({super.key, required this.atStart});
34+
35+
@override
36+
State<VerticalMenuButton> createState() => _VerticalMenuButtonState();
37+
}
38+
39+
class _VerticalMenuButtonState extends State<VerticalMenuButton> {
40+
final MenuController _controller = MenuController();
41+
final MenuState _menuState = MenuState();
42+
final List<void Function()> _pendingChanges = [];
43+
44+
void _flushChanges() {
45+
for (final cb in _pendingChanges) {
46+
cb();
47+
}
48+
_pendingChanges.clear();
49+
}
50+
51+
@override
52+
Widget build(BuildContext context) {
53+
final data = DecorationsMenuData.of(context);
54+
final menuBar = data?.menuBar;
55+
if (menuBar == null || data!.isAtStart != widget.atStart) {
56+
return const SizedBox.shrink();
57+
}
58+
59+
final menuTheme = Theme.of(context).extension<MenuThemeExtension>()!;
60+
return MenuAnchor(
61+
controller: _controller,
62+
style: MenuStyle(
63+
backgroundColor: WidgetStateProperty.all(menuTheme.popupBackgroundColor),
64+
elevation: WidgetStateProperty.all(menuTheme.popupElevation),
65+
padding: WidgetStateProperty.all(menuTheme.popupPadding),
66+
shape: WidgetStateProperty.all(
67+
RoundedRectangleBorder(
68+
borderRadius: BorderRadius.circular(menuTheme.borderRadius),
69+
),
70+
),
71+
),
72+
alignmentOffset: Offset.zero,
73+
anchorTapClosesMenu: false,
74+
onClose: _flushChanges,
75+
menuChildren: [
76+
MenuChangeNotifier(
77+
registerPendingChange: (cb) => _pendingChanges.add(cb),
78+
menuState: _menuState,
79+
closeMenu: _controller.close,
80+
child: Column(
81+
mainAxisSize: MainAxisSize.min,
82+
children: (menuBar.items ?? [])
83+
.map((item) => MenuItemSwt(value: item))
84+
.toList(),
85+
),
86+
),
87+
],
88+
builder: (context, controller, child) {
89+
return IconButton(
90+
tooltip: 'Menu',
91+
icon: const Icon(Icons.menu),
92+
onPressed: () {
93+
if (controller.isOpen) {
94+
controller.close();
95+
} else {
96+
controller.open();
97+
}
98+
},
99+
);
100+
},
101+
);
102+
}
103+
}
5104

6105
class DecorationsImpl<T extends DecorationsSwt, V extends VDecorations>
7106
extends CanvasImpl<T, V> {
8107
@override
9108
Widget build(BuildContext context) {
10-
return super.build(context);
109+
final menuMode = (getConfigFlags().decorations_align ?? "hleft").toLowerCase();
110+
final isVertical = menuMode == "vleft" || menuMode == "vright";
111+
final isAtStart = menuMode == "vleft";
112+
113+
final Widget content;
114+
if (!isVertical && state.menuBar != null) {
115+
content = Column(
116+
children: [
117+
MenuSwt(value: state.menuBar!),
118+
Expanded(child: super.build(context)),
119+
],
120+
);
121+
} else {
122+
content = super.build(context);
123+
}
124+
125+
return DecorationsMenuData(
126+
menuBar: isVertical ? state.menuBar : null,
127+
isAtStart: isAtStart,
128+
child: content,
129+
);
11130
}
12131
}

flutter-lib/lib/src/impl/menu_evolve.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import '../gen/swt.dart';
66
import '../gen/widget.dart';
77
import '../styles.dart';
88
import '../theme/theme_extensions/menu_theme_extension.dart';
9+
import 'widget_config.dart';
910
import 'utils/text_utils.dart';
1011

1112
class MenuState {
@@ -89,7 +90,7 @@ class MenuImpl<T extends MenuSwt, V extends VMenu>
8990
return _buildPopupMenu(context, widgetTheme, enabled, visible);
9091
}
9192

92-
if (!visible) {
93+
if (!isBar && !visible) {
9394
return const SizedBox.shrink();
9495
}
9596

@@ -107,6 +108,8 @@ class MenuImpl<T extends MenuSwt, V extends VMenu>
107108
bool visible,
108109
) {
109110
final menuItems = _getMenuItems();
111+
final mode = (getConfigFlags().decorations_align ?? "hleft").toLowerCase();
112+
final alignRight = mode == "hright";
110113
final backgroundColor = enabled
111114
? widgetTheme.menuBarBackgroundColor
112115
: widgetTheme.disabledBackgroundColor;
@@ -134,6 +137,8 @@ class MenuImpl<T extends MenuSwt, V extends VMenu>
134137
),
135138
),
136139
child: Row(
140+
mainAxisAlignment:
141+
alignRight ? MainAxisAlignment.end : MainAxisAlignment.start,
137142
children: menuItems
138143
.map(
139144
(item) => _MenuBarItem(

flutter-lib/lib/src/impl/menuitem_evolve.dart

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../theme/theme_extensions/menu_theme_extension.dart';
1010
import '../theme/theme_extensions/menuitem_theme_extension.dart';
1111
import '../theme/theme_settings/menuitem_theme_settings.dart';
1212
import 'menu_evolve.dart';
13+
import 'utils/image_utils.dart';
1314
import 'utils/text_utils.dart';
1415

1516
class MenuItemImpl<T extends MenuItemSwt, V extends VMenuItem>
@@ -59,6 +60,7 @@ class MenuItemImpl<T extends MenuItemSwt, V extends VMenuItem>
5960
menuTheme: menuTheme,
6061
isEnabled: isEnabled,
6162
text: state.text,
63+
leading: _buildMenuIcon(widgetTheme, isEnabled),
6264
subMenu: state.menu!,
6365
);
6466
}
@@ -97,10 +99,14 @@ class MenuItemImpl<T extends MenuItemSwt, V extends VMenuItem>
9799
widgetTheme: widgetTheme,
98100
isEnabled: isEnabled,
99101
onTap: isEnabled ? _onCheckPressed : null,
100-
leading: _MenuCheckbox(
102+
leading: _buildToggleLeading(
103+
indicator: _MenuCheckbox(
104+
widgetTheme: widgetTheme,
105+
isEnabled: isEnabled,
106+
isSelected: isChecked,
107+
),
101108
widgetTheme: widgetTheme,
102109
isEnabled: isEnabled,
103-
isSelected: isChecked,
104110
),
105111
child: Text(stripAccelerators(state.text), style: textStyle),
106112
);
@@ -118,10 +124,14 @@ class MenuItemImpl<T extends MenuItemSwt, V extends VMenuItem>
118124
widgetTheme: widgetTheme,
119125
isEnabled: isEnabled,
120126
onTap: isEnabled ? _onRadioPressed : null,
121-
leading: _MenuRadioButton(
127+
leading: _buildToggleLeading(
128+
indicator: _MenuRadioButton(
129+
widgetTheme: widgetTheme,
130+
isEnabled: isEnabled,
131+
isSelected: isSelected,
132+
),
122133
widgetTheme: widgetTheme,
123134
isEnabled: isEnabled,
124-
isSelected: isSelected,
125135
),
126136
child: Text(stripAccelerators(state.text), style: textStyle),
127137
);
@@ -154,13 +164,43 @@ class MenuItemImpl<T extends MenuItemSwt, V extends VMenuItem>
154164
notifier.closeMenu();
155165
}
156166
} : null,
167+
leading: _buildMenuIcon(widgetTheme, isEnabled),
157168
trailing: acceleratorText.isNotEmpty
158169
? Text(acceleratorText, style: acceleratorStyle)
159170
: null,
160171
child: Text(stripAccelerators(capturedState.text), style: textStyle),
161172
);
162173
}
163174

175+
Widget? _buildMenuIcon(MenuItemThemeExtension widgetTheme, bool isEnabled) {
176+
if (state.image == null) return null;
177+
return ImageUtils.buildVImage(
178+
state.image,
179+
size: widgetTheme.iconSize,
180+
color: isEnabled ? widgetTheme.iconColor : widgetTheme.disabledIconColor,
181+
enabled: isEnabled,
182+
useBinaryImage: true,
183+
renderAsIcon: true,
184+
);
185+
}
186+
187+
Widget _buildToggleLeading({
188+
required Widget indicator,
189+
required MenuItemThemeExtension widgetTheme,
190+
required bool isEnabled,
191+
}) {
192+
final icon = _buildMenuIcon(widgetTheme, isEnabled);
193+
if (icon == null) return indicator;
194+
return Row(
195+
mainAxisSize: MainAxisSize.min,
196+
children: [
197+
indicator,
198+
SizedBox(width: widgetTheme.iconTextSpacing),
199+
icon,
200+
],
201+
);
202+
}
203+
164204
void _registerRadioCallback(BuildContext context) {
165205
final notifier = MenuChangeNotifier.of(context);
166206
if (notifier != null) {
@@ -276,13 +316,15 @@ class _CascadeMenuItemRow extends StatefulWidget {
276316
final MenuThemeExtension menuTheme;
277317
final bool isEnabled;
278318
final String? text;
319+
final Widget? leading;
279320
final VMenu subMenu;
280321

281322
const _CascadeMenuItemRow({
282323
required this.widgetTheme,
283324
required this.menuTheme,
284325
required this.isEnabled,
285326
required this.text,
327+
this.leading,
286328
required this.subMenu,
287329
});
288330

@@ -394,6 +436,10 @@ class _CascadeMenuItemRowState extends State<_CascadeMenuItemRow> {
394436
menuChildren: _menuChildren,
395437
child: Row(
396438
children: [
439+
if (widget.leading != null) ...[
440+
widget.leading!,
441+
SizedBox(width: widget.widgetTheme.iconTextSpacing),
442+
],
397443
Expanded(
398444
child: Text(stripAccelerators(widget.text), style: textStyle),
399445
),

flutter-lib/lib/src/impl/styledtext_evolve.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ class StyledTextImpl<T extends StyledTextSwt, V extends VStyledText>
5353
final FocusNode _focusNode = FocusNode();
5454

5555
@override
56-
Color get bg => colorFromVColor(
57-
state.background,
58-
defaultColor: _styledTextTheme.backgroundColor,
59-
);
56+
Color get bg =>
57+
getBackgroundColor(
58+
background: state.background,
59+
defaultColor: _styledTextTheme.backgroundColor,
60+
) ??
61+
_styledTextTheme.backgroundColor;
6062

6163
@override
6264
void initState() {

0 commit comments

Comments
 (0)