Skip to content

Commit

Permalink
CupertinoButton Control (#2495)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ndonkoHenri committed Jan 29, 2024
1 parent d103034 commit adb15f7
Show file tree
Hide file tree
Showing 8 changed files with 477 additions and 98 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
# VS Code
.vscode/

# Pycharm Files
.idea/

# mac specific
.DS_Store
*.bkp
Expand Down
8 changes: 8 additions & 0 deletions package/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import 'clipboard.dart';
import 'column.dart';
import 'container.dart';
import 'cupertino_alert_dialog.dart';
import 'cupertino_button.dart';
import 'cupertino_checkbox.dart';
import 'cupertino_dialog_action.dart';
import 'cupertino_list_tile.dart';
Expand Down Expand Up @@ -289,6 +290,13 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "cupertinobutton":
return CupertinoButtonControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "outlinedbutton":
return OutlinedButtonControl(
key: key,
Expand Down
100 changes: 100 additions & 0 deletions package/lib/src/controls/cupertino_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import '../models/control.dart';
import '../utils/alignment.dart';
import '../utils/borders.dart';
import '../utils/colors.dart';
import '../utils/edge_insets.dart';
import '../utils/launch_url.dart';
import 'create_control.dart';
import 'error.dart';
import 'flet_control_stateful_mixin.dart';

class CupertinoButtonControl extends StatefulWidget {
final Control? parent;
final Control control;
final List<Control> children;
final bool parentDisabled;

const CupertinoButtonControl(
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentDisabled});

@override
State<CupertinoButtonControl> createState() => _CupertinoButtonControlState();
}

class _CupertinoButtonControlState extends State<CupertinoButtonControl>
with FletControlStatefulMixin {
@override
Widget build(BuildContext context) {
debugPrint("CupertinoButton build: ${widget.control.id}");
bool disabled = widget.control.isDisabled || widget.parentDisabled;

var contentCtrls = widget.children.where((c) => c.name == "content");
if (contentCtrls.isEmpty) {
return const ErrorControl(
"CupertinoButton has no content control. Please specify one.");
}

bool filled = widget.control.attrBool("filled", false)!;
double pressedOpacity = widget.control.attrDouble("opacityOnClick", 0.4)!;
double minSize = widget.control.attrDouble("minSize", 44.0)!;
String url = widget.control.attrString("url", "")!;
EdgeInsets? padding = parseEdgeInsets(widget.control, "padding");
Color disabledColor = HexColor.fromString(Theme.of(context),
widget.control.attrString("disabledColor", "")!) ??
CupertinoColors.quaternarySystemFill;
Color? bgColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("bgColor", "")!);
AlignmentGeometry alignment =
parseAlignment(widget.control, "alignment") ?? Alignment.center;
BorderRadius borderRadius =
parseBorderRadius(widget.control, "borderRadius") ??
const BorderRadius.all(Radius.circular(8.0));

Function()? onPressed = !disabled
? () {
debugPrint("Button ${widget.control.id} clicked!");
if (url != "") {
openWebBrowser(url,
webWindowName: widget.control.attrString("urlTarget"));
}
sendControlEvent(widget.control.id, "click", "");
}
: null;

CupertinoButton? button;

button = !filled
? CupertinoButton(
onPressed: onPressed,
disabledColor: disabledColor,
color: bgColor,
padding: padding,
borderRadius: borderRadius,
pressedOpacity: pressedOpacity,
alignment: alignment,
minSize: minSize,
child:
createControl(widget.control, contentCtrls.first.id, disabled),
)
: CupertinoButton.filled(
onPressed: onPressed,
disabledColor: disabledColor,
padding: padding,
borderRadius: borderRadius,
pressedOpacity: pressedOpacity,
alignment: alignment,
minSize: minSize,
child:
createControl(widget.control, contentCtrls.first.id, disabled),
);

return constrainedControl(context, button, widget.parent, widget.control);
}
}
209 changes: 112 additions & 97 deletions package/lib/src/controls/elevated_button.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flet/src/controls/cupertino_button.dart';
import 'package:flutter/material.dart';

