diff --git a/packages/flet/lib/src/controls/alert_dialog.dart b/packages/flet/lib/src/controls/alert_dialog.dart index fe092d2e9..058e5e03a 100644 --- a/packages/flet/lib/src/controls/alert_dialog.dart +++ b/packages/flet/lib/src/controls/alert_dialog.dart @@ -73,6 +73,7 @@ class _AlertDialogControlState extends State actionsPadding: parseEdgeInsets(widget.control, "actionsPadding"), actionsAlignment: actionsAlignment, shape: parseOutlinedBorder(widget.control, "shape"), + semanticLabel: widget.control.attrString("semanticsLabel"), insetPadding: parseEdgeInsets(widget.control, "insetPadding") ?? const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0), backgroundColor: HexColor.fromString( @@ -141,8 +142,7 @@ class _AlertDialogControlState extends State if (shouldDismiss) { widget.backend .updateControlState(widget.control.id, {"open": "false"}); - widget.backend - .triggerControlEvent(widget.control.id, "dismiss", ""); + widget.backend.triggerControlEvent(widget.control.id, "dismiss"); } }); }); diff --git a/packages/flet/lib/src/controls/bottom_sheet.dart b/packages/flet/lib/src/controls/bottom_sheet.dart index a3d0bcbd2..a9f28a86c 100644 --- a/packages/flet/lib/src/controls/bottom_sheet.dart +++ b/packages/flet/lib/src/controls/bottom_sheet.dart @@ -104,8 +104,7 @@ class _BottomSheetControlState extends State { if (shouldDismiss) { resetOpenState(); - widget.backend - .triggerControlEvent(widget.control.id, "dismiss", ""); + widget.backend.triggerControlEvent(widget.control.id, "dismiss"); } }); }); diff --git a/packages/flet/lib/src/controls/chip.dart b/packages/flet/lib/src/controls/chip.dart index b0082c2e6..cee7fabde 100644 --- a/packages/flet/lib/src/controls/chip.dart +++ b/packages/flet/lib/src/controls/chip.dart @@ -61,7 +61,7 @@ class _ChipControlState extends State { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override @@ -111,14 +111,14 @@ class _ChipControlState extends State { Function()? onClickHandler = onClick && !disabled ? () { debugPrint("Chip ${widget.control.id} clicked!"); - widget.backend.triggerControlEvent(widget.control.id, "click", ""); + widget.backend.triggerControlEvent(widget.control.id, "click"); } : null; Function()? onDeleteHandler = onDelete && !disabled ? () { debugPrint("Chip ${widget.control.id} deleted!"); - widget.backend.triggerControlEvent(widget.control.id, "delete", ""); + widget.backend.triggerControlEvent(widget.control.id, "delete"); } : null; diff --git a/packages/flet/lib/src/controls/container.dart b/packages/flet/lib/src/controls/container.dart index 7264fd496..f9c59cb69 100644 --- a/packages/flet/lib/src/controls/container.dart +++ b/packages/flet/lib/src/controls/container.dart @@ -184,7 +184,7 @@ class ContainerControl extends StatelessWidget with FletStoreMixin { onLongPress: onLongPress ? () { debugPrint("Container ${control.id} long pressed!"); - backend.triggerControlEvent(control.id, "long_press", ""); + backend.triggerControlEvent(control.id, "long_press"); } : null, onHover: onHover @@ -306,7 +306,7 @@ class ContainerControl extends StatelessWidget with FletStoreMixin { onLongPress: onLongPress ? () { debugPrint("Container ${control.id} clicked!"); - backend.triggerControlEvent(control.id, "long_press", ""); + backend.triggerControlEvent(control.id, "long_press"); } : null, child: result, diff --git a/packages/flet/lib/src/controls/create_control.dart b/packages/flet/lib/src/controls/create_control.dart index e4c66e4f8..319a27776 100644 --- a/packages/flet/lib/src/controls/create_control.dart +++ b/packages/flet/lib/src/controls/create_control.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:collection/collection.dart'; +import 'package:flet/src/controls/semantics_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; @@ -68,6 +69,7 @@ import 'list_view.dart'; import 'markdown.dart'; import 'menu_bar.dart'; import 'menu_item_button.dart'; +import 'merge_semantics.dart'; import 'navigation_bar.dart'; import 'navigation_rail.dart'; import 'outlined_button.dart'; @@ -552,12 +554,24 @@ Widget createWidget( backend: backend); case "semantics": return SemanticsControl( + key: key, + parent: parent, + control: controlView.control, + children: controlView.children, + parentDisabled: parentDisabled, + parentAdaptive: parentAdaptive, + backend: backend); + case "mergesemantics": + return MergeSemanticsControl( key: key, parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled, parentAdaptive: parentAdaptive); + case "semanticsservice": + return SemanticsServiceControl( + parent: parent, control: controlView.control, backend: backend); case "shadermask": return ShaderMaskControl( key: key, diff --git a/packages/flet/lib/src/controls/cupertino_alert_dialog.dart b/packages/flet/lib/src/controls/cupertino_alert_dialog.dart index b2ac7af0b..a03db7309 100644 --- a/packages/flet/lib/src/controls/cupertino_alert_dialog.dart +++ b/packages/flet/lib/src/controls/cupertino_alert_dialog.dart @@ -102,8 +102,7 @@ class _CupertinoAlertDialogControlState if (shouldDismiss) { widget.backend .updateControlState(widget.control.id, {"open": "false"}); - widget.backend - .triggerControlEvent(widget.control.id, "dismiss", ""); + widget.backend.triggerControlEvent(widget.control.id, "dismiss"); } }); }); diff --git a/packages/flet/lib/src/controls/cupertino_button.dart b/packages/flet/lib/src/controls/cupertino_button.dart index 4a7ff7573..af81de9f4 100644 --- a/packages/flet/lib/src/controls/cupertino_button.dart +++ b/packages/flet/lib/src/controls/cupertino_button.dart @@ -150,7 +150,7 @@ class _CupertinoButtonControlState extends State { openWebBrowser(url, webWindowName: widget.control.attrString("urlTarget")); } - widget.backend.triggerControlEvent(widget.control.id, "click", ""); + widget.backend.triggerControlEvent(widget.control.id, "click"); } : null; diff --git a/packages/flet/lib/src/controls/cupertino_checkbox.dart b/packages/flet/lib/src/controls/cupertino_checkbox.dart index 9ffaa6e93..405dee467 100644 --- a/packages/flet/lib/src/controls/cupertino_checkbox.dart +++ b/packages/flet/lib/src/controls/cupertino_checkbox.dart @@ -40,7 +40,7 @@ class _CheckboxControlState extends State { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override diff --git a/packages/flet/lib/src/controls/cupertino_dialog_action.dart b/packages/flet/lib/src/controls/cupertino_dialog_action.dart index 281b1eb36..183620d4b 100644 --- a/packages/flet/lib/src/controls/cupertino_dialog_action.dart +++ b/packages/flet/lib/src/controls/cupertino_dialog_action.dart @@ -36,7 +36,7 @@ class CupertinoDialogActionControl extends StatelessWidget { Function()? onPressed = !disabled ? () { debugPrint("CupertinoDialogAction ${control.id} clicked!"); - backend.triggerControlEvent(control.id, "click", ""); + backend.triggerControlEvent(control.id, "click"); } : null; diff --git a/packages/flet/lib/src/controls/cupertino_list_tile.dart b/packages/flet/lib/src/controls/cupertino_list_tile.dart index febbc7eeb..0bb235ee2 100644 --- a/packages/flet/lib/src/controls/cupertino_list_tile.dart +++ b/packages/flet/lib/src/controls/cupertino_list_tile.dart @@ -89,7 +89,7 @@ class CupertinoListTileControl extends StatelessWidget { openWebBrowser(url, webWindowName: urlTarget); } if (onclick) { - backend.triggerControlEvent(control.id, "click", ""); + backend.triggerControlEvent(control.id, "click"); } } : null; diff --git a/packages/flet/lib/src/controls/cupertino_radio.dart b/packages/flet/lib/src/controls/cupertino_radio.dart index be039c775..b7495bd0c 100644 --- a/packages/flet/lib/src/controls/cupertino_radio.dart +++ b/packages/flet/lib/src/controls/cupertino_radio.dart @@ -41,7 +41,7 @@ class _CupertinoRadioControlState extends State void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override diff --git a/packages/flet/lib/src/controls/cupertino_slider.dart b/packages/flet/lib/src/controls/cupertino_slider.dart index 0c564327b..9760db4f8 100644 --- a/packages/flet/lib/src/controls/cupertino_slider.dart +++ b/packages/flet/lib/src/controls/cupertino_slider.dart @@ -43,7 +43,7 @@ class _CupertinoSliderControlState extends State { widget.backend.updateControlState(widget.control.id, props, server: false); _debouncer.run(() { widget.backend.updateControlState(widget.control.id, props); - widget.backend.triggerControlEvent(widget.control.id, "change", ''); + widget.backend.triggerControlEvent(widget.control.id, "change"); }); } diff --git a/packages/flet/lib/src/controls/cupertino_switch.dart b/packages/flet/lib/src/controls/cupertino_switch.dart index e5fd2f1e9..abc0eba7a 100644 --- a/packages/flet/lib/src/controls/cupertino_switch.dart +++ b/packages/flet/lib/src/controls/cupertino_switch.dart @@ -54,7 +54,7 @@ class _CupertinoSwitchControlState extends State { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override diff --git a/packages/flet/lib/src/controls/cupertino_textfield.dart b/packages/flet/lib/src/controls/cupertino_textfield.dart index 51c45dc62..944af5cd3 100644 --- a/packages/flet/lib/src/controls/cupertino_textfield.dart +++ b/packages/flet/lib/src/controls/cupertino_textfield.dart @@ -55,7 +55,7 @@ class _CupertinoTextFieldControlState extends State { if (!HardwareKeyboard.instance.isShiftPressed && evt.logicalKey.keyLabel == 'Enter') { if (evt is KeyDownEvent) { - widget.backend.triggerControlEvent(widget.control.id, "submit", ""); + widget.backend.triggerControlEvent(widget.control.id, "submit"); } return KeyEventResult.handled; } else { @@ -283,6 +283,8 @@ class _CupertinoTextFieldControlState extends State { cursorRadius: parseRadius(widget.control, "cursorRadius") ?? const Radius.circular(2.0), keyboardType: keyboardType, + clearButtonSemanticLabel: + widget.control.attrString("clearButtonSemanticsLabel"), autocorrect: autocorrect, enableSuggestions: enableSuggestions, smartDashesType: smartDashesType diff --git a/packages/flet/lib/src/controls/datatable.dart b/packages/flet/lib/src/controls/datatable.dart index 58d3df6bf..98127e08d 100644 --- a/packages/flet/lib/src/controls/datatable.dart +++ b/packages/flet/lib/src/controls/datatable.dart @@ -140,8 +140,7 @@ class _DataTableControlState extends State : null, onLongPress: row.control.attrBool("onLongPress", false)! ? () { - widget.backend.triggerControlEvent( - row.control.id, "long_press", ""); + widget.backend.triggerControlEvent(row.control.id, "long_press"); } : null, cells: row.children @@ -153,25 +152,25 @@ class _DataTableControlState extends State onDoubleTap: cell.attrBool("onDoubleTap", false)! ? () { widget.backend.triggerControlEvent( - cell.id, "double_tap", ""); + cell.id, "double_tap"); } : null, onLongPress: cell.attrBool("onLongPress", false)! ? () { widget.backend.triggerControlEvent( - cell.id, "long_press", ""); + cell.id, "long_press"); } : null, onTap: cell.attrBool("onTap", false)! ? () { widget.backend - .triggerControlEvent(cell.id, "tap", ""); + .triggerControlEvent(cell.id, "tap"); } : null, onTapCancel: cell.attrBool("onTapCancel", false)! ? () { widget.backend.triggerControlEvent( - cell.id, "tap_cancel", ""); + cell.id, "tap_cancel"); } : null, onTapDown: cell.attrBool("onTapDown", false)! diff --git a/packages/flet/lib/src/controls/dismissible.dart b/packages/flet/lib/src/controls/dismissible.dart index 643043f50..cd02df0bb 100644 --- a/packages/flet/lib/src/controls/dismissible.dart +++ b/packages/flet/lib/src/controls/dismissible.dart @@ -94,7 +94,7 @@ class _DismissibleControlState extends State { onResize: widget.control.attrBool("onResize", false)! ? () { widget.backend - .triggerControlEvent(widget.control.id, "resize", ""); + .triggerControlEvent(widget.control.id, "resize"); } : null, onUpdate: widget.control.attrBool("onUpdate", false)! diff --git a/packages/flet/lib/src/controls/dropdown.dart b/packages/flet/lib/src/controls/dropdown.dart index bcbbe9651..fdb95fe95 100644 --- a/packages/flet/lib/src/controls/dropdown.dart +++ b/packages/flet/lib/src/controls/dropdown.dart @@ -47,7 +47,7 @@ class _DropdownControlState extends State with FletStoreMixin { _focused = _focusNode.hasFocus; }); widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override diff --git a/packages/flet/lib/src/controls/elevated_button.dart b/packages/flet/lib/src/controls/elevated_button.dart index 7d5169688..257ccc98b 100644 --- a/packages/flet/lib/src/controls/elevated_button.dart +++ b/packages/flet/lib/src/controls/elevated_button.dart @@ -54,7 +54,7 @@ class _ElevatedButtonControlState extends State void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override @@ -102,8 +102,7 @@ class _ElevatedButtonControlState extends State openWebBrowser(url, webWindowName: widget.control.attrString("urlTarget")); } - widget.backend - .triggerControlEvent(widget.control.id, "click", ""); + widget.backend.triggerControlEvent(widget.control.id, "click"); } : null; @@ -111,7 +110,7 @@ class _ElevatedButtonControlState extends State ? () { debugPrint("Button ${widget.control.id} long pressed!"); widget.backend - .triggerControlEvent(widget.control.id, "long_press", ""); + .triggerControlEvent(widget.control.id, "long_press"); } : null; diff --git a/packages/flet/lib/src/controls/floating_action_button.dart b/packages/flet/lib/src/controls/floating_action_button.dart index c0a7dc82a..0563936c0 100644 --- a/packages/flet/lib/src/controls/floating_action_button.dart +++ b/packages/flet/lib/src/controls/floating_action_button.dart @@ -51,7 +51,7 @@ class FloatingActionButtonControl extends StatelessWidget { if (url != "") { openWebBrowser(url, webWindowName: urlTarget); } - backend.triggerControlEvent(control.id, "click", ""); + backend.triggerControlEvent(control.id, "click"); }; if (text == null && icon == null && contentCtrls.isEmpty) { diff --git a/packages/flet/lib/src/controls/icon.dart b/packages/flet/lib/src/controls/icon.dart index 491567d8a..4cdc78460 100644 --- a/packages/flet/lib/src/controls/icon.dart +++ b/packages/flet/lib/src/controls/icon.dart @@ -16,7 +16,8 @@ class IconControl extends StatelessWidget { debugPrint("Icon build: ${control.id}"); var name = control.attrString("name", "")!; - var size = control.attrDouble("size", null); + var size = control.attrDouble("size"); + var semanticsLabel = control.attrString("semanticsLabel"); var color = HexColor.fromString( Theme.of(context), control.attrString("color", "")!); @@ -26,6 +27,7 @@ class IconControl extends StatelessWidget { parseIcon(name), size: size, color: color, + semanticLabel: semanticsLabel, ), parent, control); diff --git a/packages/flet/lib/src/controls/icon_button.dart b/packages/flet/lib/src/controls/icon_button.dart index 18044dca1..5bc2a1f73 100644 --- a/packages/flet/lib/src/controls/icon_button.dart +++ b/packages/flet/lib/src/controls/icon_button.dart @@ -53,7 +53,7 @@ class _IconButtonControlState extends State void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override @@ -101,8 +101,7 @@ class _IconButtonControlState extends State if (url != "") { openWebBrowser(url, webWindowName: urlTarget); } - widget.backend - .triggerControlEvent(widget.control.id, "click", ""); + widget.backend.triggerControlEvent(widget.control.id, "click"); }; Widget? button; diff --git a/packages/flet/lib/src/controls/list_tile.dart b/packages/flet/lib/src/controls/list_tile.dart index fcb5bcde7..161d7d3c1 100644 --- a/packages/flet/lib/src/controls/list_tile.dart +++ b/packages/flet/lib/src/controls/list_tile.dart @@ -91,7 +91,7 @@ class ListTileControl extends StatelessWidget with FletStoreMixin { openWebBrowser(url, webWindowName: urlTarget); } if (onclick) { - backend.triggerControlEvent(control.id, "click", ""); + backend.triggerControlEvent(control.id, "click"); } } : null; @@ -99,7 +99,7 @@ class ListTileControl extends StatelessWidget with FletStoreMixin { Function()? onLongPress = onLongPressDefined && !disabled ? () { debugPrint("Button ${control.id} clicked!"); - backend.triggerControlEvent(control.id, "long_press", ""); + backend.triggerControlEvent(control.id, "long_press"); } : null; diff --git a/packages/flet/lib/src/controls/menu_item_button.dart b/packages/flet/lib/src/controls/menu_item_button.dart index be4bc83ed..c3f4b1d7f 100644 --- a/packages/flet/lib/src/controls/menu_item_button.dart +++ b/packages/flet/lib/src/controls/menu_item_button.dart @@ -46,7 +46,7 @@ class _MenuItemButtonControlState extends State { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override @@ -98,8 +98,7 @@ class _MenuItemButtonControlState extends State { : null, onPressed: onClick && !disabled ? () { - widget.backend - .triggerControlEvent(widget.control.id, "click", ""); + widget.backend.triggerControlEvent(widget.control.id, "click"); } : null, leadingIcon: leading.isNotEmpty diff --git a/packages/flet/lib/src/controls/merge_semantics.dart b/packages/flet/lib/src/controls/merge_semantics.dart new file mode 100644 index 000000000..52ec645c1 --- /dev/null +++ b/packages/flet/lib/src/controls/merge_semantics.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import 'create_control.dart'; + +class MergeSemanticsControl extends StatelessWidget { + final Control? parent; + final Control control; + final List children; + final bool parentDisabled; + final bool? parentAdaptive; + + const MergeSemanticsControl( + {super.key, + this.parent, + required this.control, + required this.children, + required this.parentDisabled, + required this.parentAdaptive}); + + @override + Widget build(BuildContext context) { + debugPrint("MergeSemantics build: ${control.id}"); + + var contentCtrls = + children.where((c) => c.name == "content" && c.isVisible); + bool disabled = control.isDisabled || parentDisabled; + + MergeSemantics mergeSemantics = MergeSemantics( + child: contentCtrls.isNotEmpty + ? createControl(control, contentCtrls.first.id, disabled, + parentAdaptive: parentAdaptive) + : null); + + return constrainedControl(context, mergeSemantics, parent, control); + } +} diff --git a/packages/flet/lib/src/controls/outlined_button.dart b/packages/flet/lib/src/controls/outlined_button.dart index 8141dba7a..5502d40e7 100644 --- a/packages/flet/lib/src/controls/outlined_button.dart +++ b/packages/flet/lib/src/controls/outlined_button.dart @@ -53,7 +53,7 @@ class _OutlinedButtonControlState extends State void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override @@ -78,15 +78,14 @@ class _OutlinedButtonControlState extends State if (url != "") { openWebBrowser(url, webWindowName: urlTarget); } - widget.backend.triggerControlEvent(widget.control.id, "click", ""); + widget.backend.triggerControlEvent(widget.control.id, "click"); } : null; Function()? onLongPressHandler = onLongPress && !disabled ? () { debugPrint("Button ${widget.control.id} long pressed!"); - widget.backend - .triggerControlEvent(widget.control.id, "long_press", ""); + widget.backend.triggerControlEvent(widget.control.id, "long_press"); } : null; diff --git a/packages/flet/lib/src/controls/page.dart b/packages/flet/lib/src/controls/page.dart index ea7053311..b06980bb7 100644 --- a/packages/flet/lib/src/controls/page.dart +++ b/packages/flet/lib/src/controls/page.dart @@ -856,7 +856,7 @@ class _ViewControlState extends State with FletStoreMixin { void dismissDrawer(String id) { widget.backend.updateControlState(id, {"open": "false"}); - widget.backend.triggerControlEvent(id, "dismiss", ""); + widget.backend.triggerControlEvent(id, "dismiss"); } WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/packages/flet/lib/src/controls/pagelet.dart b/packages/flet/lib/src/controls/pagelet.dart index d41ef81d1..cd5297ff4 100644 --- a/packages/flet/lib/src/controls/pagelet.dart +++ b/packages/flet/lib/src/controls/pagelet.dart @@ -127,7 +127,7 @@ class _PageletControlState extends State with FletStoreMixin { void dismissDrawer(String id) { widget.backend.updateControlState(id, {"open": "false"}); - widget.backend.triggerControlEvent(id, "dismiss", ""); + widget.backend.triggerControlEvent(id, "dismiss"); } WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/packages/flet/lib/src/controls/popup_menu_button.dart b/packages/flet/lib/src/controls/popup_menu_button.dart index 7db777413..bad6d611b 100644 --- a/packages/flet/lib/src/controls/popup_menu_button.dart +++ b/packages/flet/lib/src/controls/popup_menu_button.dart @@ -52,10 +52,10 @@ class PopupMenuButtonControl extends StatelessWidget with FletStoreMixin { ? RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)) : null, onCanceled: () { - backend.triggerControlEvent(control.id, "cancelled", ""); + backend.triggerControlEvent(control.id, "cancelled"); }, onSelected: (itemId) { - backend.triggerControlEvent(itemId, "click", ""); + backend.triggerControlEvent(itemId, "click"); }, position: menuPosition, itemBuilder: (BuildContext context) => diff --git a/packages/flet/lib/src/controls/radio.dart b/packages/flet/lib/src/controls/radio.dart index 68d7f05a6..3009ffd1a 100644 --- a/packages/flet/lib/src/controls/radio.dart +++ b/packages/flet/lib/src/controls/radio.dart @@ -43,7 +43,7 @@ class _RadioControlState extends State with FletStoreMixin { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override diff --git a/packages/flet/lib/src/controls/range_slider.dart b/packages/flet/lib/src/controls/range_slider.dart index fd3041c65..75d1eb4bc 100644 --- a/packages/flet/lib/src/controls/range_slider.dart +++ b/packages/flet/lib/src/controls/range_slider.dart @@ -46,7 +46,7 @@ class _SliderControlState extends State { widget.backend.updateControlState(widget.control.id, props, server: false); _debouncer.run(() { widget.backend.updateControlState(widget.control.id, props); - widget.backend.triggerControlEvent(widget.control.id, "change", ""); + widget.backend.triggerControlEvent(widget.control.id, "change"); }); } @@ -91,13 +91,13 @@ class _SliderControlState extends State { onChangeStart: !disabled ? (RangeValues newValues) { widget.backend - .triggerControlEvent(widget.control.id, "change_start", ''); + .triggerControlEvent(widget.control.id, "change_start"); } : null, onChangeEnd: !disabled ? (RangeValues newValues) { widget.backend - .triggerControlEvent(widget.control.id, "change_end", ''); + .triggerControlEvent(widget.control.id, "change_end"); } : null); diff --git a/packages/flet/lib/src/controls/search_anchor.dart b/packages/flet/lib/src/controls/search_anchor.dart index bb54bcc66..5c3e8a1d4 100644 --- a/packages/flet/lib/src/controls/search_anchor.dart +++ b/packages/flet/lib/src/controls/search_anchor.dart @@ -189,8 +189,7 @@ class _SearchAnchorControlState extends State { : null, onTap: () { if (onTap) { - widget.backend - .triggerControlEvent(widget.control.id, "tap", ""); + widget.backend.triggerControlEvent(widget.control.id, "tap"); } controller.openView(); }, diff --git a/packages/flet/lib/src/controls/semantics.dart b/packages/flet/lib/src/controls/semantics.dart index 19b2abd5e..a9be6a9ca 100644 --- a/packages/flet/lib/src/controls/semantics.dart +++ b/packages/flet/lib/src/controls/semantics.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../flet_control_backend.dart'; import '../models/control.dart'; import 'create_control.dart'; @@ -9,6 +10,7 @@ class SemanticsControl extends StatelessWidget { final List children; final bool parentDisabled; final bool? parentAdaptive; + final FletControlBackend backend; const SemanticsControl( {super.key, @@ -16,7 +18,8 @@ class SemanticsControl extends StatelessWidget { required this.control, required this.children, required this.parentDisabled, - required this.parentAdaptive}); + required this.parentAdaptive, + required this.backend}); @override Widget build(BuildContext context) { @@ -27,15 +30,128 @@ class SemanticsControl extends StatelessWidget { var label = control.attrString("label"); bool disabled = control.isDisabled || parentDisabled; - return constrainedControl( - context, - Semantics( - label: label, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null), - parent, - control); + Semantics semantics = Semantics( + label: label, + enabled: !disabled, + expanded: control.attrBool("expanded"), + hidden: control.attrBool("hidden"), + selected: control.attrBool("selected"), + checked: control.attrBool("checked"), + button: control.attrBool("button"), + slider: control.attrBool("slider"), + value: control.attrString("value"), + textField: control.attrBool("textField"), + image: control.attrBool("image"), + link: control.attrBool("link"), + header: control.attrBool("header"), + increasedValue: control.attrString("increasedValue"), + decreasedValue: control.attrString("decreasedValue"), + hint: control.attrString("hint"), + onTapHint: control.attrString("onTapHint"), + onLongPressHint: control.attrString("onLongPressHint"), + container: control.attrBool("container")!, + liveRegion: control.attrBool("liveRegion"), + obscured: control.attrBool("obscured"), + multiline: control.attrBool("multiline"), + focused: control.attrBool("focused"), + readOnly: control.attrBool("readOnly"), + focusable: control.attrBool("focusable"), + tooltip: control.attrString("tooltip"), + toggled: control.attrBool("toggled"), + maxValueLength: control.attrInt("maxValueLength"), + onTap: control.attrBool("onclick", false)! + ? () { + backend.triggerControlEvent(control.id, "click"); + } + : null, + onIncrease: control.attrBool("onIncrease", false)! + ? () { + backend.triggerControlEvent(control.id, "increase"); + } + : null, + onDecrease: control.attrBool("onDecrease", false)! + ? () { + backend.triggerControlEvent(control.id, "decrease"); + } + : null, + onDismiss: control.attrBool("onDismiss", false)! + ? () { + backend.triggerControlEvent(control.id, "dismiss"); + } + : null, + onScrollLeft: control.attrBool("onScrollLeft", false)! + ? () { + backend.triggerControlEvent(control.id, "scrollLeft"); + } + : null, + onScrollRight: control.attrBool("onScrollRight", false)! + ? () { + backend.triggerControlEvent(control.id, "scrollRight"); + } + : null, + onScrollUp: control.attrBool("onScrollUp", false)! + ? () { + backend.triggerControlEvent(control.id, "scrollUp"); + } + : null, + onScrollDown: control.attrBool("onScrollDown", false)! + ? () { + backend.triggerControlEvent(control.id, "scrollDown"); + } + : null, + onCopy: control.attrBool("onCopy", false)! + ? () { + backend.triggerControlEvent(control.id, "copy"); + } + : null, + onCut: control.attrBool("onCut", false)! + ? () { + backend.triggerControlEvent(control.id, "cut"); + } + : null, + onPaste: control.attrBool("onPaste", false)! + ? () { + backend.triggerControlEvent(control.id, "paste"); + } + : null, + onLongPress: control.attrBool("onDismiss", false)! + ? () { + backend.triggerControlEvent(control.id, "dismiss"); + } + : null, + onMoveCursorForwardByCharacter: + control.attrBool("onMoveCursorForwardByCharacter", false)! + ? (bool value) { + backend.triggerControlEvent(control.id, + "move_cursor_forward_by_character", value.toString()); + } + : null, + onMoveCursorBackwardByCharacter: + control.attrBool("onMoveCursorBackwardByCharacter", false)! + ? (bool value) { + backend.triggerControlEvent(control.id, + "move_cursor_backward_by_character", value.toString()); + } + : null, + onDidGainAccessibilityFocus: + control.attrBool("onDidGainAccessibilityFocus", false)! + ? () { + backend.triggerControlEvent( + control.id, "did_gain_accessibility_focus"); + } + : null, + onDidLoseAccessibilityFocus: + control.attrBool("onDidLoseAccessibilityFocus", false)! + ? () { + backend.triggerControlEvent( + control.id, "did_lose_accessibility_focus"); + } + : null, + child: contentCtrls.isNotEmpty + ? createControl(control, contentCtrls.first.id, disabled, + parentAdaptive: parentAdaptive) + : null); + + return constrainedControl(context, semantics, parent, control); } } diff --git a/packages/flet/lib/src/controls/semantics_service.dart b/packages/flet/lib/src/controls/semantics_service.dart new file mode 100644 index 000000000..09d289633 --- /dev/null +++ b/packages/flet/lib/src/controls/semantics_service.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; + +import '../flet_control_backend.dart'; +import '../models/control.dart'; + +class SemanticsServiceControl extends StatelessWidget { + final Control? parent; + final Control control; + final FletControlBackend backend; + + const SemanticsServiceControl( + {super.key, this.parent, required this.control, required this.backend}); + + @override + Widget build(BuildContext context) { + debugPrint("SemanticsService build: ${control.id}"); + + backend.subscribeMethods(control.id, (methodName, args) async { + var message = args["message"].toString(); + switch (methodName) { + case "announce_message": + debugPrint("SemanticsService.announceMessage($message)"); + var rtl = args["rtl"] == "true"; + var assertiveness = Assertiveness.values.firstWhere( + (e) => e.name == args["assertiveness"].toString().toLowerCase(), + orElse: () => Assertiveness.polite); + SemanticsService.announce( + message, rtl ? TextDirection.rtl : TextDirection.ltr, + assertiveness: assertiveness); + break; + case "announce_tooltip": + debugPrint("SemanticsService.announceTooltip($message)"); + SemanticsService.tooltip(message); + break; + } + return null; + }); + + return const SizedBox.shrink(); + } +} diff --git a/packages/flet/lib/src/controls/shake_detector.dart b/packages/flet/lib/src/controls/shake_detector.dart index 5fdbbea22..c3c4bfb1d 100644 --- a/packages/flet/lib/src/controls/shake_detector.dart +++ b/packages/flet/lib/src/controls/shake_detector.dart @@ -60,7 +60,7 @@ class _ShakeDetectorControlState extends State { _shakeDetector?.stopListening(); _shakeDetector = ShakeDetector.autoStart( onPhoneShake: () { - widget.backend.triggerControlEvent(widget.control.id, "shake", ""); + widget.backend.triggerControlEvent(widget.control.id, "shake"); }, minimumShakeCount: minimumShakeCount, shakeSlopTimeMS: shakeSlopTimeMs, diff --git a/packages/flet/lib/src/controls/slider.dart b/packages/flet/lib/src/controls/slider.dart index 54cbd8bb0..1040eebd7 100644 --- a/packages/flet/lib/src/controls/slider.dart +++ b/packages/flet/lib/src/controls/slider.dart @@ -50,7 +50,7 @@ class _SliderControlState extends State with FletStoreMixin { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } void onChange(double value) { @@ -61,7 +61,7 @@ class _SliderControlState extends State with FletStoreMixin { widget.backend.updateControlState(widget.control.id, props, server: false); _debouncer.run(() { widget.backend.updateControlState(widget.control.id, props); - widget.backend.triggerControlEvent(widget.control.id, "change", ''); + widget.backend.triggerControlEvent(widget.control.id, "change"); }); } diff --git a/packages/flet/lib/src/controls/snack_bar.dart b/packages/flet/lib/src/controls/snack_bar.dart index 52f5af594..5cd8382e9 100644 --- a/packages/flet/lib/src/controls/snack_bar.dart +++ b/packages/flet/lib/src/controls/snack_bar.dart @@ -50,8 +50,7 @@ class _SnackBarControlState extends State { widget.control.attrString("actionColor", "")!), onPressed: () { debugPrint("SnackBar ${widget.control.id} clicked!"); - widget.backend - .triggerControlEvent(widget.control.id, "action", ""); + widget.backend.triggerControlEvent(widget.control.id, "action"); }) : null; diff --git a/packages/flet/lib/src/controls/submenu_button.dart b/packages/flet/lib/src/controls/submenu_button.dart index ab467a067..fba221383 100644 --- a/packages/flet/lib/src/controls/submenu_button.dart +++ b/packages/flet/lib/src/controls/submenu_button.dart @@ -46,7 +46,7 @@ class _SubMenuButtonControlState extends State { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override @@ -102,8 +102,7 @@ class _SubMenuButtonControlState extends State { : null, onClose: onClose && !disabled ? () { - widget.backend - .triggerControlEvent(widget.control.id, "close", ""); + widget.backend.triggerControlEvent(widget.control.id, "close"); } : null, onHover: onHover && !disabled @@ -114,7 +113,7 @@ class _SubMenuButtonControlState extends State { : null, onOpen: onOpen && !disabled ? () { - widget.backend.triggerControlEvent(widget.control.id, "open", ""); + widget.backend.triggerControlEvent(widget.control.id, "open"); } : null, leadingIcon: leading.isNotEmpty diff --git a/packages/flet/lib/src/controls/switch.dart b/packages/flet/lib/src/controls/switch.dart index 83f4cc82a..7457229ed 100644 --- a/packages/flet/lib/src/controls/switch.dart +++ b/packages/flet/lib/src/controls/switch.dart @@ -59,7 +59,7 @@ class _SwitchControlState extends State with FletStoreMixin { void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override diff --git a/packages/flet/lib/src/controls/text_button.dart b/packages/flet/lib/src/controls/text_button.dart index 7d35172a8..342d04dfe 100644 --- a/packages/flet/lib/src/controls/text_button.dart +++ b/packages/flet/lib/src/controls/text_button.dart @@ -53,7 +53,7 @@ class _TextButtonControlState extends State void _onFocusChange() { widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur", ""); + widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); } @override @@ -101,8 +101,7 @@ class _TextButtonControlState extends State if (url != "") { openWebBrowser(url, webWindowName: urlTarget); } - widget.backend - .triggerControlEvent(widget.control.id, "click", ""); + widget.backend.triggerControlEvent(widget.control.id, "click"); } : null; @@ -110,7 +109,7 @@ class _TextButtonControlState extends State ? () { debugPrint("Button ${widget.control.id} long pressed!"); widget.backend - .triggerControlEvent(widget.control.id, "long_press", ""); + .triggerControlEvent(widget.control.id, "long_press"); } : null; diff --git a/packages/flet/lib/src/controls/textfield.dart b/packages/flet/lib/src/controls/textfield.dart index 7d9041414..83dfad402 100644 --- a/packages/flet/lib/src/controls/textfield.dart +++ b/packages/flet/lib/src/controls/textfield.dart @@ -52,7 +52,7 @@ class _TextFieldControlState extends State if (!HardwareKeyboard.instance.isShiftPressed && evt.logicalKey.keyLabel == 'Enter') { if (evt is KeyDownEvent) { - widget.backend.triggerControlEvent(widget.control.id, "submit", ""); + widget.backend.triggerControlEvent(widget.control.id, "submit"); } return KeyEventResult.handled; } else { diff --git a/packages/flet/lib/src/flet_control_backend.dart b/packages/flet/lib/src/flet_control_backend.dart index 57a2991a8..daeaa452f 100644 --- a/packages/flet/lib/src/flet_control_backend.dart +++ b/packages/flet/lib/src/flet_control_backend.dart @@ -2,8 +2,8 @@ abstract class FletControlBackend { void updateControlState(String controlId, Map props, {bool client = true, bool server = true}); - void triggerControlEvent( - String controlId, String eventName, String eventData); + void triggerControlEvent(String controlId, String eventName, + [String eventData = ""]); void subscribeMethods(String controlId, Future Function(String, Map) methodHandler); diff --git a/packages/flet/lib/src/flet_server.dart b/packages/flet/lib/src/flet_server.dart index 6738468bd..a13aae18f 100644 --- a/packages/flet/lib/src/flet_server.dart +++ b/packages/flet/lib/src/flet_server.dart @@ -178,8 +178,8 @@ class FletServer implements FletControlBackend { } @override - void triggerControlEvent( - String controlId, String eventName, String eventData) { + void triggerControlEvent(String controlId, String eventName, + [String eventData = ""]) { _sendPageEvent( eventTarget: controlId, eventName: eventName, eventData: eventData); } diff --git a/packages/flet_audio/lib/src/audio.dart b/packages/flet_audio/lib/src/audio.dart index 7869e9595..a019876db 100644 --- a/packages/flet_audio/lib/src/audio.dart +++ b/packages/flet_audio/lib/src/audio.dart @@ -138,8 +138,7 @@ class _AudioControlState extends State with FletStoreMixin { } _onSeekComplete = () { - widget.backend - .triggerControlEvent(widget.control.id, "seek_complete", ""); + widget.backend.triggerControlEvent(widget.control.id, "seek_complete"); }; () async { @@ -168,7 +167,7 @@ class _AudioControlState extends State with FletStoreMixin { if (srcChanged) { debugPrint("Audio.srcChanged!"); - widget.backend.triggerControlEvent(widget.control.id, "loaded", ""); + widget.backend.triggerControlEvent(widget.control.id, "loaded"); } if (releaseMode != null && releaseMode != prevReleaseMode) { diff --git a/packages/flet_video/lib/src/video.dart b/packages/flet_video/lib/src/video.dart index ffafe969f..38b42691a 100644 --- a/packages/flet_video/lib/src/video.dart +++ b/packages/flet_video/lib/src/video.dart @@ -30,7 +30,7 @@ class _VideoControlState extends State with FletStoreMixin { pitch: widget.control.attrDouble("pitch") != null ? true : false, ready: () { if (widget.control.attrBool("onLoaded", false)!) { - widget.backend.triggerControlEvent(widget.control.id, "loaded", ""); + widget.backend.triggerControlEvent(widget.control.id, "loaded"); } }, ); 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 2d327c7c8..8927833ab 100644 --- a/sdk/python/packages/flet-core/src/flet_core/__init__.py +++ b/sdk/python/packages/flet-core/src/flet_core/__init__.py @@ -200,6 +200,7 @@ from flet_core.segmented_button import Segment, SegmentedButton from flet_core.selection_area import SelectionArea from flet_core.semantics import Semantics +from flet_core.semantics_service import Assertiveness, SemanticsService from flet_core.shader_mask import ShaderMask from flet_core.shadow import BoxShadow, ShadowBlurStyle from flet_core.shake_detector import ShakeDetector @@ -261,7 +262,13 @@ ) from flet_core.user_control import UserControl from flet_core.vertical_divider import VerticalDivider -from flet_core.video import FilterQuality, PlaylistMode, Video, VideoMedia, VideoSubtitleConfiguration +from flet_core.video import ( + FilterQuality, + PlaylistMode, + Video, + VideoMedia, + VideoSubtitleConfiguration, +) from flet_core.view import View from flet_core.webview import WebView from flet_core.window_drag_area import WindowDragArea diff --git a/sdk/python/packages/flet-core/src/flet_core/alert_dialog.py b/sdk/python/packages/flet-core/src/flet_core/alert_dialog.py index 9b650da46..ebde98645 100644 --- a/sdk/python/packages/flet-core/src/flet_core/alert_dialog.py +++ b/sdk/python/packages/flet-core/src/flet_core/alert_dialog.py @@ -76,6 +76,7 @@ def __init__( actions_alignment: MainAxisAlignment = MainAxisAlignment.NONE, shape: Optional[OutlinedBorder] = None, inset_padding: PaddingValue = None, + semantics_label: Optional[str] = None, adaptive: Optional[bool] = None, on_dismiss=None, # @@ -115,6 +116,7 @@ def __init__( self.actions_alignment = actions_alignment self.shape = shape self.inset_padding = inset_padding + self.semantics_label = semantics_label self.on_dismiss = on_dismiss def _get_control_name(self): @@ -274,6 +276,15 @@ def inset_padding(self) -> PaddingValue: def inset_padding(self, value: PaddingValue): self.__inset_padding = value + # semantics_label + @property + def semantics_label(self): + return self._get_attr("semanticsLabel") + + @semantics_label.setter + def semantics_label(self, value): + self._set_attr("semanticsLabel", value) + # on_dismiss @property def on_dismiss(self): diff --git a/sdk/python/packages/flet-core/src/flet_core/cupertino_textfield.py b/sdk/python/packages/flet-core/src/flet_core/cupertino_textfield.py index 03c33bc2c..c6455f3d9 100644 --- a/sdk/python/packages/flet-core/src/flet_core/cupertino_textfield.py +++ b/sdk/python/packages/flet-core/src/flet_core/cupertino_textfield.py @@ -47,6 +47,7 @@ def __init__( shadow: Union[None, BoxShadow, List[BoxShadow]] = None, prefix_visibility_mode: Optional[VisibilityMode] = None, suffix_visibility_mode: Optional[VisibilityMode] = None, + clear_button_semantics_label: Optional[str] = None, # # TextField # @@ -206,6 +207,7 @@ def __init__( self.shadow = shadow self.suffix_visibility_mode = suffix_visibility_mode self.prefix_visibility_mode = prefix_visibility_mode + self.clear_button_semantics_label = clear_button_semantics_label self.border = border def _get_control_name(self): @@ -292,6 +294,15 @@ def prefix_visibility_mode(self, value: Optional[VisibilityMode]): value.value if isinstance(value, VisibilityMode) else value, ) + # clear_button_semantics_label + @property + def clear_button_semantics_label(self): + return self._get_attr("clearButtonSemanticsLabel") + + @clear_button_semantics_label.setter + def clear_button_semantics_label(self, value): + self._set_attr("clearButtonSemanticsLabel", value) + # border @property def border(self) -> Optional[Border]: diff --git a/sdk/python/packages/flet-core/src/flet_core/icon.py b/sdk/python/packages/flet-core/src/flet_core/icon.py index df7323450..1589949c6 100644 --- a/sdk/python/packages/flet-core/src/flet_core/icon.py +++ b/sdk/python/packages/flet-core/src/flet_core/icon.py @@ -48,6 +48,7 @@ def __init__( name: Optional[str] = None, color: Optional[str] = None, size: OptionalNumber = None, + semantics_label: Optional[str] = None, # # ConstrainedControl # @@ -101,6 +102,7 @@ def __init__( self.name = name self.color = color self.size = size + self.semantics_label = semantics_label def _get_control_name(self): return "icon" @@ -131,3 +133,12 @@ def size(self) -> OptionalNumber: @size.setter def size(self, value: OptionalNumber): self._set_attr("size", value) + + # semantics_label + @property + def semantics_label(self): + return self._get_attr("semanticsLabel") + + @semantics_label.setter + def semantics_label(self, value): + self._set_attr("semanticsLabel", value) diff --git a/sdk/python/packages/flet-core/src/flet_core/merge_semantics.py b/sdk/python/packages/flet-core/src/flet_core/merge_semantics.py new file mode 100644 index 000000000..7ce9e923f --- /dev/null +++ b/sdk/python/packages/flet-core/src/flet_core/merge_semantics.py @@ -0,0 +1,58 @@ +from typing import Any, Optional + +from flet_core.control import Control +from flet_core.ref import Ref + + +class MergeSemantics(Control): + """ + A control that merges the semantics of its descendants. + + Causes all the semantics of the subtree rooted at this node to be merged into one node in the semantics tree. + + Used by accessibility tools, search engines, and other semantic analysis software to determine the meaning of the application. + + ----- + + Online docs: https://flet.dev/docs/controls/mergesemantics + """ + + def __init__( + self, + content: Optional[Control] = None, + # + # Control + # + ref: Optional[Ref] = None, + visible: Optional[bool] = None, + disabled: Optional[bool] = None, + data: Any = None, + ): + Control.__init__( + self, + ref=ref, + visible=visible, + disabled=disabled, + data=data, + ) + + self.content = content + + def _get_control_name(self): + return "mergesemantics" + + def _get_children(self): + children = [] + if isinstance(self.__content, Control): + self.__content._set_attr_internal("n", "content") + children.append(self.__content) + return children + + # content + @property + def content(self) -> Optional[Control]: + return self.__content + + @content.setter + def content(self, value: Optional[Control]): + self.__content = value diff --git a/sdk/python/packages/flet-core/src/flet_core/semantics.py b/sdk/python/packages/flet-core/src/flet_core/semantics.py index 69bc72f84..53e7f9c30 100644 --- a/sdk/python/packages/flet-core/src/flet_core/semantics.py +++ b/sdk/python/packages/flet-core/src/flet_core/semantics.py @@ -1,6 +1,6 @@ from typing import Any, Optional -from flet_core.control import Control +from flet_core.control import Control, OptionalNumber from flet_core.ref import Ref @@ -19,6 +19,49 @@ def __init__( self, content: Optional[Control] = None, label: Optional[str] = None, + expanded: Optional[bool] = None, + hidden: Optional[bool] = None, + selected: Optional[bool] = None, + button: Optional[bool] = None, + obscured: Optional[bool] = None, + multiline: Optional[bool] = None, + focusable: Optional[bool] = None, + read_only: Optional[bool] = None, + focus: Optional[bool] = None, + slider: Optional[bool] = None, + tooltip: Optional[str] = None, + toggled: Optional[bool] = None, + max_value_length: OptionalNumber = None, + checked: Optional[bool] = None, + value: Optional[str] = None, + increased_value: Optional[str] = None, + decreased_value: Optional[str] = None, + hint_text: Optional[str] = None, + on_tap_hint_text: Optional[str] = None, + on_long_press_hint_text: Optional[str] = None, + container: Optional[bool] = None, + live_region: Optional[bool] = None, + textfield: Optional[bool] = None, + link: Optional[bool] = None, + header: Optional[bool] = None, + image: Optional[bool] = None, + on_tap=None, + on_double_tap=None, + on_increase=None, + on_decrease=None, + on_dismiss=None, + on_scroll_left=None, + on_scroll_right=None, + on_scroll_up=None, + on_scroll_down=None, + on_copy=None, + on_cut=None, + on_paste=None, + on_long_press=None, + on_move_cursor_forward_by_character=None, + on_move_cursor_backward_by_character=None, + on_did_gain_accessibility_focus=None, + on_did_lose_accessibility_focus=None, # # Control # @@ -37,6 +80,49 @@ def __init__( self.content = content self.label = label + self.expanded = expanded + self.hidden = hidden + self.selected = selected + self.button = button + self.obscured = obscured + self.multiline = multiline + self.focusable = focusable + self.read_only = read_only + self.focus = focus + self.slider = slider + self.tooltip = tooltip + self.toggled = toggled + self.max_value_length = max_value_length + self.checked = checked + self.value = value + self.increased_value = increased_value + self.decreased_value = decreased_value + self.hint_text = hint_text + self.on_tap_hint_text = on_tap_hint_text + self.on_long_press_hint_text = on_long_press_hint_text + self.container = container + self.live_region = live_region + self.textfield = textfield + self.link = link + self.header = header + self.image = image + self.on_tap = on_tap + self.on_double_tap = on_double_tap + self.on_increase = on_increase + self.on_decrease = on_decrease + self.on_dismiss = on_dismiss + self.on_scroll_left = on_scroll_left + self.on_scroll_right = on_scroll_right + self.on_scroll_up = on_scroll_up + self.on_scroll_down = on_scroll_down + self.on_copy = on_copy + self.on_cut = on_cut + self.on_paste = on_paste + self.on_long_press = on_long_press + self.on_move_cursor_forward_by_character = on_move_cursor_forward_by_character + self.on_move_cursor_backward_by_character = on_move_cursor_backward_by_character + self.on_did_gain_accessibility_focus = on_did_gain_accessibility_focus + self.on_did_lose_accessibility_focus = on_did_lose_accessibility_focus def _get_control_name(self): return "semantics" @@ -65,3 +151,415 @@ def content(self) -> Optional[Control]: @content.setter def content(self, value: Optional[Control]): self.__content = value + + # expanded + @property + def expanded(self): + return self._get_attr("expanded") + + @expanded.setter + def expanded(self, value: Optional[bool]): + self._set_attr("expanded", value) + + # hidden + @property + def hidden(self): + return self._get_attr("hidden") + + @hidden.setter + def hidden(self, value: Optional[bool]): + self._set_attr("hidden", value) + + # textfield + @property + def textfield(self): + return self._get_attr("textfield") + + @textfield.setter + def textfield(self, value: Optional[bool]): + self._set_attr("textfield", value) + + # link + @property + def link(self): + return self._get_attr("link") + + @link.setter + def link(self, value: Optional[bool]): + self._set_attr("link", value) + + # image + @property + def image(self): + return self._get_attr("image") + + @image.setter + def image(self, value: Optional[bool]): + self._set_attr("image", value) + + # header + @property + def header(self): + return self._get_attr("header") + + @header.setter + def header(self, value: Optional[bool]): + self._set_attr("header", value) + + # selected + @property + def selected(self): + return self._get_attr("selected") + + @selected.setter + def selected(self, value: Optional[bool]): + self._set_attr("selected", value) + + # button + @property + def button(self): + return self._get_attr("button") + + @button.setter + def button(self, value: Optional[bool]): + self._set_attr("button", value) + + # obscured + @property + def obscured(self): + return self._get_attr("obscured") + + @obscured.setter + def obscured(self, value: Optional[bool]): + self._set_attr("obscured", value) + + # multiline + @property + def multiline(self): + return self._get_attr("multiline") + + @multiline.setter + def multiline(self, value: Optional[bool]): + self._set_attr("multiline", value) + + # focusable + @property + def focusable(self): + return self._get_attr("focusable") + + @focusable.setter + def focusable(self, value: Optional[bool]): + self._set_attr("focusable", value) + + # read_only + @property + def read_only(self): + return self._get_attr("readOnly") + + @read_only.setter + def read_only(self, value): + self._set_attr("readOnly", value) + + # focused + @property + def focused(self): + return self._get_attr("focus") + + @focused.setter + def focused(self, value: Optional[bool]): + self._set_attr("focused", value) + + # slider + @property + def slider(self): + return self._get_attr("slider") + + @slider.setter + def slider(self, value: Optional[bool]): + self._set_attr("slider", value) + + # tooltip + @property + def tooltip(self): + return self._get_attr("tooltip") + + @tooltip.setter + def tooltip(self, value: Optional[str]): + self._set_attr("tooltip", value) + + # toggled + @property + def toggled(self): + return self._get_attr("toggled") + + @toggled.setter + def toggled(self, value: Optional[bool]): + self._set_attr("toggled", value) + + # max_value_length + @property + def max_value_length(self): + return self._get_attr("maxValueLength") + + @max_value_length.setter + def max_value_length(self, value: OptionalNumber): + self._set_attr("maxValueLength", value) + + # checked + @property + def checked(self): + return self._get_attr("checked") + + @checked.setter + def checked(self, value: Optional[bool]): + self._set_attr("checked", value) + + # value + @property + def value(self): + return self._get_attr("value") + + @value.setter + def value(self, value: Optional[str]): + self._set_attr("value", value) + + # increased_value + @property + def increased_value(self): + return self._get_attr("increasedValue") + + @increased_value.setter + def increased_value(self, value: Optional[str]): + self._set_attr("increasedValue", value) + + # decreased_value + @property + def decreased_value(self): + return self._get_attr("decreasedValue") + + @decreased_value.setter + def decreased_value(self, value: Optional[str]): + self._set_attr("decreasedValue", value) + + # hint_text + @property + def hint_text(self): + return self._get_attr("hintText") + + @hint_text.setter + def hint_text(self, value: Optional[bool]): + self._set_attr("hintText", value) + + # on_long_press_hint_text + @property + def on_long_press_hint_text(self): + return self._get_attr("onLongPressHintText") + + @on_long_press_hint_text.setter + def on_long_press_hint_text(self, value: Optional[bool]): + self._set_attr("onLongPressHintText", value) + + # on_tap_hint_text + @property + def on_tap_hint_text(self): + return self._get_attr("onTapHintText") + + @on_tap_hint_text.setter + def on_tap_hint_text(self, value: Optional[bool]): + self._set_attr("onTapHintText", value) + + # container + @property + def container(self): + return self._get_attr("container") + + @container.setter + def container(self, value: Optional[bool]): + self._set_attr("container", value) + + # live_region + @property + def live_region(self): + return self._get_attr("liveRegion") + + @live_region.setter + def live_region(self, value: Optional[bool]): + self._set_attr("liveRegion", value) + + # on_tap + @property + def on_tap(self): + return self._get_event_handler("tap") + + @on_tap.setter + def on_tap(self, handler): + self._add_event_handler("tap", handler) + self._set_attr("onTap", True if handler is not None else None) + + # on_double_tap + @property + def on_double_tap(self): + return self._get_event_handler("double_tap") + + @on_double_tap.setter + def on_double_tap(self, handler): + self._add_event_handler("double_tap", handler) + self._set_attr("onDoubleTap", True if handler is not None else None) + + # on_increase + @property + def on_increase(self): + return self._get_event_handler("increase") + + @on_increase.setter + def on_increase(self, handler): + self._add_event_handler("increase", handler) + self._set_attr("onIncrease", True if handler is not None else None) + + # on_decrease + @property + def on_decrease(self): + return self._get_event_handler("decrease") + + @on_decrease.setter + def on_decrease(self, handler): + self._add_event_handler("decrease", handler) + self._set_attr("onDecrease", True if handler is not None else None) + + # on_dismiss + @property + def on_dismiss(self): + return self._get_event_handler("dismiss") + + @on_dismiss.setter + def on_dismiss(self, handler): + self._add_event_handler("dismiss", handler) + self._set_attr("onDismiss", True if handler is not None else None) + + # on_scroll_left + @property + def on_scroll_left(self): + return self._get_event_handler("scroll_left") + + @on_scroll_left.setter + def on_scroll_left(self, handler): + self._add_event_handler("scroll_left", handler) + self._set_attr("onScrollLeft", True if handler is not None else None) + + # on_scroll_right + @property + def on_scroll_right(self): + return self._get_event_handler("scroll_right") + + @on_scroll_right.setter + def on_scroll_right(self, handler): + self._add_event_handler("scroll_right", handler) + self._set_attr("onScrollRight", True if handler is not None else None) + + # on_scroll_up + @property + def on_scroll_up(self): + return self._get_event_handler("scroll_up") + + @on_scroll_up.setter + def on_scroll_up(self, handler): + self._add_event_handler("scroll_up", handler) + self._set_attr("onScrollUp", True if handler is not None else None) + + # on_scroll_down + @property + def on_scroll_down(self): + return self._get_event_handler("scroll_down") + + @on_scroll_down.setter + def on_scroll_down(self, handler): + self._add_event_handler("scroll_down", handler) + self._set_attr("onScrollDown", True if handler is not None else None) + + # on_copy + @property + def on_copy(self): + return self._get_event_handler("copy") + + @on_copy.setter + def on_copy(self, handler): + self._add_event_handler("copy", handler) + self._set_attr("onCopy", True if handler is not None else None) + + # on_cut + @property + def on_cut(self): + return self._get_event_handler("cut") + + @on_cut.setter + def on_cut(self, handler): + self._add_event_handler("cut", handler) + self._set_attr("onCut", True if handler is not None else None) + + # on_paste + @property + def on_paste(self): + return self._get_event_handler("paste") + + @on_paste.setter + def on_paste(self, handler): + self._add_event_handler("paste", handler) + self._set_attr("onPaste", True if handler is not None else None) + + # on_long_press + @property + def on_long_press(self): + return self._get_event_handler("long_press") + + @on_long_press.setter + def on_long_press(self, handler): + self._add_event_handler("long_press", handler) + self._set_attr("onLongPress", True if handler is not None else None) + + # on_move_cursor_forward_by_character + @property + def on_move_cursor_forward_by_character(self): + return self._get_event_handler("move_cursor_forward_by_character") + + @on_move_cursor_forward_by_character.setter + def on_move_cursor_forward_by_character(self, handler): + self._add_event_handler("move_cursor_forward_by_character", handler) + self._set_attr( + "onMoveCursorForwardByCharacter", True if handler is not None else None + ) + + # on_move_cursor_backward_by_character + @property + def on_move_cursor_backward_by_character(self): + return self._get_event_handler("move_cursor_backward_by_character") + + @on_move_cursor_backward_by_character.setter + def on_move_cursor_backward_by_character(self, handler): + self._add_event_handler("move_cursor_backward_by_character", handler) + self._set_attr( + "onMoveCursorBackwardByCharacter", True if handler is not None else None + ) + + # on_did_gain_accessibility_focus + @property + def on_did_gain_accessibility_focus(self): + return self._get_event_handler("did_gain_accessibility_focus") + + @on_did_gain_accessibility_focus.setter + def on_did_gain_accessibility_focus(self, handler): + self._add_event_handler("did_gain_accessibility_focus", handler) + self._set_attr( + "onDidGainAccessibilityFocus", True if handler is not None else None + ) + + # on_did_lose_accessibility_focus + @property + def on_did_lose_accessibility_focus(self): + return self._get_event_handler("did_lose_accessibility_focus") + + @on_did_lose_accessibility_focus.setter + def on_did_lose_accessibility_focus(self, handler): + self._add_event_handler("did_lose_accessibility_focus", handler) + self._set_attr( + "onDidLoseAccessibilityFocus", True if handler is not None else None + ) diff --git a/sdk/python/packages/flet-core/src/flet_core/semantics_service.py b/sdk/python/packages/flet-core/src/flet_core/semantics_service.py new file mode 100644 index 000000000..074f5b6a2 --- /dev/null +++ b/sdk/python/packages/flet-core/src/flet_core/semantics_service.py @@ -0,0 +1,51 @@ +from enum import Enum +from typing import Any, Optional + +from flet_core.control import Control +from flet_core.ref import Ref + + +class Assertiveness(Enum): + POLITE = "polite" + ASSERTIVE = "assertive" + + +class SemanticsService(Control): + def __init__( + self, + ref: Optional[Ref] = None, + data: Any = None, + ): + Control.__init__( + self, + ref=ref, + data=data, + ) + + def _get_control_name(self): + return "semanticsservice" + + def announce_message( + self, + message: str, + rtl: bool = False, + assertiveness: Assertiveness = Assertiveness.POLITE, + ): + self.invoke_method( + "announce_message", + arguments={ + "message": message, + "rtl": str(rtl), + "assertiveness": assertiveness.value + if isinstance(assertiveness, Assertiveness) + else str(assertiveness), + }, + ) + + def announce_tooltip(self, message: str): + self.invoke_method( + "announce_tooltip", + arguments={ + "message": message, + }, + )