diff --git a/package/lib/src/controls/checkbox.dart b/package/lib/src/controls/checkbox.dart index 8e6707767..3ffc3e7a2 100644 --- a/package/lib/src/controls/checkbox.dart +++ b/package/lib/src/controls/checkbox.dart @@ -1,3 +1,5 @@ +import 'package:flet/src/controls/cupertino_checkbox.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../actions.dart'; @@ -69,7 +71,6 @@ class _CheckboxControlState extends State { void _onChange(bool? value) { var svalue = value != null ? value.toString() : ""; - //debugPrint(svalue); setState(() { _value = value; }); @@ -87,6 +88,15 @@ class _CheckboxControlState extends State { @override Widget build(BuildContext context) { debugPrint("Checkbox build: ${widget.control.id}"); + bool adaptive = widget.control.attrBool("adaptive", false)!; + if (adaptive && + (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS)) { + return CupertinoCheckboxControl( + control: widget.control, + parentDisabled: widget.parentDisabled, + dispatch: widget.dispatch); + } String label = widget.control.attrString("label", "")!; LabelPosition labelPosition = LabelPosition.values.firstWhere( @@ -96,6 +106,7 @@ class _CheckboxControlState extends State { orElse: () => LabelPosition.right); _tristate = widget.control.attrBool("tristate", false)!; bool autofocus = widget.control.attrBool("autofocus", false)!; + bool disabled = widget.control.isDisabled || widget.parentDisabled; debugPrint("Checkbox StoreConnector build: ${widget.control.id}"); @@ -109,6 +120,14 @@ class _CheckboxControlState extends State { autofocus: autofocus, focusNode: _focusNode, value: _value, + activeColor: HexColor.fromString( + Theme.of(context), widget.control.attrString("activeColor", "")!), + focusColor: HexColor.fromString( + Theme.of(context), widget.control.attrString("focusColor", "")!), + hoverColor: HexColor.fromString( + Theme.of(context), widget.control.attrString("hoverColor", "")!), + overlayColor: parseMaterialStateColor( + Theme.of(context), widget.control, "overlayColor"), checkColor: HexColor.fromString( Theme.of(context), widget.control.attrString("checkColor", "")!), fillColor: parseMaterialStateColor( diff --git a/package/lib/src/controls/create_control.dart b/package/lib/src/controls/create_control.dart index 61db6b2ed..88d525ba0 100644 --- a/package/lib/src/controls/create_control.dart +++ b/package/lib/src/controls/create_control.dart @@ -84,6 +84,7 @@ import 'transparent_pointer.dart'; import 'vertical_divider.dart'; import 'webview.dart'; import 'window_drag_area.dart'; +import 'cupertino_checkbox.dart'; Widget createControl(Control? parent, String id, bool parentDisabled, {Widget? nextChild}) { @@ -477,6 +478,13 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent, control: controlView.control, parentDisabled: parentDisabled, dispatch: controlView.dispatch); + case "cupertinocheckbox": + return CupertinoCheckboxControl( + key: key, + parent: parent, + control: controlView.control, + parentDisabled: parentDisabled, + dispatch: controlView.dispatch); case "switch": return SwitchControl( key: key, diff --git a/package/lib/src/controls/cupertino_checkbox.dart b/package/lib/src/controls/cupertino_checkbox.dart new file mode 100644 index 000000000..c268524e7 --- /dev/null +++ b/package/lib/src/controls/cupertino_checkbox.dart @@ -0,0 +1,146 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../actions.dart'; +import '../flet_app_services.dart'; +import '../models/control.dart'; +import '../protocol/update_control_props_payload.dart'; +import '../utils/colors.dart'; +import 'create_control.dart'; +import 'list_tile.dart'; + +enum LabelPosition { right, left } + +class CupertinoCheckboxControl extends StatefulWidget { + final Control? parent; + final Control control; + final bool parentDisabled; + final dynamic dispatch; + + const CupertinoCheckboxControl( + {Key? key, + this.parent, + required this.control, + required this.parentDisabled, + required this.dispatch}) + : super(key: key); + + @override + State createState() => _CheckboxControlState(); +} + +class _CheckboxControlState extends State { + bool? _value; + bool _tristate = false; + late final FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + _focusNode.addListener(_onFocusChange); + } + + void _onFocusChange() { + FletAppServices.of(context).server.sendPageEvent( + eventTarget: widget.control.id, + eventName: _focusNode.hasFocus ? "focus" : "blur", + eventData: ""); + } + + @override + void dispose() { + _focusNode.removeListener(_onFocusChange); + _focusNode.dispose(); + super.dispose(); + } + + void _toggleValue() { + bool? newValue; + if (!_tristate) { + newValue = !_value!; + } else if (_tristate && _value == null) { + newValue = false; + } else if (_tristate && _value == false) { + newValue = true; + } + _onChange(newValue); + } + + void _onChange(bool? value) { + var svalue = value != null ? value.toString() : ""; + setState(() { + _value = value; + }); + List> props = [ + {"i": widget.control.id, "value": svalue} + ]; + widget.dispatch( + UpdateControlPropsAction(UpdateControlPropsPayload(props: props))); + var server = FletAppServices.of(context).server; + server.updateControlProps(props: props); + server.sendPageEvent( + eventTarget: widget.control.id, eventName: "change", eventData: svalue); + } + + @override + Widget build(BuildContext context) { + debugPrint("CupertinoCheckBox build: ${widget.control.id}"); + + String label = widget.control.attrString("label", "")!; + LabelPosition labelPosition = LabelPosition.values.firstWhere( + (p) => + p.name.toLowerCase() == + widget.control.attrString("labelPosition", "")!.toLowerCase(), + orElse: () => LabelPosition.right); + _tristate = widget.control.attrBool("tristate", false)!; + bool autofocus = widget.control.attrBool("autofocus", false)!; + bool disabled = widget.control.isDisabled || widget.parentDisabled; + + debugPrint("CupertinoCheckbox StoreConnector build: ${widget.control.id}"); + + bool? value = widget.control.attrBool("value", _tristate ? null : false); + if (_value != value) { + _value = value; + } + + var cupertinoCheckbox = CupertinoCheckbox( + autofocus: autofocus, + focusNode: _focusNode, + value: _value, + activeColor: HexColor.fromString( + Theme.of(context), widget.control.attrString("activeColor", "")!), + checkColor: HexColor.fromString( + Theme.of(context), widget.control.attrString("checkColor", "")!), + focusColor: HexColor.fromString( + Theme.of(context), widget.control.attrString("focusColor", "")!), + inactiveColor: HexColor.fromString( + Theme.of(context), widget.control.attrString("inactiveColor", "")!), + tristate: _tristate, + onChanged: !disabled + ? (bool? value) { + _onChange(value); + } + : null); + + ListTileClicks.of(context)?.notifier.addListener(() { + _toggleValue(); + }); + + Widget result = cupertinoCheckbox; + if (label != "") { + var labelWidget = disabled + ? Text(label, + style: TextStyle(color: Theme.of(context).disabledColor)) + : MouseRegion(cursor: SystemMouseCursors.click, child: Text(label)); + result = MergeSemantics( + child: GestureDetector( + onTap: !disabled ? _toggleValue : null, + child: labelPosition == LabelPosition.right + ? Row(children: [cupertinoCheckbox, labelWidget]) + : Row(children: [labelWidget, cupertinoCheckbox]))); + } + + return constrainedControl(context, result, widget.parent, widget.control); + } +} diff --git a/sdk/python/packages/flet-core/src/flet_core/__init__.py b/sdk/python/packages/flet-core/src/flet_core/__init__.py index bae73909c..8bccbf29c 100644 --- a/sdk/python/packages/flet-core/src/flet_core/__init__.py +++ b/sdk/python/packages/flet-core/src/flet_core/__init__.py @@ -225,3 +225,4 @@ from flet_core.badge import Badge from flet_core.navigation_drawer import NavigationDrawer, NavigationDrawerDestination from flet_core.selection_area import SelectionArea +from flet_core.cupertino_checkbox import CupertinoCheckbox diff --git a/sdk/python/packages/flet-core/src/flet_core/checkbox.py b/sdk/python/packages/flet-core/src/flet_core/checkbox.py index 4f4ea1d1c..0af246cde 100644 --- a/sdk/python/packages/flet-core/src/flet_core/checkbox.py +++ b/sdk/python/packages/flet-core/src/flet_core/checkbox.py @@ -14,11 +14,6 @@ ScaleValue, ) -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - class Checkbox(ConstrainedControl): """ @@ -91,7 +86,12 @@ def __init__( tristate: Optional[bool] = None, autofocus: Optional[bool] = None, fill_color: Union[None, str, Dict[MaterialState, str]] = None, + overlay_color: Union[None, str, Dict[MaterialState, str]] = None, check_color: Optional[str] = None, + active_color: Optional[str] = None, + hover_color: Optional[str] = None, + focus_color: Optional[str] = None, + adaptive: Optional[bool] = None, on_change=None, on_focus=None, on_blur=None, @@ -132,6 +132,11 @@ def __init__( self.autofocus = autofocus self.check_color = check_color self.fill_color = fill_color + self.focus_color = focus_color + self.hover_color = hover_color + self.overlay_color = overlay_color + self.active_color = active_color + self.adaptive = adaptive self.on_change = on_change self.on_focus = on_focus self.on_blur = on_blur @@ -142,6 +147,7 @@ def _get_control_name(self): def _before_build_command(self): super()._before_build_command() self._set_attr_json("fillColor", self.__fill_color) + self._set_attr_json("overlayColor", self.__overlay_color) # value @property @@ -163,6 +169,15 @@ def tristate(self) -> Optional[bool]: def tristate(self, value: Optional[bool]): self._set_attr("tristate", value) + # adaptive + @property + def adaptive(self) -> Optional[bool]: + return self._get_attr("adaptive", data_type="bool", def_value=False) + + @adaptive.setter + def adaptive(self, value: Optional[bool]): + self._set_attr("adaptive", value) + # label @property def label(self): @@ -206,6 +221,33 @@ def check_color(self): def check_color(self, value): self._set_attr("checkColor", value) + # active_color + @property + def active_color(self): + return self._get_attr("activeColor") + + @active_color.setter + def active_color(self, value): + self._set_attr("activeColor", value) + + # focus_color + @property + def focus_color(self): + return self._get_attr("focusColor") + + @focus_color.setter + def focus_color(self, value): + self._set_attr("focusColor", value) + + # hover_color + @property + def hover_color(self): + return self._get_attr("hoverColor") + + @hover_color.setter + def hover_color(self, value): + self._set_attr("hoverColor", value) + # fill_color @property def fill_color(self) -> Union[None, str, Dict[MaterialState, str]]: @@ -215,6 +257,15 @@ def fill_color(self) -> Union[None, str, Dict[MaterialState, str]]: def fill_color(self, value: Union[None, str, Dict[MaterialState, str]]): self.__fill_color = value + # overlay_color + @property + def overlay_color(self) -> Union[None, str, Dict[MaterialState, str]]: + return self.__overlay_color + + @overlay_color.setter + def overlay_color(self, value: Union[None, str, Dict[MaterialState, str]]): + self.__overlay_color = value + # on_change @property def on_change(self): diff --git a/sdk/python/packages/flet-core/src/flet_core/cupertino_checkbox.py b/sdk/python/packages/flet-core/src/flet_core/cupertino_checkbox.py new file mode 100644 index 000000000..4010cbb57 --- /dev/null +++ b/sdk/python/packages/flet-core/src/flet_core/cupertino_checkbox.py @@ -0,0 +1,245 @@ +from typing import Any, Dict, Optional, Union + +from flet_core.constrained_control import ConstrainedControl +from flet_core.control import OptionalNumber +from flet_core.ref import Ref +from flet_core.types import ( + AnimationValue, + LabelPosition, + LabelPositionString, + OffsetValue, + ResponsiveNumber, + RotateValue, + ScaleValue, +) + + +class CupertinoCheckbox(ConstrainedControl): + """ + A macOS style checkbox. Checkbox allows to select one or more items from a group, or switch between two mutually exclusive options (checked or unchecked, on or off). + + Example: + ``` + import flet as ft + + def main(page): + c = ft.CupertinoCheckbox( + label="Cupertino Checkbox", + active_color=ft.colors.GREEN, + inactive_color=ft.colors.RED, + check_color=ft.colors.BLUE, + ), + page.add(c) + + ft.app(target=main) + ``` + + ----- + Online docs: https://flet.dev/docs/controls/cupertinocheckbox + """ + + def __init__( + self, + ref: Optional[Ref] = None, + key: Optional[str] = None, + width: OptionalNumber = None, + height: OptionalNumber = None, + left: OptionalNumber = None, + top: OptionalNumber = None, + right: OptionalNumber = None, + bottom: OptionalNumber = None, + expand: Union[None, bool, int] = None, + col: Optional[ResponsiveNumber] = None, + opacity: OptionalNumber = None, + rotate: RotateValue = None, + scale: ScaleValue = None, + offset: OffsetValue = None, + aspect_ratio: OptionalNumber = None, + animate_opacity: AnimationValue = None, + animate_size: AnimationValue = None, + animate_position: AnimationValue = None, + animate_rotation: AnimationValue = None, + animate_scale: AnimationValue = None, + animate_offset: AnimationValue = None, + on_animation_end=None, + tooltip: Optional[str] = None, + visible: Optional[bool] = None, + disabled: Optional[bool] = None, + data: Any = None, + # + # Specific + # + label: Optional[str] = None, + label_position: LabelPosition = LabelPosition.NONE, + value: Optional[bool] = None, + tristate: Optional[bool] = None, + autofocus: Optional[bool] = None, + check_color: Optional[str] = None, + active_color: Optional[str] = None, + inactive_color: Optional[str] = None, + focus_color: Optional[str] = None, + on_change=None, + on_focus=None, + on_blur=None, + ): + ConstrainedControl.__init__( + self, + ref=ref, + key=key, + width=width, + height=height, + left=left, + top=top, + right=right, + bottom=bottom, + expand=expand, + col=col, + opacity=opacity, + rotate=rotate, + scale=scale, + offset=offset, + aspect_ratio=aspect_ratio, + animate_opacity=animate_opacity, + animate_size=animate_size, + animate_position=animate_position, + animate_rotation=animate_rotation, + animate_scale=animate_scale, + animate_offset=animate_offset, + on_animation_end=on_animation_end, + tooltip=tooltip, + visible=visible, + disabled=disabled, + data=data, + ) + self.value = value + self.tristate = tristate + self.label = label + self.label_position = label_position + self.autofocus = autofocus + self.check_color = check_color + self.active_color = active_color + self.inactive_color = inactive_color + self.focus_color = focus_color + self.on_change = on_change + self.on_focus = on_focus + self.on_blur = on_blur + + def _get_control_name(self): + return "cupertinocheckbox" + + # value + @property + def value(self) -> Optional[bool]: + return self._get_attr( + "value", data_type="bool?", def_value=False if not self.tristate else None + ) + + @value.setter + def value(self, value: Optional[bool]): + self._set_attr("value", value) + + # tristate + @property + def tristate(self) -> Optional[bool]: + return self._get_attr("tristate", data_type="bool", def_value=False) + + @tristate.setter + def tristate(self, value: Optional[bool]): + self._set_attr("tristate", value) + + # label + @property + def label(self): + return self._get_attr("label") + + @label.setter + def label(self, value): + self._set_attr("label", value) + + # label_position + @property + def label_position(self) -> LabelPosition: + return self.__label_position + + @label_position.setter + def label_position(self, value: LabelPosition): + self.__label_position = value + if isinstance(value, LabelPosition): + self._set_attr("labelPosition", value.value) + else: + self.__set_label_position(value) + + def __set_label_position(self, value: LabelPositionString): + self._set_attr("labelPosition", value) + + # autofocus + @property + def autofocus(self) -> Optional[bool]: + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) + + # check_color + @property + def check_color(self): + return self._get_attr("checkColor") + + @check_color.setter + def check_color(self, value): + self._set_attr("checkColor", value) + + # active_color + @property + def active_color(self): + return self._get_attr("activeColor") + + @active_color.setter + def active_color(self, value): + self._set_attr("activeColor", value) + + # inactive_color + @property + def inactive_color(self): + return self._get_attr("inactiveColor") + + @inactive_color.setter + def inactive_color(self, value): + self._set_attr("inactiveColor", value) + + # focus_color + @property + def focus_color(self): + return self._get_attr("focusColor") + + @focus_color.setter + def focus_color(self, value): + self._set_attr("focusColor", value) + + # on_change + @property + def on_change(self): + return self._get_event_handler("change") + + @on_change.setter + def on_change(self, handler): + self._add_event_handler("change", handler) + + # on_focus + @property + def on_focus(self): + return self._get_event_handler("focus") + + @on_focus.setter + def on_focus(self, handler): + self._add_event_handler("focus", handler) + + # on_blur + @property + def on_blur(self): + return self._get_event_handler("blur") + + @on_blur.setter + def on_blur(self, handler): + self._add_event_handler("blur", handler)