import '../models/control.dart';
Expand All @@ -8,6 +9,7 @@ import '../utils/launch_url.dart';
import 'create_control.dart';
import 'error.dart';
import 'flet_control_stateful_mixin.dart';
import 'flet_store_mixin.dart';

class ElevatedButtonControl extends StatefulWidget {
final Control? parent;
Expand All @@ -27,7 +29,7 @@ class ElevatedButtonControl extends StatefulWidget {
}

class _ElevatedButtonControlState extends State<ElevatedButtonControl>
with FletControlStatefulMixin {
with FletControlStatefulMixin, FletStoreMixin {
late final FocusNode _focusNode;
String? _lastFocusValue;

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

String text = widget.control.attrString("text", "")!;
String url = widget.control.attrString("url", "")!;
IconData? icon = parseIcon(widget.control.attrString("icon", "")!);
Color? iconColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("iconColor", "")!);
var contentCtrls = widget.children.where((c) => c.name == "content");
bool onHover = widget.control.attrBool("onHover", false)!;
bool onLongPress = widget.control.attrBool("onLongPress", false)!;
bool autofocus = widget.control.attrBool("autofocus", false)!;
bool disabled = widget.control.isDisabled || widget.parentDisabled;

Function()? onPressed = !disabled
? () {
debugPrint("Button ${widget.control.id} clicked!");
if (url != "") {
openWebBrowser(url,
webWindowName: widget.control.attrString("urlTarget"));
return withPagePlatform((context, platform) {
bool adaptive = widget.control.attrBool("adaptive", false)!;
if (adaptive &&
(platform == TargetPlatform.iOS ||
platform == TargetPlatform.macOS)) {
return CupertinoButtonControl(
control: widget.control,
parentDisabled: widget.parentDisabled,
children: widget.children);
}

String text = widget.control.attrString("text", "")!;
String url = widget.control.attrString("url", "")!;
IconData? icon = parseIcon(widget.control.attrString("icon", "")!);
Color? iconColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("iconColor", "")!);
var contentCtrls = widget.children.where((c) => c.name == "content");
bool onHover = widget.control.attrBool("onHover", false)!;
bool onLongPress = widget.control.attrBool("onLongPress", false)!;
bool autofocus = widget.control.attrBool("autofocus", false)!;
bool disabled = widget.control.isDisabled || widget.parentDisabled;

Function()? onPressed = !disabled
? () {
debugPrint("Button ${widget.control.id} clicked!");
if (url != "") {
openWebBrowser(url,
webWindowName: widget.control.attrString("urlTarget"));
}
sendControlEvent(widget.control.id, "click", "");
}
sendControlEvent(widget.control.id, "click", "");
}
: null;

Function()? onLongPressHandler = onLongPress && !disabled
? () {
debugPrint("Button ${widget.control.id} long pressed!");
sendControlEvent(widget.control.id, "long_press", "");
}
: null;

Function(bool)? onHoverHandler = onHover && !disabled
? (state) {
debugPrint("Button ${widget.control.id} hovered!");
sendControlEvent(widget.control.id, "hover", state.toString());
}
: null;

ElevatedButton? button;

var theme = Theme.of(context);

var style = parseButtonStyle(Theme.of(context), widget.control, "style",
defaultForegroundColor: theme.colorScheme.primary,
defaultBackgroundColor: theme.colorScheme.surface,
defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08),
defaultShadowColor: theme.colorScheme.shadow,
defaultSurfaceTintColor: theme.colorScheme.surfaceTint,
defaultElevation: 1,
defaultPadding: const EdgeInsets.symmetric(horizontal: 8),
defaultBorderSide: BorderSide.none,
defaultShape: theme.useMaterial3
? const StadiumBorder()
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));

if (icon != null) {
if (text == "") {
return const ErrorControl("Error displaying ElevatedButton",
description: "\"icon\" must be specified together with \"text\".");
: null;

Function()? onLongPressHandler = onLongPress && !disabled
? () {
debugPrint("Button ${widget.control.id} long pressed!");
sendControlEvent(widget.control.id, "long_press", "");
}
: null;

Function(bool)? onHoverHandler = onHover && !disabled
? (state) {
debugPrint("Button ${widget.control.id} hovered!");
sendControlEvent(widget.control.id, "hover", state.toString());
}
: null;

ElevatedButton? button;

var theme = Theme.of(context);

var style = parseButtonStyle(Theme.of(context), widget.control, "style",
defaultForegroundColor: theme.colorScheme.primary,
defaultBackgroundColor: theme.colorScheme.surface,
defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08),
defaultShadowColor: theme.colorScheme.shadow,
defaultSurfaceTintColor: theme.colorScheme.surfaceTint,
defaultElevation: 1,
defaultPadding: const EdgeInsets.symmetric(horizontal: 8),
defaultBorderSide: BorderSide.none,
defaultShape: theme.useMaterial3
? const StadiumBorder()
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));

if (icon != null) {
if (text == "") {
return const ErrorControl("Error displaying ElevatedButton",
description:
"\"icon\" must be specified together with \"text\".");
}
button = ElevatedButton.icon(
style: style,
autofocus: autofocus,
focusNode: _focusNode,
onPressed: onPressed,
onLongPress: onLongPressHandler,
onHover: onHoverHandler,
icon: Icon(
icon,
color: iconColor,
),
label: Text(text));
} else if (contentCtrls.isNotEmpty) {
button = ElevatedButton(
style: style,
autofocus: autofocus,
focusNode: _focusNode,
onPressed: onPressed,
onLongPress: onLongPressHandler,
onHover: onHoverHandler,
child:
createControl(widget.control, contentCtrls.first.id, disabled));
} else {
button = ElevatedButton(
style: style,
autofocus: autofocus,
focusNode: _focusNode,
onPressed: onPressed,
onLongPress: onLongPressHandler,
onHover: onHoverHandler,
child: Text(text));
}

var focusValue = widget.control.attrString("focus");
if (focusValue != null && focusValue != _lastFocusValue) {
_lastFocusValue = focusValue;
_focusNode.requestFocus();
}
button = ElevatedButton.icon(
style: style,
autofocus: autofocus,
focusNode: _focusNode,
onPressed: onPressed,
onLongPress: onLongPressHandler,
onHover: onHoverHandler,
icon: Icon(
icon,
color: iconColor,
),
label: Text(text));
} else if (contentCtrls.isNotEmpty) {
button = ElevatedButton(
style: style,
autofocus: autofocus,
focusNode: _focusNode,
onPressed: onPressed,
onLongPress: onLongPressHandler,
onHover: onHoverHandler,
child:
createControl(widget.control, contentCtrls.first.id, disabled));
} else {
button = ElevatedButton(
style: style,
autofocus: autofocus,
focusNode: _focusNode,
onPressed: onPressed,
onLongPress: onLongPressHandler,
onHover: onHoverHandler,
child: Text(text));
}

var focusValue = widget.control.attrString("focus");
if (focusValue != null && focusValue != _lastFocusValue) {
_lastFocusValue = focusValue;
_focusNode.requestFocus();
}

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

return constrainedControl(context, button, widget.parent, widget.control);
});
}
}
1 change: 1 addition & 0 deletions sdk/python/packages/flet-core/src/flet_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from flet_core.control_event import ControlEvent
from flet_core.cupertino_alert_dialog import CupertinoAlertDialog
from flet_core.cupertino_app_bar import CupertinoAppBar
from flet_core.cupertino_button import CupertinoButton
from flet_core.cupertino_checkbox import CupertinoCheckbox
from flet_core.cupertino_dialog_action import CupertinoDialogAction
from flet_core.cupertino_list_tile import CupertinoListTile
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/packages/flet-core/src/flet_core/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ def opacity(self):

@opacity.setter
def opacity(self, value):
if value is not None:
value = max(0.0, min(value, 1.0)) # make sure 0.0 <= value <= 1.0
self._set_attr("opacity", value)

# tooltip
Expand Down

0 comments on commit adb15f7

Please sign in to comment.