Skip to content

Commit 700ce60

Browse files
Adaptive buttons (#2591)
* *Button.adaptive * CupertinoButton: text, icon, icon_color * Create .gitignore * IconButton.adaptive * Adapt CupertinoButton for IconButton's needs * Remove "package" dir * Fix AppBar in adaptive mode * Fix AppBar in cupertino mode * Fix AppBar in Pagelet control * CupertinoButton to take style from "style" property * "selected" is maintained on Python side, bgcolor from material state * Added `NavigationBar.border` property that works for adaptive mode only --------- Co-authored-by: Feodor Fitsner <feodor@appveyor.com>
1 parent e175224 commit 700ce60

File tree

16 files changed

+414
-230
lines changed

16 files changed

+414
-230
lines changed

packages/flet/lib/src/controls/create_control.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ Widget createWidget(
343343
control: controlView.control,
344344
children: controlView.children,
345345
parentDisabled: parentDisabled,
346+
parentAdaptive: parentAdaptive,
346347
backend: backend);
347348
case "cupertinodialogaction":
348349
return CupertinoDialogActionControl(

packages/flet/lib/src/controls/cupertino_button.dart

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import '../flet_control_backend.dart';
55
import '../models/control.dart';
66
import '../utils/alignment.dart';
77
import '../utils/borders.dart';
8+
import '../utils/buttons.dart';
89
import '../utils/colors.dart';
910
import '../utils/edge_insets.dart';
11+
import '../utils/icons.dart';
1012
import '../utils/launch_url.dart';
1113
import 'create_control.dart';
1214
import 'error.dart';
@@ -39,7 +41,49 @@ class _CupertinoButtonControlState extends State<CupertinoButtonControl> {
3941
bool disabled = widget.control.isDisabled || widget.parentDisabled;
4042

4143
var contentCtrls = widget.children.where((c) => c.name == "content");
42-
if (contentCtrls.isEmpty) {
44+
45+
String? text = widget.control.attrString("text");
46+
IconData? icon = parseIcon(widget.control.attrString("icon", "")!);
47+
Color? iconColor = HexColor.fromString(
48+
Theme.of(context), widget.control.attrString("iconColor", "")!);
49+
50+
// IconButton props below
51+
double? iconSize = widget.control.attrDouble("iconSize");
52+
bool selected = widget.control.attrBool("selected", false)!;
53+
IconData? selectedIcon =
54+
parseIcon(widget.control.attrString("selectedIcon", "")!);
55+
Color? selectedIconColor = HexColor.fromString(
56+
Theme.of(context), widget.control.attrString("selectedIconColor", "")!);
57+
58+
Widget? content;
59+
List<Widget> children = [];
60+
if (icon != null) {
61+
children.add(Icon(
62+
selected ? selectedIcon : icon,
63+
color: selected ? selectedIconColor : iconColor,
64+
size: iconSize,
65+
));
66+
}
67+
if (text != null) {
68+
children.add(Text(text));
69+
}
70+
71+
if (children.isNotEmpty) {
72+
if (children.length == 2) {
73+
children.insert(1, const SizedBox(width: 8));
74+
content = Row(
75+
mainAxisSize: MainAxisSize.min,
76+
children: children,
77+
);
78+
} else {
79+
content = children.first;
80+
}
81+
} else if (contentCtrls.isNotEmpty) {
82+
content = createControl(widget.control, contentCtrls.first.id, disabled,
83+
parentAdaptive: widget.parentAdaptive);
84+
}
85+
86+
if (content == null) {
4387
return const ErrorControl(
4488
"CupertinoButton has no content control. Please specify one.");
4589
}
@@ -48,7 +92,6 @@ class _CupertinoButtonControlState extends State<CupertinoButtonControl> {
4892
double pressedOpacity = widget.control.attrDouble("opacityOnClick", 0.4)!;
4993
double minSize = widget.control.attrDouble("minSize", 44.0)!;
5094
String url = widget.control.attrString("url", "")!;
51-
EdgeInsets? padding = parseEdgeInsets(widget.control, "padding");
5295
Color disabledColor = HexColor.fromString(Theme.of(context),
5396
widget.control.attrString("disabledColor", "")!) ??
5497
CupertinoColors.quaternarySystemFill;
@@ -60,9 +103,34 @@ class _CupertinoButtonControlState extends State<CupertinoButtonControl> {
60103
parseBorderRadius(widget.control, "borderRadius") ??
61104
const BorderRadius.all(Radius.circular(8.0));
62105

106+
EdgeInsets? padding = parseEdgeInsets(widget.control, "padding");
107+
108+
var theme = Theme.of(context);
109+
var style = parseButtonStyle(Theme.of(context), widget.control, "style",
110+
defaultForegroundColor: theme.colorScheme.primary,
111+
defaultBackgroundColor: Colors.transparent,
112+
defaultOverlayColor: Colors.transparent,
113+
defaultShadowColor: Colors.transparent,
114+
defaultSurfaceTintColor: Colors.transparent,
115+
defaultElevation: 0,
116+
defaultPadding: const EdgeInsets.all(8),
117+
defaultBorderSide: BorderSide.none,
118+
defaultShape: theme.useMaterial3
119+
? const StadiumBorder()
120+
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));
121+
122+
if (padding == null && style != null) {
123+
padding = style.padding?.resolve({}) as EdgeInsets?;
124+
}
125+
126+
if (bgColor == null && style != null) {
127+
bgColor = style.backgroundColor
128+
?.resolve(selected ? {MaterialState.selected} : {});
129+
}
130+
63131
Function()? onPressed = !disabled
64132
? () {
65-
debugPrint("Button ${widget.control.id} clicked!");
133+
debugPrint("CupertinoButton ${widget.control.id} clicked!");
66134
if (url != "") {
67135
openWebBrowser(url,
68136
webWindowName: widget.control.attrString("urlTarget"));
@@ -71,9 +139,7 @@ class _CupertinoButtonControlState extends State<CupertinoButtonControl> {
71139
}
72140
: null;
73141

74-
CupertinoButton? button;
75-
76-
button = !filled
142+
CupertinoButton? button = !filled
77143
? CupertinoButton(
78144
onPressed: onPressed,
79145
disabledColor: disabledColor,
@@ -83,9 +149,7 @@ class _CupertinoButtonControlState extends State<CupertinoButtonControl> {
83149
pressedOpacity: pressedOpacity,
84150
alignment: alignment,
85151
minSize: minSize,
86-
child: createControl(
87-
widget.control, contentCtrls.first.id, disabled,
88-
parentAdaptive: widget.parentAdaptive),
152+
child: content,
89153
)
90154
: CupertinoButton.filled(
91155
onPressed: onPressed,
@@ -95,9 +159,7 @@ class _CupertinoButtonControlState extends State<CupertinoButtonControl> {
95159
pressedOpacity: pressedOpacity,
96160
alignment: alignment,
97161
minSize: minSize,
98-
child: createControl(
99-
widget.control, contentCtrls.first.id, disabled,
100-
parentAdaptive: widget.parentAdaptive),
162+
child: content,
101163
);
102164

103165
return constrainedControl(context, button, widget.parent, widget.control);

packages/flet/lib/src/controls/icon_button.dart

Lines changed: 116 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import '../utils/colors.dart';
77
import '../utils/icons.dart';
88
import '../utils/launch_url.dart';
99
import 'create_control.dart';
10+
import 'cupertino_button.dart';
1011
import 'error.dart';
12+
import 'flet_store_mixin.dart';
1113

1214
class IconButtonControl extends StatefulWidget {
1315
final Control? parent;
@@ -30,7 +32,7 @@ class IconButtonControl extends StatefulWidget {
3032
State<IconButtonControl> createState() => _IconButtonControlState();
3133
}
3234

33-
class _IconButtonControlState extends State<IconButtonControl> {
35+
class _IconButtonControlState extends State<IconButtonControl> with FletStoreMixin {
3436
late final FocusNode _focusNode;
3537
String? _lastFocusValue;
3638

@@ -55,102 +57,118 @@ class _IconButtonControlState extends State<IconButtonControl> {
5557

5658
@override
5759
Widget build(BuildContext context) {
58-
debugPrint("Button build: ${widget.control.id}");
59-
60-
IconData? icon = parseIcon(widget.control.attrString("icon", "")!);
61-
IconData? selectedIcon =
62-
parseIcon(widget.control.attrString("selectedIcon", "")!);
63-
Color? iconColor = HexColor.fromString(
64-
Theme.of(context), widget.control.attrString("iconColor", "")!);
65-
Color? selectedIconColor = HexColor.fromString(
66-
Theme.of(context), widget.control.attrString("selectedIconColor", "")!);
67-
Color? bgColor = HexColor.fromString(
68-
Theme.of(context), widget.control.attrString("bgColor", "")!);
69-
double? iconSize = widget.control.attrDouble("iconSize");
70-
var tooltip = widget.control.attrString("tooltip");
71-
var contentCtrls = widget.children.where((c) => c.name == "content");
72-
bool autofocus = widget.control.attrBool("autofocus", false)!;
73-
bool selected = widget.control.attrBool("selected", false)!;
74-
String url = widget.control.attrString("url", "")!;
75-
String? urlTarget = widget.control.attrString("urlTarget");
76-
bool disabled = widget.control.isDisabled || widget.parentDisabled;
77-
78-
Function()? onPressed = disabled
79-
? null
80-
: () {
81-
debugPrint("Button ${widget.control.id} clicked!");
82-
if (url != "") {
83-
openWebBrowser(url, webWindowName: urlTarget);
84-
}
85-
widget.backend.triggerControlEvent(widget.control.id, "click", "");
86-
};
87-
88-
Widget? button;
89-
90-
var theme = Theme.of(context);
91-
92-
var style = parseButtonStyle(Theme.of(context), widget.control, "style",
93-
defaultForegroundColor: theme.colorScheme.primary,
94-
defaultBackgroundColor: Colors.transparent,
95-
defaultOverlayColor: Colors.transparent,
96-
defaultShadowColor: Colors.transparent,
97-
defaultSurfaceTintColor: Colors.transparent,
98-
defaultElevation: 0,
99-
defaultPadding: const EdgeInsets.all(8),
100-
defaultBorderSide: BorderSide.none,
101-
defaultShape: theme.useMaterial3
102-
? const StadiumBorder()
103-
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));
104-
105-
if (icon != null) {
106-
button = IconButton(
107-
autofocus: autofocus,
108-
focusNode: _focusNode,
109-
icon: Icon(
110-
icon,
111-
color: iconColor,
112-
),
113-
iconSize: iconSize,
114-
tooltip: tooltip,
115-
style: style,
116-
isSelected: selected,
117-
selectedIcon: selectedIcon != null
118-
? Icon(selectedIcon, color: selectedIconColor)
119-
: null,
120-
onPressed: onPressed);
121-
} else if (contentCtrls.isNotEmpty) {
122-
button = IconButton(
123-
autofocus: autofocus,
124-
focusNode: _focusNode,
125-
onPressed: onPressed,
126-
iconSize: iconSize,
127-
style: style,
128-
tooltip: tooltip,
129-
isSelected: selected,
130-
selectedIcon: selectedIcon != null
131-
? Icon(selectedIcon, color: selectedIconColor)
132-
: null,
133-
icon: createControl(widget.control, contentCtrls.first.id, disabled,
134-
parentAdaptive: widget.parentAdaptive));
135-
} else {
136-
return const ErrorControl(
137-
"Icon button does not have an icon neither content specified.");
138-
}
139-
140-
if (bgColor != null) {
141-
button = Container(
142-
decoration:
143-
ShapeDecoration(color: bgColor, shape: const CircleBorder()),
144-
child: button,
145-
);
146-
}
147-
148-
var focusValue = widget.control.attrString("focus");
149-
if (focusValue != null && focusValue != _lastFocusValue) {
150-
_lastFocusValue = focusValue;
151-
_focusNode.requestFocus();
152-
}
153-
154-
return constrainedControl(context, button, widget.parent, widget.control);
60+
debugPrint("IconButton build: ${widget.control.id}");
61+
62+
return withPagePlatform((context, platform) {
63+
bool? adaptive =
64+
widget.control.attrBool("adaptive") ?? widget.parentAdaptive;
65+
if (adaptive == true &&
66+
(platform == TargetPlatform.iOS ||
67+
platform == TargetPlatform.macOS)) {
68+
return CupertinoButtonControl(
69+
control: widget.control,
70+
parentDisabled: widget.parentDisabled,
71+
parentAdaptive: adaptive,
72+
children: widget.children,
73+
backend: widget.backend);
74+
}
75+
76+
IconData? icon = parseIcon(widget.control.attrString("icon", "")!);
77+
IconData? selectedIcon =
78+
parseIcon(widget.control.attrString("selectedIcon", "")!);
79+
Color? iconColor = HexColor.fromString(
80+
Theme.of(context), widget.control.attrString("iconColor", "")!);
81+
Color? selectedIconColor = HexColor.fromString(Theme.of(context),
82+
widget.control.attrString("selectedIconColor", "")!);
83+
Color? bgColor = HexColor.fromString(
84+
Theme.of(context), widget.control.attrString("bgColor", "")!);
85+
double? iconSize = widget.control.attrDouble("iconSize");
86+
var tooltip = widget.control.attrString("tooltip");
87+
var contentCtrls = widget.children.where((c) => c.name == "content");
88+
bool autofocus = widget.control.attrBool("autofocus", false)!;
89+
bool selected = widget.control.attrBool("selected", false)!;
90+
String url = widget.control.attrString("url", "")!;
91+
String? urlTarget = widget.control.attrString("urlTarget");
92+
bool disabled = widget.control.isDisabled || widget.parentDisabled;
93+
94+
Function()? onPressed = disabled
95+
? null
96+
: () {
97+
debugPrint("Button ${widget.control.id} clicked!");
98+
if (url != "") {
99+
openWebBrowser(url, webWindowName: urlTarget);
100+
}
101+
widget.backend
102+
.triggerControlEvent(widget.control.id, "click", "");
103+
};
104+
105+
Widget? button;
106+
107+
var theme = Theme.of(context);
108+
109+
var style = parseButtonStyle(Theme.of(context), widget.control, "style",
110+
defaultForegroundColor: theme.colorScheme.primary,
111+
defaultBackgroundColor: Colors.transparent,
112+
defaultOverlayColor: Colors.transparent,
113+
defaultShadowColor: Colors.transparent,
114+
defaultSurfaceTintColor: Colors.transparent,
115+
defaultElevation: 0,
116+
defaultPadding: const EdgeInsets.all(8),
117+
defaultBorderSide: BorderSide.none,
118+
defaultShape: theme.useMaterial3
119+
? const StadiumBorder()
120+
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));
121+
122+
if (icon != null) {
123+
button = IconButton(
124+
autofocus: autofocus,
125+
focusNode: _focusNode,
126+
icon: Icon(
127+
icon,
128+
color: iconColor,
129+
),
130+
iconSize: iconSize,
131+
tooltip: tooltip,
132+
style: style,
133+
isSelected: selected,
134+
selectedIcon: selectedIcon != null
135+
? Icon(selectedIcon, color: selectedIconColor)
136+
: null,
137+
onPressed: onPressed);
138+
} else if (contentCtrls.isNotEmpty) {
139+
button = IconButton(
140+
autofocus: autofocus,
141+
focusNode: _focusNode,
142+
onPressed: onPressed,
143+
iconSize: iconSize,
144+
style: style,
145+
tooltip: tooltip,
146+
isSelected: selected,
147+
selectedIcon: selectedIcon != null
148+
? Icon(selectedIcon, color: selectedIconColor)
149+
: null,
150+
icon: createControl(widget.control, contentCtrls.first.id, disabled,
151+
parentAdaptive: widget.parentAdaptive));
152+
} else {
153+
return const ErrorControl(
154+
"Icon button does not have an icon neither content specified.");
155+
}
156+
157+
if (bgColor != null) {
158+
button = Container(
159+
decoration:
160+
ShapeDecoration(color: bgColor, shape: const CircleBorder()),
161+
child: button,
162+
);
163+
}
164+
165+
var focusValue = widget.control.attrString("focus");
166+
if (focusValue != null && focusValue != _lastFocusValue) {
167+
_lastFocusValue = focusValue;
168+
_focusNode.requestFocus();
169+
}
170+
171+
return constrainedControl(context, button, widget.parent, widget.control);
172+
});
155173
}
156174
}

0 commit comments

Comments
 (0)