Skip to content

Commit adb15f7

Browse files
authored
CupertinoButton Control (#2495)
* initial commit * add .idea to gitignore * check opacity limits * move specifics to the top * fix opacity * prop: filled * sort imports * prop: min_size * prop: adaptive
1 parent d103034 commit adb15f7

File tree

8 files changed

+477
-98
lines changed

8 files changed

+477
-98
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
# VS Code
1919
.vscode/
2020

21+
# Pycharm Files
22+
.idea/
23+
2124
# mac specific
2225
.DS_Store
2326
*.bkp

package/lib/src/controls/create_control.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'clipboard.dart';
3131
import 'column.dart';
3232
import 'container.dart';
3333
import 'cupertino_alert_dialog.dart';
34+
import 'cupertino_button.dart';
3435
import 'cupertino_checkbox.dart';
3536
import 'cupertino_dialog_action.dart';
3637
import 'cupertino_list_tile.dart';
@@ -289,6 +290,13 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent,
289290
control: controlView.control,
290291
children: controlView.children,
291292
parentDisabled: parentDisabled);
293+
case "cupertinobutton":
294+
return CupertinoButtonControl(
295+
key: key,
296+
parent: parent,
297+
control: controlView.control,
298+
children: controlView.children,
299+
parentDisabled: parentDisabled);
292300
case "outlinedbutton":
293301
return OutlinedButtonControl(
294302
key: key,
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import 'package:flutter/cupertino.dart';
2+
import 'package:flutter/material.dart';
3+
4+
import '../models/control.dart';
5+
import '../utils/alignment.dart';
6+
import '../utils/borders.dart';
7+
import '../utils/colors.dart';
8+
import '../utils/edge_insets.dart';
9+
import '../utils/launch_url.dart';
10+
import 'create_control.dart';
11+
import 'error.dart';
12+
import 'flet_control_stateful_mixin.dart';
13+
14+
class CupertinoButtonControl extends StatefulWidget {
15+
final Control? parent;
16+
final Control control;
17+
final List<Control> children;
18+
final bool parentDisabled;
19+
20+
const CupertinoButtonControl(
21+
{super.key,
22+
this.parent,
23+
required this.control,
24+
required this.children,
25+
required this.parentDisabled});
26+
27+
@override
28+
State<CupertinoButtonControl> createState() => _CupertinoButtonControlState();
29+
}
30+
31+
class _CupertinoButtonControlState extends State<CupertinoButtonControl>
32+
with FletControlStatefulMixin {
33+
@override
34+
Widget build(BuildContext context) {
35+
debugPrint("CupertinoButton build: ${widget.control.id}");
36+
bool disabled = widget.control.isDisabled || widget.parentDisabled;
37+
38+
var contentCtrls = widget.children.where((c) => c.name == "content");
39+
if (contentCtrls.isEmpty) {
40+
return const ErrorControl(
41+
"CupertinoButton has no content control. Please specify one.");
42+
}
43+
44+
bool filled = widget.control.attrBool("filled", false)!;
45+
double pressedOpacity = widget.control.attrDouble("opacityOnClick", 0.4)!;
46+
double minSize = widget.control.attrDouble("minSize", 44.0)!;
47+
String url = widget.control.attrString("url", "")!;
48+
EdgeInsets? padding = parseEdgeInsets(widget.control, "padding");
49+
Color disabledColor = HexColor.fromString(Theme.of(context),
50+
widget.control.attrString("disabledColor", "")!) ??
51+
CupertinoColors.quaternarySystemFill;
52+
Color? bgColor = HexColor.fromString(
53+
Theme.of(context), widget.control.attrString("bgColor", "")!);
54+
AlignmentGeometry alignment =
55+
parseAlignment(widget.control, "alignment") ?? Alignment.center;
56+
BorderRadius borderRadius =
57+
parseBorderRadius(widget.control, "borderRadius") ??
58+
const BorderRadius.all(Radius.circular(8.0));
59+
60+
Function()? onPressed = !disabled
61+
? () {
62+
debugPrint("Button ${widget.control.id} clicked!");
63+
if (url != "") {
64+
openWebBrowser(url,
65+
webWindowName: widget.control.attrString("urlTarget"));
66+
}
67+
sendControlEvent(widget.control.id, "click", "");
68+
}
69+
: null;
70+
71+
CupertinoButton? button;
72+
73+
button = !filled
74+
? CupertinoButton(
75+
onPressed: onPressed,
76+
disabledColor: disabledColor,
77+
color: bgColor,
78+
padding: padding,
79+
borderRadius: borderRadius,
80+
pressedOpacity: pressedOpacity,
81+
alignment: alignment,
82+
minSize: minSize,
83+
child:
84+
createControl(widget.control, contentCtrls.first.id, disabled),
85+
)
86+
: CupertinoButton.filled(
87+
onPressed: onPressed,
88+
disabledColor: disabledColor,
89+
padding: padding,
90+
borderRadius: borderRadius,
91+
pressedOpacity: pressedOpacity,
92+
alignment: alignment,
93+
minSize: minSize,
94+
child:
95+
createControl(widget.control, contentCtrls.first.id, disabled),
96+
);
97+
98+
return constrainedControl(context, button, widget.parent, widget.control);
99+
}
100+
}
Lines changed: 112 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:flet/src/controls/cupertino_button.dart';
12
import 'package:flutter/material.dart';
23

34
import '../models/control.dart';
@@ -8,6 +9,7 @@ import '../utils/launch_url.dart';
89
import 'create_control.dart';
910
import 'error.dart';
1011
import 'flet_control_stateful_mixin.dart';
12+
import 'flet_store_mixin.dart';
1113

1214
class ElevatedButtonControl extends StatefulWidget {
1315
final Control? parent;
@@ -27,7 +29,7 @@ class ElevatedButtonControl extends StatefulWidget {
2729
}
2830

2931
class _ElevatedButtonControlState extends State<ElevatedButtonControl>
30-
with FletControlStatefulMixin {
32+
with FletControlStatefulMixin, FletStoreMixin {
3133
late final FocusNode _focusNode;
3234
String? _lastFocusValue;
3335

@@ -54,103 +56,116 @@ class _ElevatedButtonControlState extends State<ElevatedButtonControl>
5456
Widget build(BuildContext context) {
5557
debugPrint("Button build: ${widget.control.id}");
5658

57-
String text = widget.control.attrString("text", "")!;
58-
String url = widget.control.attrString("url", "")!;
59-
IconData? icon = parseIcon(widget.control.attrString("icon", "")!);
60-
Color? iconColor = HexColor.fromString(
61-
Theme.of(context), widget.control.attrString("iconColor", "")!);
62-
var contentCtrls = widget.children.where((c) => c.name == "content");
63-
bool onHover = widget.control.attrBool("onHover", false)!;
64-
bool onLongPress = widget.control.attrBool("onLongPress", false)!;
65-
bool autofocus = widget.control.attrBool("autofocus", false)!;
66-
bool disabled = widget.control.isDisabled || widget.parentDisabled;
67-
68-
Function()? onPressed = !disabled
69-
? () {
70-
debugPrint("Button ${widget.control.id} clicked!");
71-
if (url != "") {
72-
openWebBrowser(url,
73-
webWindowName: widget.control.attrString("urlTarget"));
59+
return withPagePlatform((context, platform) {
60+
bool adaptive = widget.control.attrBool("adaptive", false)!;
61+
if (adaptive &&
62+
(platform == TargetPlatform.iOS ||
63+
platform == TargetPlatform.macOS)) {
64+
return CupertinoButtonControl(
65+
control: widget.control,
66+
parentDisabled: widget.parentDisabled,
67+
children: widget.children);
68+
}
69+
70+
String text = widget.control.attrString("text", "")!;
71+
String url = widget.control.attrString("url", "")!;
72+
IconData? icon = parseIcon(widget.control.attrString("icon", "")!);
73+
Color? iconColor = HexColor.fromString(
74+
Theme.of(context), widget.control.attrString("iconColor", "")!);
75+
var contentCtrls = widget.children.where((c) => c.name == "content");
76+
bool onHover = widget.control.attrBool("onHover", false)!;
77+
bool onLongPress = widget.control.attrBool("onLongPress", false)!;
78+
bool autofocus = widget.control.attrBool("autofocus", false)!;
79+
bool disabled = widget.control.isDisabled || widget.parentDisabled;
80+
81+
Function()? onPressed = !disabled
82+
? () {
83+
debugPrint("Button ${widget.control.id} clicked!");
84+
if (url != "") {
85+
openWebBrowser(url,
86+
webWindowName: widget.control.attrString("urlTarget"));
87+
}
88+
sendControlEvent(widget.control.id, "click", "");
7489
}
75-
sendControlEvent(widget.control.id, "click", "");
76-
}
77-
: null;
78-
79-
Function()? onLongPressHandler = onLongPress && !disabled
80-
? () {
81-
debugPrint("Button ${widget.control.id} long pressed!");
82-
sendControlEvent(widget.control.id, "long_press", "");
83-
}
84-
: null;
85-
86-
Function(bool)? onHoverHandler = onHover && !disabled
87-
? (state) {
88-
debugPrint("Button ${widget.control.id} hovered!");
89-
sendControlEvent(widget.control.id, "hover", state.toString());
90-
}
91-
: null;
92-
93-
ElevatedButton? button;
94-
95-
var theme = Theme.of(context);
96-
97-
var style = parseButtonStyle(Theme.of(context), widget.control, "style",
98-
defaultForegroundColor: theme.colorScheme.primary,
99-
defaultBackgroundColor: theme.colorScheme.surface,
100-
defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08),
101-
defaultShadowColor: theme.colorScheme.shadow,
102-
defaultSurfaceTintColor: theme.colorScheme.surfaceTint,
103-
defaultElevation: 1,
104-
defaultPadding: const EdgeInsets.symmetric(horizontal: 8),
105-
defaultBorderSide: BorderSide.none,
106-
defaultShape: theme.useMaterial3
107-
? const StadiumBorder()
108-
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));
109-
110-
if (icon != null) {
111-
if (text == "") {
112-
return const ErrorControl("Error displaying ElevatedButton",
113-
description: "\"icon\" must be specified together with \"text\".");
90+
: null;
91+
92+
Function()? onLongPressHandler = onLongPress && !disabled
93+
? () {
94+
debugPrint("Button ${widget.control.id} long pressed!");
95+
sendControlEvent(widget.control.id, "long_press", "");
96+
}
97+
: null;
98+
99+
Function(bool)? onHoverHandler = onHover && !disabled
100+
? (state) {
101+
debugPrint("Button ${widget.control.id} hovered!");
102+
sendControlEvent(widget.control.id, "hover", state.toString());
103+
}
104+
: null;
105+
106+
ElevatedButton? button;
107+
108+
var theme = Theme.of(context);
109+
110+
var style = parseButtonStyle(Theme.of(context), widget.control, "style",
111+
defaultForegroundColor: theme.colorScheme.primary,
112+
defaultBackgroundColor: theme.colorScheme.surface,
113+
defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08),
114+
defaultShadowColor: theme.colorScheme.shadow,
115+
defaultSurfaceTintColor: theme.colorScheme.surfaceTint,
116+
defaultElevation: 1,
117+
defaultPadding: const EdgeInsets.symmetric(horizontal: 8),
118+
defaultBorderSide: BorderSide.none,
119+
defaultShape: theme.useMaterial3
120+
? const StadiumBorder()
121+
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));
122+
123+
if (icon != null) {
124+
if (text == "") {
125+
return const ErrorControl("Error displaying ElevatedButton",
126+
description:
127+
"\"icon\" must be specified together with \"text\".");
128+
}
129+
button = ElevatedButton.icon(
130+
style: style,
131+
autofocus: autofocus,
132+
focusNode: _focusNode,
133+
onPressed: onPressed,
134+
onLongPress: onLongPressHandler,
135+
onHover: onHoverHandler,
136+
icon: Icon(
137+
icon,
138+
color: iconColor,
139+
),
140+
label: Text(text));
141+
} else if (contentCtrls.isNotEmpty) {
142+
button = ElevatedButton(
143+
style: style,
144+
autofocus: autofocus,
145+
focusNode: _focusNode,
146+
onPressed: onPressed,
147+
onLongPress: onLongPressHandler,
148+
onHover: onHoverHandler,
149+
child:
150+
createControl(widget.control, contentCtrls.first.id, disabled));
151+
} else {
152+
button = ElevatedButton(
153+
style: style,
154+
autofocus: autofocus,
155+
focusNode: _focusNode,
156+
onPressed: onPressed,
157+
onLongPress: onLongPressHandler,
158+
onHover: onHoverHandler,
159+
child: Text(text));
160+
}
161+
162+
var focusValue = widget.control.attrString("focus");
163+
if (focusValue != null && focusValue != _lastFocusValue) {
164+
_lastFocusValue = focusValue;
165+
_focusNode.requestFocus();
114166
}
115-
button = ElevatedButton.icon(
116-
style: style,
117-
autofocus: autofocus,
118-
focusNode: _focusNode,
119-
onPressed: onPressed,
120-
onLongPress: onLongPressHandler,
121-
onHover: onHoverHandler,
122-
icon: Icon(
123-
icon,
124-
color: iconColor,
125-
),
126-
label: Text(text));
127-
} else if (contentCtrls.isNotEmpty) {
128-
button = ElevatedButton(
129-
style: style,
130-
autofocus: autofocus,
131-
focusNode: _focusNode,
132-
onPressed: onPressed,
133-
onLongPress: onLongPressHandler,
134-
onHover: onHoverHandler,
135-
child:
136-
createControl(widget.control, contentCtrls.first.id, disabled));
137-
} else {
138-
button = ElevatedButton(
139-
style: style,
140-
autofocus: autofocus,
141-
focusNode: _focusNode,
142-
onPressed: onPressed,
143-
onLongPress: onLongPressHandler,
144-
onHover: onHoverHandler,
145-
child: Text(text));
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);
167+
168+
return constrainedControl(context, button, widget.parent, widget.control);
169+
});
155170
}
156171
}

sdk/python/packages/flet-core/src/flet_core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from flet_core.control_event import ControlEvent
6767
from flet_core.cupertino_alert_dialog import CupertinoAlertDialog
6868
from flet_core.cupertino_app_bar import CupertinoAppBar
69+
from flet_core.cupertino_button import CupertinoButton
6970
from flet_core.cupertino_checkbox import CupertinoCheckbox
7071
from flet_core.cupertino_dialog_action import CupertinoDialogAction
7172
from flet_core.cupertino_list_tile import CupertinoListTile

sdk/python/packages/flet-core/src/flet_core/control.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ def opacity(self):
227227

228228
@opacity.setter
229229
def opacity(self, value):
230+
if value is not None:
231+
value = max(0.0, min(value, 1.0)) # make sure 0.0 <= value <= 1.0
230232
self._set_attr("opacity", value)
231233

232234
# tooltip

0 commit comments

Comments
 (0)