diff --git a/client/lib/controls/checkbox.dart b/client/lib/controls/checkbox.dart index 2c91b442a..2c604dc91 100644 --- a/client/lib/controls/checkbox.dart +++ b/client/lib/controls/checkbox.dart @@ -28,10 +28,17 @@ class CheckboxControl extends StatefulWidget { class _CheckboxControlState extends State { bool? _value; + final FocusNode _focusNode = FocusNode(); @override void initState() { super.initState(); + _focusNode.addListener(() { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: _focusNode.hasFocus ? "focus" : "blur", + eventData: ""); + }); } @override @@ -50,6 +57,7 @@ class _CheckboxControlState extends State { widget.control.attrString("labelPosition", "")!.toLowerCase(), orElse: () => LabelPosition.right); bool tristate = widget.control.attrBool("tristate", false)!; + bool autofocus = widget.control.attrBool("autofocus", false)!; bool disabled = widget.control.isDisabled || widget.parentDisabled; return StoreConnector( @@ -66,7 +74,7 @@ class _CheckboxControlState extends State { onChange(bool? value) { var svalue = value != null ? value.toString() : ""; - debugPrint(svalue); + //debugPrint(svalue); setState(() { _value = value; }); @@ -83,6 +91,8 @@ class _CheckboxControlState extends State { } var checkbox = Checkbox( + autofocus: autofocus, + focusNode: _focusNode, value: _value, tristate: tristate, onChanged: !disabled diff --git a/client/lib/controls/clipboard.dart b/client/lib/controls/clipboard.dart new file mode 100644 index 000000000..25dea0148 --- /dev/null +++ b/client/lib/controls/clipboard.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import '../models/control.dart'; + +class ClipboardControl extends StatelessWidget { + final Control? parent; + final Control control; + + const ClipboardControl({Key? key, this.parent, required this.control}) + : super(key: key); + + @override + Widget build(BuildContext context) { + debugPrint("Clipboard build: ${control.id}"); + + var value = control.attrString("value"); + + if (value != null) { + debugPrint("Clipboard JSON value: $value"); + + var jv = json.decode(value); + Clipboard.setData(ClipboardData(text: jv["d"] as String?)); + } + + return const SizedBox.shrink(); + } +} diff --git a/client/lib/controls/container.dart b/client/lib/controls/container.dart index cb8a01eca..ca98a156b 100644 --- a/client/lib/controls/container.dart +++ b/client/lib/controls/container.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import '../models/control.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; -import 'create_control.dart'; -import 'error.dart'; import '../utils/colors.dart'; -import '../models/control.dart'; import '../utils/edge_insets.dart'; +import 'create_control.dart'; +import 'error.dart'; class ContainerControl extends StatelessWidget { final Control? parent; @@ -25,7 +25,7 @@ class ContainerControl extends StatelessWidget { @override Widget build(BuildContext context) { - debugPrint("Icon build: ${control.id}"); + debugPrint("Container build: ${control.id}"); var bgColor = HexColor.fromString(context, control.attrString("bgColor", "")!); diff --git a/client/lib/controls/create_control.dart b/client/lib/controls/create_control.dart index f7c69652b..24e6c3342 100644 --- a/client/lib/controls/create_control.dart +++ b/client/lib/controls/create_control.dart @@ -8,6 +8,7 @@ import '../models/control_view_model.dart'; import 'alert_dialog.dart'; import 'banner.dart'; import 'checkbox.dart'; +import 'clipboard.dart'; import 'column.dart'; import 'container.dart'; import 'dropdown.dart'; @@ -45,17 +46,22 @@ import 'textfield.dart'; // } Widget createControl(Control? parent, String id, bool parentDisabled) { + //debugPrint("createControl(): $id"); return StoreConnector( distinct: true, converter: (store) { //debugPrint("ControlViewModel $id converter"); return ControlViewModel.fromStore(store, id); }, - onWillChange: (prev, next) { - //debugPrint("${next.type} $id will change"); + // onWillChange: (prev, next) { + // debugPrint("onWillChange() $id: $prev, $next"); + // }, + ignoreChange: (state) { + //debugPrint("ignoreChange: $id"); + return state.controls[id] == null; }, builder: (context, controlView) { - //debugPrint("${control.type} ${control.id} builder"); + //debugPrint("createControl builder(): $id"); switch (controlView.control.type) { case ControlType.page: return PageControl( @@ -64,6 +70,8 @@ Widget createControl(Control? parent, String id, bool parentDisabled) { return TextControl(control: controlView.control); case ControlType.icon: return IconControl(control: controlView.control); + case ControlType.clipboard: + return ClipboardControl(control: controlView.control); case ControlType.image: return ImageControl(parent: parent, control: controlView.control); case ControlType.progressRing: @@ -205,12 +213,18 @@ Widget createControl(Control? parent, String id, bool parentDisabled) { } Widget baseControl(Widget widget, Control? parent, Control control) { - return _expandable(_opacity(widget, parent, control), parent, control); + return _expandable( + _tooltip(_opacity(widget, parent, control), parent, control), + parent, + control); } Widget constrainedControl(Widget widget, Control? parent, Control control) { return _expandable( - _sizedControl(_opacity(widget, parent, control), parent, control), + _sizedControl( + _tooltip(_opacity(widget, parent, control), parent, control), + parent, + control), parent, control); } @@ -225,6 +239,18 @@ Widget _opacity(Widget widget, Control? parent, Control control) { : widget; } +Widget _tooltip(Widget widget, Control? parent, Control control) { + var tooltip = control.attrString("tooltip"); + return tooltip != null + ? Tooltip( + message: tooltip, + padding: const EdgeInsets.all(4.0), + child: widget, + waitDuration: const Duration(milliseconds: 800), + ) + : widget; +} + Widget _sizedControl(Widget widget, Control? parent, Control control) { var width = control.attrDouble("width", null); var height = control.attrDouble("height", null); diff --git a/client/lib/controls/dropdown.dart b/client/lib/controls/dropdown.dart index d5273af63..75ce722ca 100644 --- a/client/lib/controls/dropdown.dart +++ b/client/lib/controls/dropdown.dart @@ -28,6 +28,18 @@ class DropdownControl extends StatefulWidget { class _DropdownControlState extends State { String? _value; + final FocusNode _focusNode = FocusNode(); + + @override + void initState() { + super.initState(); + _focusNode.addListener(() { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: _focusNode.hasFocus ? "focus" : "blur", + eventData: ""); + }); + } @override Widget build(BuildContext context) { @@ -41,6 +53,7 @@ class _DropdownControlState extends State { builder: (context, itemsView) { debugPrint("Dropdown StoreConnector build: ${widget.control.id}"); + bool autofocus = widget.control.attrBool("autofocus", false)!; bool disabled = widget.control.isDisabled || widget.parentDisabled; String? value = widget.control.attrString("value"); @@ -54,6 +67,8 @@ class _DropdownControlState extends State { itemsView.children.where((c) => c.name == "suffix"); var dropDown = DropdownButtonFormField( + autofocus: autofocus, + focusNode: _focusNode, value: _value, decoration: buildInputDecoration( widget.control, diff --git a/client/lib/controls/elevated_button.dart b/client/lib/controls/elevated_button.dart index e438a17d0..1fac73bc1 100644 --- a/client/lib/controls/elevated_button.dart +++ b/client/lib/controls/elevated_button.dart @@ -29,6 +29,7 @@ class ElevatedButtonControl extends StatelessWidget { Color? iconColor = HexColor.fromString(context, control.attrString("iconColor", "")!); var contentCtrls = children.where((c) => c.name == "content"); + bool autofocus = control.attrBool("autofocus", false)!; bool disabled = control.isDisabled || parentDisabled; Function()? onPressed = disabled @@ -45,6 +46,7 @@ class ElevatedButtonControl extends StatelessWidget { if (icon != null) { button = ElevatedButton.icon( + autofocus: autofocus, onPressed: onPressed, icon: Icon( icon, @@ -53,6 +55,7 @@ class ElevatedButtonControl extends StatelessWidget { label: Text(text)); } else if (contentCtrls.isNotEmpty) { button = ElevatedButton( + autofocus: autofocus, onPressed: onPressed, child: createControl(control, contentCtrls.first.id, disabled)); } else { diff --git a/client/lib/controls/floating_action_button.dart b/client/lib/controls/floating_action_button.dart index 7062a1a3b..3336ff7ae 100644 --- a/client/lib/controls/floating_action_button.dart +++ b/client/lib/controls/floating_action_button.dart @@ -30,6 +30,7 @@ class FloatingActionButtonControl extends StatelessWidget { Color? bgColor = HexColor.fromString(context, control.attrString("bgColor", "")!); var contentCtrls = children.where((c) => c.name == "content"); + bool autofocus = control.attrBool("autofocus", false)!; bool disabled = control.isDisabled || parentDisabled; Function()? onPressed = disabled @@ -49,16 +50,19 @@ class FloatingActionButtonControl extends StatelessWidget { Widget button; if (contentCtrls.isNotEmpty) { button = FloatingActionButton( + autofocus: autofocus, onPressed: onPressed, child: createControl(control, contentCtrls.first.id, disabled)); } else if (icon != null && text == null) { button = FloatingActionButton( + autofocus: autofocus, onPressed: onPressed, child: Icon(icon), backgroundColor: bgColor, ); } else if (icon != null && text != null) { button = FloatingActionButton.extended( + autofocus: autofocus, onPressed: onPressed, label: Text(text), icon: Icon(icon), diff --git a/client/lib/controls/grid_view.dart b/client/lib/controls/grid_view.dart index 93153891f..d57347930 100644 --- a/client/lib/controls/grid_view.dart +++ b/client/lib/controls/grid_view.dart @@ -26,21 +26,35 @@ class GridViewControl extends StatelessWidget { final horizontal = control.attrBool("horizontal", false)!; final runsCount = control.attrInt("runsCount", 1)!; + final maxExtent = control.attrDouble("maxExtent"); final spacing = control.attrDouble("spacing", 10)!; final runSpacing = control.attrDouble("runSpacing", 10)!; final padding = parseEdgeInsets(control, "padding"); - - List controls = - children.map((c) => createControl(control, c.id, disabled)).toList(); + final childAspectRatio = control.attrDouble("childAspectRatio", 1)!; + + List visibleControls = children.where((c) => c.isVisible).toList(); + + var gridDelegate = maxExtent == null + ? SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: runsCount, + mainAxisSpacing: spacing, + crossAxisSpacing: runSpacing, + childAspectRatio: childAspectRatio) + : SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: maxExtent, + mainAxisSpacing: spacing, + crossAxisSpacing: runSpacing, + childAspectRatio: childAspectRatio); return constrainedControl( - GridView.count( + GridView.builder( scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, - crossAxisCount: runsCount, - mainAxisSpacing: spacing, - crossAxisSpacing: runSpacing, padding: padding, - children: controls, + gridDelegate: gridDelegate, + itemCount: visibleControls.length, + itemBuilder: (context, index) { + return createControl(control, visibleControls[index].id, disabled); + }, ), parent, control); diff --git a/client/lib/controls/icon_button.dart b/client/lib/controls/icon_button.dart index ca39d5243..34069abaa 100644 --- a/client/lib/controls/icon_button.dart +++ b/client/lib/controls/icon_button.dart @@ -25,12 +25,12 @@ class IconButtonControl extends StatelessWidget { Widget build(BuildContext context) { debugPrint("Button build: ${control.id}"); - String? tooltip = control.attrString("tooltip"); IconData? icon = getMaterialIcon(control.attrString("icon", "")!); Color? iconColor = HexColor.fromString(context, control.attrString("iconColor", "")!); double? iconSize = control.attrDouble("iconSize"); var contentCtrls = children.where((c) => c.name == "content"); + bool autofocus = control.attrBool("autofocus", false)!; bool disabled = control.isDisabled || parentDisabled; Function()? onPressed = disabled @@ -47,18 +47,18 @@ class IconButtonControl extends StatelessWidget { if (icon != null) { button = IconButton( + autofocus: autofocus, icon: Icon( icon, color: iconColor, ), iconSize: iconSize, - tooltip: tooltip, onPressed: onPressed); } else if (contentCtrls.isNotEmpty) { button = IconButton( + autofocus: autofocus, onPressed: onPressed, iconSize: iconSize, - tooltip: tooltip, icon: createControl(control, contentCtrls.first.id, disabled)); } else { return const ErrorControl( diff --git a/client/lib/controls/list_view.dart b/client/lib/controls/list_view.dart index 826f4ba04..5a5b96666 100644 --- a/client/lib/controls/list_view.dart +++ b/client/lib/controls/list_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import '../models/control.dart'; @@ -40,20 +41,7 @@ class ListViewControl extends StatelessWidget { final spacing = control.attrDouble("spacing", 0)!; final padding = parseEdgeInsets(control, "padding"); - List controls = []; - - bool firstControl = true; - for (var ctrl in children.where((c) => c.isVisible)) { - // spacer between displayed controls - if (spacing > 0 && !firstControl) { - controls.add( - horizontal ? SizedBox(width: spacing) : SizedBox(height: spacing)); - } - firstControl = false; - - // displayed control - controls.add(createControl(control, ctrl.id, disabled)); - } + List visibleControls = children.where((c) => c.isVisible).toList(); if (autoScroll) { WidgetsBinding.instance!.addPostFrameCallback((_) { @@ -62,12 +50,33 @@ class ListViewControl extends StatelessWidget { } return constrainedControl( - ListView( - controller: _controller, - scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, - padding: padding, - children: controls, - ), + spacing > 0 + ? ListView.separated( + controller: _controller, + scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, + padding: padding, + itemCount: children.length, + itemBuilder: (context, index) { + return createControl( + control, visibleControls[index].id, disabled); + }, + separatorBuilder: (context, index) { + return Divider(height: spacing); + }, + ) + : ListView.builder( + controller: _controller, + scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, + padding: padding, + itemCount: children.length, + itemBuilder: (context, index) { + return createControl( + control, visibleControls[index].id, disabled); + }, + prototypeItem: children.isNotEmpty + ? createControl(control, visibleControls[0].id, disabled) + : null, + ), parent, control); } diff --git a/client/lib/controls/outlined_button.dart b/client/lib/controls/outlined_button.dart index 463a8b5ff..bac83c164 100644 --- a/client/lib/controls/outlined_button.dart +++ b/client/lib/controls/outlined_button.dart @@ -29,6 +29,7 @@ class OutlinedButtonControl extends StatelessWidget { Color? iconColor = HexColor.fromString(context, control.attrString("iconColor", "")!); var contentCtrls = children.where((c) => c.name == "content"); + bool autofocus = control.attrBool("autofocus", false)!; bool disabled = control.isDisabled || parentDisabled; Function()? onPressed = disabled @@ -45,6 +46,7 @@ class OutlinedButtonControl extends StatelessWidget { if (icon != null) { button = OutlinedButton.icon( + autofocus: autofocus, onPressed: onPressed, icon: Icon( icon, @@ -53,6 +55,7 @@ class OutlinedButtonControl extends StatelessWidget { label: Text(text)); } else if (contentCtrls.isNotEmpty) { button = OutlinedButton( + autofocus: autofocus, onPressed: onPressed, child: createControl(control, contentCtrls.first.id, disabled)); } else { diff --git a/client/lib/controls/page.dart b/client/lib/controls/page.dart index 1e79ba5b6..7138f989a 100644 --- a/client/lib/controls/page.dart +++ b/client/lib/controls/page.dart @@ -7,6 +7,7 @@ import '../models/app_state.dart'; import '../models/control.dart'; import '../models/control_children_view_model.dart'; import '../utils/alignment.dart'; +import '../utils/color_theme.dart'; import '../utils/colors.dart'; import '../utils/edge_insets.dart'; import '../utils/theme.dart'; @@ -71,14 +72,15 @@ class PageControl extends StatelessWidget { // theme var theme = parseTheme(control, "theme") ?? ThemeData( - colorSchemeSeed: const Color.fromARGB(255, 0, 152, 218), + colorScheme: lightColorScheme, brightness: Brightness.light, useMaterial3: true, visualDensity: VisualDensity.adaptivePlatformDensity); var darkTheme = parseTheme(control, "darkTheme") ?? ThemeData( - colorSchemeSeed: const Color.fromARGB(255, 104, 192, 233), + //colorSchemeSeed: const Color.fromARGB(255, 104, 192, 233), + colorScheme: darkColorScheme, brightness: Brightness.dark, useMaterial3: true, visualDensity: VisualDensity.adaptivePlatformDensity); diff --git a/client/lib/controls/radio.dart b/client/lib/controls/radio.dart index b34dc7c7f..8f0f4675d 100644 --- a/client/lib/controls/radio.dart +++ b/client/lib/controls/radio.dart @@ -30,9 +30,17 @@ class RadioControl extends StatefulWidget { } class _RadioControlState extends State { + final FocusNode _focusNode = FocusNode(); + @override void initState() { super.initState(); + _focusNode.addListener(() { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: _focusNode.hasFocus ? "focus" : "blur", + eventData: ""); + }); } @override @@ -51,6 +59,7 @@ class _RadioControlState extends State { p.name.toLowerCase() == widget.control.attrString("labelPosition", "")!.toLowerCase(), orElse: () => LabelPosition.right); + bool autofocus = widget.control.attrBool("autofocus", false)!; bool disabled = widget.control.isDisabled || widget.parentDisabled; return StoreConnector( @@ -83,6 +92,8 @@ class _RadioControlState extends State { } var radio = Radio( + autofocus: autofocus, + focusNode: _focusNode, groupValue: groupValue, value: value, onChanged: !disabled diff --git a/client/lib/controls/slider.dart b/client/lib/controls/slider.dart index 5bd45a4de..8659ca354 100644 --- a/client/lib/controls/slider.dart +++ b/client/lib/controls/slider.dart @@ -29,10 +29,17 @@ class SliderControl extends StatefulWidget { class _SliderControlState extends State { double _value = 0; Timer? _debounce; + final FocusNode _focusNode = FocusNode(); @override void initState() { super.initState(); + _focusNode.addListener(() { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: _focusNode.hasFocus ? "focus" : "blur", + eventData: ""); + }); } @override @@ -68,6 +75,7 @@ class _SliderControlState extends State { debugPrint("SliderControl build: ${widget.control.id}"); String? label = widget.control.attrString("label"); + bool autofocus = widget.control.attrBool("autofocus", false)!; bool disabled = widget.control.isDisabled || widget.parentDisabled; double min = widget.control.attrDouble("min", 0)!; @@ -87,6 +95,8 @@ class _SliderControlState extends State { } var slider = Slider( + autofocus: autofocus, + focusNode: _focusNode, value: _value, min: min, max: max, diff --git a/client/lib/controls/snack_bar.dart b/client/lib/controls/snack_bar.dart index 2167fd3ee..d70cfe3a9 100644 --- a/client/lib/controls/snack_bar.dart +++ b/client/lib/controls/snack_bar.dart @@ -78,8 +78,8 @@ class _SnackBarControlState extends State { debugPrint("SnackBar StoreConnector build: ${widget.control.id}"); var open = widget.control.attrBool("open", false)!; - var removeCurrentSnackbar = - widget.control.attrBool("removeCurrentSnackBar", false)!; + var removeCurrentSnackbar = true; + //widget.control.attrBool("removeCurrentSnackBar", false)!; debugPrint("Current open state: $_open"); debugPrint("New open state: $open"); diff --git a/client/lib/controls/switch.dart b/client/lib/controls/switch.dart index f6e2d0795..0d7b94ca6 100644 --- a/client/lib/controls/switch.dart +++ b/client/lib/controls/switch.dart @@ -28,10 +28,17 @@ class SwitchControl extends StatefulWidget { class _SwitchControlState extends State { bool _value = false; + final FocusNode _focusNode = FocusNode(); @override void initState() { super.initState(); + _focusNode.addListener(() { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: _focusNode.hasFocus ? "focus" : "blur", + eventData: ""); + }); } @override @@ -49,6 +56,7 @@ class _SwitchControlState extends State { p.name.toLowerCase() == widget.control.attrString("labelPosition", "")!.toLowerCase(), orElse: () => LabelPosition.right); + bool autofocus = widget.control.attrBool("autofocus", false)!; bool disabled = widget.control.isDisabled || widget.parentDisabled; return StoreConnector( @@ -81,6 +89,8 @@ class _SwitchControlState extends State { } var swtch = Switch( + autofocus: autofocus, + focusNode: _focusNode, value: _value, onChanged: !disabled ? (bool value) { diff --git a/client/lib/controls/text.dart b/client/lib/controls/text.dart index 388bc645a..3be20069f 100644 --- a/client/lib/controls/text.dart +++ b/client/lib/controls/text.dart @@ -17,6 +17,7 @@ class TextControl extends StatelessWidget { debugPrint("Text build: ${control.id}"); String text = control.attrString("value", "")!; + bool noWrap = control.attrBool("noWrap", false)!; TextStyle? style; var styleName = control.attrString("style", null); @@ -53,6 +54,7 @@ class TextControl extends StatelessWidget { ) : Text( text, + softWrap: !noWrap, style: style, textAlign: textAlign, overflow: overflow, diff --git a/client/lib/controls/text_button.dart b/client/lib/controls/text_button.dart index c0c7c86f2..6488df4ef 100644 --- a/client/lib/controls/text_button.dart +++ b/client/lib/controls/text_button.dart @@ -29,6 +29,7 @@ class TextButtonControl extends StatelessWidget { Color? iconColor = HexColor.fromString(context, control.attrString("iconColor", "")!); var contentCtrls = children.where((c) => c.name == "content"); + bool autofocus = control.attrBool("autofocus", false)!; bool disabled = control.isDisabled || parentDisabled; Function()? onPressed = disabled @@ -45,6 +46,7 @@ class TextButtonControl extends StatelessWidget { if (icon != null) { button = TextButton.icon( + autofocus: autofocus, onPressed: onPressed, icon: Icon( icon, @@ -53,6 +55,7 @@ class TextButtonControl extends StatelessWidget { label: Text(text)); } else if (contentCtrls.isNotEmpty) { button = TextButton( + autofocus: autofocus, onPressed: onPressed, child: createControl(control, contentCtrls.first.id, disabled)); } else { diff --git a/client/lib/controls/textfield.dart b/client/lib/controls/textfield.dart index e1bee630d..20fcc156a 100644 --- a/client/lib/controls/textfield.dart +++ b/client/lib/controls/textfield.dart @@ -32,8 +32,9 @@ class _TextFieldControlState extends State { String _value = ""; bool _revealPassword = false; late TextEditingController _controller; + late final FocusNode _focusNode = FocusNode(); - late final _focusNode = FocusNode( + late final _shiftEnterfocusNode = FocusNode( onKey: (FocusNode node, RawKeyEvent evt) { if (!evt.isShiftPressed && evt.logicalKey.keyLabel == 'Enter') { if (evt is RawKeyDownEvent) { @@ -53,6 +54,18 @@ class _TextFieldControlState extends State { void initState() { super.initState(); _controller = TextEditingController(); + _shiftEnterfocusNode.addListener(() { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: _shiftEnterfocusNode.hasFocus ? "focus" : "blur", + eventData: ""); + }); + _focusNode.addListener(() { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: _focusNode.hasFocus ? "focus" : "blur", + eventData: ""); + }); } @override @@ -65,6 +78,7 @@ class _TextFieldControlState extends State { Widget build(BuildContext context) { debugPrint("TextField build: ${widget.control.id}"); + bool autofocus = widget.control.attrBool("autofocus", false)!; bool disabled = widget.control.isDisabled || widget.parentDisabled; return StoreConnector( @@ -122,7 +136,14 @@ class _TextFieldControlState extends State { ); var textField = TextFormField( + autofocus: autofocus, enabled: !disabled, + onFieldSubmitted: (_) { + ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: "submit", + eventData: ""); + }, decoration: buildInputDecoration( widget.control, prefixControls.isNotEmpty ? prefixControls.first : null, @@ -136,10 +157,10 @@ class _TextFieldControlState extends State { obscureText: password && !_revealPassword, controller: _controller, focusNode: keyboardType == TextInputType.multiline && shiftEnter - ? _focusNode - : null, + ? _shiftEnterfocusNode + : _focusNode, onChanged: (String value) { - debugPrint(value); + //debugPrint(value); setState(() { _value = value; }); diff --git a/client/lib/main.dart b/client/lib/main.dart index 5aedaad24..40d7335fa 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -17,7 +17,14 @@ import 'session_store/session_store.dart' if (dart.library.js) "session_store/session_store_js.dart"; import 'web_socket_client.dart'; +const bool isProduction = bool.fromEnvironment('dart.vm.product'); + void main([List? args]) async { + if (isProduction) { + // ignore: avoid_returning_null_for_void + debugPrint = (String? message, {int? wrapWidth}) => null; + } + await setupDesktop(); var pageUri = Uri.base; diff --git a/client/lib/models/control_type.dart b/client/lib/models/control_type.dart index 86a146e1a..e3000ce4f 100644 --- a/client/lib/models/control_type.dart +++ b/client/lib/models/control_type.dart @@ -2,6 +2,7 @@ enum ControlType { alertDialog, banner, checkbox, + clipboard, column, container, dropdown, diff --git a/client/lib/utils/alignment.dart b/client/lib/utils/alignment.dart index 5c1bf91de..bde660b6c 100644 --- a/client/lib/utils/alignment.dart +++ b/client/lib/utils/alignment.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/cupertino.dart'; import '../models/control.dart'; +import 'numbers.dart'; MainAxisAlignment parseMainAxisAlignment( Control control, String propName, MainAxisAlignment defValue) { @@ -51,5 +52,5 @@ Alignment? parseAlignment(Control control, String propName) { } Alignment alignmentFromJson(Map json) { - return Alignment(json['x'] as double, json['y'] as double); + return Alignment(parseDouble(json['x']), parseDouble(json['y'])); } diff --git a/client/lib/utils/color_theme.dart b/client/lib/utils/color_theme.dart new file mode 100644 index 000000000..0dc722fbc --- /dev/null +++ b/client/lib/utils/color_theme.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; + +// https://material-foundation.github.io/material-theme-builder/#/custom + +const seed = Color(0xFF6750A4); + +const lightColorScheme = ColorScheme( + brightness: Brightness.light, + primary: Color(0xFF006496), + onPrimary: Color(0xFFFFFFFF), + primaryContainer: Color(0xFFC9E6FF), + onPrimaryContainer: Color(0xFF001E31), + secondary: Color(0xFFBE0041), + onSecondary: Color(0xFFFFFFFF), + secondaryContainer: Color(0xFFFFDADE), + onSecondaryContainer: Color(0xFF400010), + tertiary: Color(0xFF00696F), + onTertiary: Color(0xFFFFFFFF), + tertiaryContainer: Color(0xFF70F6FF), + onTertiaryContainer: Color(0xFF002022), + error: Color(0xFFB3261E), + errorContainer: Color(0xFFF9DEDC), + onError: Color(0xFFFFFFFF), + onErrorContainer: Color(0xFF410E0B), + background: Color(0xFFFFFBFE), + onBackground: Color(0xFF1C1B1F), + surface: Color(0xFFFFFBFE), + onSurface: Color(0xFF1C1B1F), + surfaceVariant: Color(0xFFE7E0EC), + onSurfaceVariant: Color(0xFF49454F), + outline: Color(0xFF79747E), + onInverseSurface: Color(0xFFF4EFF4), + inverseSurface: Color(0xFF313033), + inversePrimary: Color(0xFF8BCDFF), + shadow: Color(0xFF000000), +); + +const darkColorScheme = ColorScheme( + brightness: Brightness.dark, + primary: Color(0xFF8BCDFF), + onPrimary: Color(0xFF003350), + primaryContainer: Color(0xFF004B72), + onPrimaryContainer: Color(0xFFC9E6FF), + secondary: Color(0xFFFFB2BC), + onSecondary: Color(0xFF67001F), + secondaryContainer: Color(0xFF91002F), + onSecondaryContainer: Color(0xFFFFDADE), + tertiary: Color(0xFF4DD9E2), + onTertiary: Color(0xFF00363A), + tertiaryContainer: Color(0xFF004F54), + onTertiaryContainer: Color(0xFF70F6FF), + error: Color(0xFFF2B8B5), + errorContainer: Color(0xFF8C1D18), + onError: Color(0xFF601410), + onErrorContainer: Color(0xFFF9DEDC), + background: Color(0xFF1C1B1F), + onBackground: Color(0xFFE6E1E5), + surface: Color(0xFF1C1B1F), + onSurface: Color(0xFFE6E1E5), + surfaceVariant: Color(0xFF49454F), + onSurfaceVariant: Color(0xFFCAC4D0), + outline: Color(0xFF938F99), + onInverseSurface: Color(0xFF1C1B1F), + inverseSurface: Color(0xFFE6E1E5), + inversePrimary: Color(0xFF006496), + shadow: Color(0xFF000000), +); diff --git a/docs/roadmap.md b/docs/roadmap.md index 1bb8714aa..91f3c03b7 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -436,10 +436,10 @@ Properties: Events: -- onClose -- onConnect -- onDisconnect -- onResize +- on_close +- on_connect +- on_disconnect +- on_resize ## Control @@ -488,6 +488,7 @@ Properties: - content - child control of any type - marging - padding +- tooltip ## Row @@ -597,7 +598,8 @@ TextTheme: https://api.flutter.dev/flutter/material/TextTheme-class.html - bgColor - overflow - (TextOverflow) `clip`, `ellipsis`, `fade`, `visible` - selectable - +- tooltip +- noWrap ## Icon @@ -611,6 +613,7 @@ Properties: - color ([more](https://api.flutter.dev/flutter/dart-ui/Color-class.html)) - size - semanticLabel (S2) - Text to announce in accessibility modes +- tooltip ## Image @@ -626,7 +629,7 @@ Properties: - opacity (S2) - override control's opacity - semanticLabel (S2) - border_radius - to make rounded corners - +- tooltip ## ProgressBar @@ -640,6 +643,7 @@ Properties: - value - label - description +- tooltip ## ProgressRing @@ -653,6 +657,7 @@ Properties: - value - label - labelPosition +- tooltip ## ElevatedButton @@ -664,6 +669,8 @@ Properties: - icon - iconColor - content - a Control representing custom button content +- tooltip +- autofocus Events: @@ -679,6 +686,8 @@ Properties: - icon - iconColor - content - a Control representing custom button content +- tooltip +- autofocus Events: @@ -694,6 +703,8 @@ Properties: - icon - iconColor - content - a Control representing custom button content +- tooltip +- autofocus Events: @@ -710,6 +721,7 @@ Properties: - iconSize - tooltip - content - a Control representing custom button content +- autofocus Events: @@ -725,6 +737,8 @@ Properties: - icon - bgColor - content - a Control representing custom button content +- tooltip +- autofocus Events: @@ -750,6 +764,13 @@ Properties: - label - labelPosition - value - radio's value +- tooltip +- autofocus + +Events: + +- focus +- blur ## Slider @@ -762,10 +783,14 @@ Properties: - min - max - divisions +- tooltip +- autofocus Events: - change +- focus +- blur ## Switch @@ -776,10 +801,14 @@ Properties: - label - labelPosition - value +- tooltip +- autofocus Events: - change +- focus +- blur ## Checkbox @@ -791,10 +820,14 @@ Properties: - tristate - label - labelPosition +- tooltip +- autofocus Events: - change +- focus +- blur ## Dropdown @@ -812,13 +845,17 @@ Properties: - errorText - prefix: Control - suffix: Control +- tooltip - value - options +- autofocus Events: - change +- focus +- blur ## TextField @@ -836,6 +873,8 @@ Properties: - errorText - prefix: Control - suffix: Control +- tooltip +- autofocus - value - keyboardType @@ -850,6 +889,8 @@ Properties: Events: - change +- focus +- blur ## AlertDialog diff --git a/sdk/python/build-wheels.py b/sdk/python/build-wheels.py index 36a4113c0..1d1f3b487 100644 --- a/sdk/python/build-wheels.py +++ b/sdk/python/build-wheels.py @@ -35,6 +35,12 @@ "wheel_tags": ["py3-none-win32"], "file_suffix": "py3-none-win32", }, + "Linux amd64 (Alpine)": { + "fletd_asset": "linux_amd64", + "fletd_exec": "fletd", + "wheel_tags": ["py3-none-musllinux_1_2_x86_64"], + "file_suffix": "py3-none-musllinux_1_2_x86_64", + }, "Linux amd64": { "fletd_asset": "linux_amd64", "fletd_exec": "fletd", diff --git a/sdk/python/flet/checkbox.py b/sdk/python/flet/checkbox.py index 53f5e26ae..055c69db1 100644 --- a/sdk/python/flet/checkbox.py +++ b/sdk/python/flet/checkbox.py @@ -23,6 +23,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -33,7 +34,10 @@ def __init__( label_position: LabelPosition = None, value: bool = None, tristate: bool = None, + autofocus: bool = None, on_change=None, + on_focus=None, + on_blur=None, ): ConstrainedControl.__init__( self, @@ -42,6 +46,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -50,7 +55,10 @@ def __init__( self.tristate = tristate self.label = label self.label_position = label_position + self.autofocus = autofocus self.on_change = on_change + self.on_focus = on_focus + self.on_blur = on_blur def _get_control_name(self): return "checkbox" @@ -94,6 +102,16 @@ def label_position(self): def label_position(self, value: LabelPosition): self._set_attr("labelPosition", value) + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) + # on_change @property def on_change(self): @@ -102,3 +120,21 @@ def on_change(self): @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) diff --git a/sdk/python/flet/clipboard.py b/sdk/python/flet/clipboard.py new file mode 100644 index 000000000..bb8a9f146 --- /dev/null +++ b/sdk/python/flet/clipboard.py @@ -0,0 +1,52 @@ +import dataclasses +import json +import time +from typing import Optional + +from beartype._decor.main import beartype + +from flet.control import Control +from flet.embed_json_encoder import EmbedJsonEncoder +from flet.ref import Ref + + +@beartype +@dataclasses.dataclass +class ClipboardData: + ts: str + d: Optional[str] + + +class Clipboard(Control): + def __init__( + self, + ref: Ref = None, + data: any = None, + # + # Specific + # + value: str = None, + ): + + Control.__init__( + self, + ref=ref, + data=data, + ) + + self.value = value + + def _get_control_name(self): + return "clipboard" + + # value + @property + def value(self): + return self.__value + + @value.setter + @beartype + def value(self, value: Optional[str]): + self.__value = value + cd = ClipboardData(str(time.time()), value) + self._set_attr("value", json.dumps(cd, cls=EmbedJsonEncoder) if value else None) diff --git a/sdk/python/flet/column.py b/sdk/python/flet/column.py index a3652f7b6..87dd311f5 100644 --- a/sdk/python/flet/column.py +++ b/sdk/python/flet/column.py @@ -65,6 +65,10 @@ def _get_control_name(self): def _get_children(self): return self.__controls + def clean(self): + Control.clean(self) + self.__controls.clear() + # tight @property def tight(self): diff --git a/sdk/python/flet/constrained_control.py b/sdk/python/flet/constrained_control.py index 26fdc0aeb..3756a58aa 100644 --- a/sdk/python/flet/constrained_control.py +++ b/sdk/python/flet/constrained_control.py @@ -12,6 +12,7 @@ def __init__( ref: Ref = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -26,6 +27,7 @@ def __init__( ref=ref, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, diff --git a/sdk/python/flet/container.py b/sdk/python/flet/container.py index cdbf64e90..ba203866a 100644 --- a/sdk/python/flet/container.py +++ b/sdk/python/flet/container.py @@ -26,6 +26,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -47,6 +48,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, diff --git a/sdk/python/flet/control.py b/sdk/python/flet/control.py index 4cc3bc6be..f5392d89c 100644 --- a/sdk/python/flet/control.py +++ b/sdk/python/flet/control.py @@ -1,6 +1,7 @@ import datetime as dt import threading from difflib import SequenceMatcher +from json import tool from typing import Union from beartype import beartype @@ -60,6 +61,7 @@ def __init__( ref: Ref = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -71,6 +73,7 @@ def __init__( self.__uid = None self.expand = expand self.opacity = opacity + self.tooltip = tooltip self.visible = visible self.disabled = disabled self.data = data @@ -185,6 +188,15 @@ def opacity(self): def opacity(self, value): self._set_attr("opacity", value) + # tooltip + @property + def tooltip(self): + return self._get_attr("tooltip") + + @tooltip.setter + def tooltip(self, value): + self._set_attr("tooltip", value) + # visible @property def visible(self): diff --git a/sdk/python/flet/dropdown.py b/sdk/python/flet/dropdown.py index 5886dae75..71c898f45 100644 --- a/sdk/python/flet/dropdown.py +++ b/sdk/python/flet/dropdown.py @@ -15,6 +15,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -40,7 +41,10 @@ def __init__( # DropDown Specific # value: str = None, + autofocus: bool = None, on_change=None, + on_focus=None, + on_blur=None, options=None, ): FormFieldControl.__init__( @@ -50,6 +54,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -75,7 +80,10 @@ def __init__( self.__options = [] self.value = value + self.autofocus = autofocus self.options = options + self.on_focus = on_focus + self.on_blur = on_blur self.on_change = on_change def _get_control_name(self): @@ -104,6 +112,16 @@ def value(self): def value(self, value): self._set_attr("value", value) + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) + # on_change @property def on_change(self): @@ -113,6 +131,24 @@ def on_change(self): 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) + class Option(Control): def __init__(self, key=None, text=None, disabled=None, ref=None): diff --git a/sdk/python/flet/elevated_button.py b/sdk/python/flet/elevated_button.py index d0aae9f1e..f3309e53b 100644 --- a/sdk/python/flet/elevated_button.py +++ b/sdk/python/flet/elevated_button.py @@ -16,6 +16,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -25,6 +26,7 @@ def __init__( icon: str = None, icon_color: str = None, content: Control = None, + autofocus: bool = None, on_click=None, ): ConstrainedControl.__init__( @@ -34,6 +36,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -43,6 +46,7 @@ def __init__( self.icon = icon self.icon_color = icon_color self.content = content + self.autofocus = autofocus self.on_click = on_click def _get_control_name(self): @@ -99,3 +103,13 @@ def content(self): @beartype def content(self, value: Optional[Control]): self.__content = value + + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) diff --git a/sdk/python/flet/floating_action_button.py b/sdk/python/flet/floating_action_button.py index c236b3785..34a9211d6 100644 --- a/sdk/python/flet/floating_action_button.py +++ b/sdk/python/flet/floating_action_button.py @@ -16,6 +16,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -25,6 +26,7 @@ def __init__( icon: str = None, bgcolor: str = None, content: Control = None, + autofocus: bool = None, on_click=None, ): ConstrainedControl.__init__( @@ -34,6 +36,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -43,6 +46,7 @@ def __init__( self.icon = icon self.bgcolor = bgcolor self.content = content + self.autofocus = autofocus self.on_click = on_click def _get_control_name(self): @@ -99,3 +103,13 @@ def content(self): @beartype def content(self, value: Optional[Control]): self.__content = value + + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) diff --git a/sdk/python/flet/form_field_control.py b/sdk/python/flet/form_field_control.py index 26a28709a..ab7ace862 100644 --- a/sdk/python/flet/form_field_control.py +++ b/sdk/python/flet/form_field_control.py @@ -18,6 +18,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -47,6 +48,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, diff --git a/sdk/python/flet/grid_view.py b/sdk/python/flet/grid_view.py index ba3282324..313df5963 100644 --- a/sdk/python/flet/grid_view.py +++ b/sdk/python/flet/grid_view.py @@ -27,8 +27,10 @@ def __init__( # horizontal: bool = None, runs_count: int = None, + max_extent: int = None, spacing: OptionalNumber = None, run_spacing: OptionalNumber = None, + child_aspect_ratio: OptionalNumber = None, padding: PaddingValue = None, ): ConstrainedControl.__init__( @@ -47,8 +49,10 @@ def __init__( self.controls = controls self.horizontal = horizontal self.runs_count = runs_count + self.max_extent = max_extent self.spacing = spacing self.run_spacing = run_spacing + self.child_aspect_ratio = child_aspect_ratio self.padding = padding def _get_control_name(self): @@ -57,6 +61,10 @@ def _get_control_name(self): def _get_children(self): return self.__controls + def clean(self): + Control.clean(self) + self.__controls.clear() + # horizontal @property def horizontal(self): @@ -77,6 +85,16 @@ def runs_count(self): def runs_count(self, value: Optional[int]): self._set_attr("runsCount", value) + # max_extent + @property + def max_extent(self): + return self._get_attr("maxExtent") + + @max_extent.setter + @beartype + def max_extent(self, value: OptionalNumber): + self._set_attr("maxExtent", value) + # spacing @property def spacing(self): @@ -97,6 +115,16 @@ def run_spacing(self): def run_spacing(self, value: OptionalNumber): self._set_attr("runSpacing", value) + # child_aspect_ratio + @property + def child_aspect_ratio(self): + return self._get_attr("childAspectRatio") + + @child_aspect_ratio.setter + @beartype + def child_aspect_ratio(self, value: OptionalNumber): + self._set_attr("childAspectRatio", value) + # padding @property def padding(self): diff --git a/sdk/python/flet/icon.py b/sdk/python/flet/icon.py index 4fb32dfee..2ce72f262 100644 --- a/sdk/python/flet/icon.py +++ b/sdk/python/flet/icon.py @@ -9,6 +9,7 @@ def __init__( ref: Ref = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -24,6 +25,7 @@ def __init__( ref=ref, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, diff --git a/sdk/python/flet/icon_button.py b/sdk/python/flet/icon_button.py index 1dc7d495e..9cd7fbf2c 100644 --- a/sdk/python/flet/icon_button.py +++ b/sdk/python/flet/icon_button.py @@ -16,16 +16,17 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, # # Specific # - tooltip: str = None, icon_size: OptionalNumber = None, icon_color: str = None, content: Control = None, + autofocus: bool = None, on_click=None, ): ConstrainedControl.__init__( @@ -35,16 +36,17 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, ) - self.tooltip = tooltip self.icon = icon self.icon_size = icon_size self.icon_color = icon_color self.content = content + self.autofocus = autofocus self.on_click = on_click def _get_control_name(self): @@ -56,15 +58,6 @@ def _get_children(self): self.__content._set_attr_internal("n", "content") return [self.__content] - # tooltip - @property - def tooltip(self): - return self._get_attr("tooltip") - - @tooltip.setter - def tooltip(self, value): - self._set_attr("tooltip", value) - # icon @property def icon(self): @@ -110,3 +103,13 @@ def content(self): @beartype def content(self, value: Optional[Control]): self.__content = value + + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) diff --git a/sdk/python/flet/image.py b/sdk/python/flet/image.py index 680d96f58..fb7501bd6 100644 --- a/sdk/python/flet/image.py +++ b/sdk/python/flet/image.py @@ -30,6 +30,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -47,6 +48,7 @@ def __init__( ref=ref, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, diff --git a/sdk/python/flet/list_view.py b/sdk/python/flet/list_view.py index 8433b7fe5..c55bd408f 100644 --- a/sdk/python/flet/list_view.py +++ b/sdk/python/flet/list_view.py @@ -55,6 +55,10 @@ def _get_control_name(self): def _get_children(self): return self.__controls + def clean(self): + Control.clean(self) + self.__controls.clear() + # horizontal @property def horizontal(self): diff --git a/sdk/python/flet/outlined_button.py b/sdk/python/flet/outlined_button.py index 489cd5c3c..70e371731 100644 --- a/sdk/python/flet/outlined_button.py +++ b/sdk/python/flet/outlined_button.py @@ -1,3 +1,4 @@ +from enum import auto from typing import Optional from beartype import beartype @@ -16,6 +17,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -25,6 +27,7 @@ def __init__( icon: str = None, icon_color: str = None, content: Control = None, + autofocus: bool = None, on_click=None, ): ConstrainedControl.__init__( @@ -34,6 +37,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -43,6 +47,7 @@ def __init__( self.icon = icon self.icon_color = icon_color self.content = content + self.autofocus = autofocus self.on_click = on_click def _get_control_name(self): @@ -99,3 +104,13 @@ def content(self): @beartype def content(self, value: Optional[Control]): self.__content = value + + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) diff --git a/sdk/python/flet/page.py b/sdk/python/flet/page.py index d84674067..5b1354f96 100644 --- a/sdk/python/flet/page.py +++ b/sdk/python/flet/page.py @@ -8,6 +8,7 @@ from flet import constants, padding from flet.banner import Banner +from flet.clipboard import Clipboard from flet.connection import Connection from flet.control import ( Control, @@ -320,6 +321,16 @@ def design(self): def design(self, value: PageDesign): self._set_attr("design", value) + # clipboard + @property + def clipboard(self): + return self.__offstage.clipboard.value + + @clipboard.setter + @beartype + def clipboard(self, value: Optional[str]): + self.__offstage.clipboard.value = value + # splash @property def splash(self): @@ -479,6 +490,7 @@ def __init__( data=data, ) + self.__clipboard = Clipboard() self.__banner = None self.__snack_bar = None self.__dialog = None @@ -489,6 +501,8 @@ def _get_control_name(self): def _get_children(self): children = [] + if self.__clipboard: + children.append(self.__clipboard) if self.__banner: children.append(self.__banner) if self.__snack_bar: @@ -499,6 +513,11 @@ def _get_children(self): children.append(self.__splash) return children + # clipboard + @property + def clipboard(self): + return self.__clipboard + # splash @property def splash(self): diff --git a/sdk/python/flet/progress_bar.py b/sdk/python/flet/progress_bar.py index d234c78f0..c26435a40 100644 --- a/sdk/python/flet/progress_bar.py +++ b/sdk/python/flet/progress_bar.py @@ -15,6 +15,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -33,6 +34,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, diff --git a/sdk/python/flet/progress_ring.py b/sdk/python/flet/progress_ring.py index 9d95a3100..e7a9ea256 100644 --- a/sdk/python/flet/progress_ring.py +++ b/sdk/python/flet/progress_ring.py @@ -20,6 +20,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -38,6 +39,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, diff --git a/sdk/python/flet/radio.py b/sdk/python/flet/radio.py index 313fab228..5fd4deb25 100644 --- a/sdk/python/flet/radio.py +++ b/sdk/python/flet/radio.py @@ -23,6 +23,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -32,6 +33,9 @@ def __init__( label: str = None, label_position: LabelPosition = None, value: str = None, + autofocus: bool = None, + on_focus=None, + on_blur=None, ): ConstrainedControl.__init__( self, @@ -40,6 +44,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -47,6 +52,9 @@ def __init__( self.value = value self.label = label self.label_position = label_position + self.autofocus = autofocus + self.on_focus = on_focus + self.on_blur = on_blur def _get_control_name(self): return "radio" @@ -78,3 +86,31 @@ def label_position(self): @beartype def label_position(self, value: LabelPosition): self._set_attr("labelPosition", value) + + # 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) + + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) diff --git a/sdk/python/flet/row.py b/sdk/python/flet/row.py index af4bf5602..a43dae9fc 100644 --- a/sdk/python/flet/row.py +++ b/sdk/python/flet/row.py @@ -65,6 +65,10 @@ def _get_control_name(self): def _get_children(self): return self.__controls + def clean(self): + Control.clean(self) + self.__controls.clear() + # tight @property def tight(self): diff --git a/sdk/python/flet/slider.py b/sdk/python/flet/slider.py index 9d17269ce..d3812a8b5 100644 --- a/sdk/python/flet/slider.py +++ b/sdk/python/flet/slider.py @@ -15,6 +15,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -26,7 +27,10 @@ def __init__( min: OptionalNumber = None, max: OptionalNumber = None, divisions: int = None, + autofocus: bool = None, on_change=None, + on_focus=None, + on_blur=None, ): ConstrainedControl.__init__( self, @@ -35,6 +39,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -44,7 +49,10 @@ def __init__( self.min = min self.max = max self.divisions = divisions + self.autofocus = autofocus self.on_change = on_change + self.on_focus = on_focus + self.on_blur = on_blur def _get_control_name(self): return "slider" @@ -98,6 +106,16 @@ def divisions(self): def divisions(self, value: Optional[int]): self._set_attr("divisions", value) + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) + # on_change @property def on_change(self): @@ -106,3 +124,21 @@ def on_change(self): @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) diff --git a/sdk/python/flet/snack_bar.py b/sdk/python/flet/snack_bar.py index 0e795511f..ea60a53e4 100644 --- a/sdk/python/flet/snack_bar.py +++ b/sdk/python/flet/snack_bar.py @@ -23,7 +23,7 @@ def __init__( # Specific # open: bool = False, - remove_current_snackbar: bool = False, + # remove_current_snackbar: bool = False, action: str = None, on_action=None, ): @@ -37,7 +37,7 @@ def __init__( ) self.open = open - self.remove_current_snackbar = remove_current_snackbar + # self.remove_current_snackbar = remove_current_snackbar self.content = content self.action = action self.on_action = on_action @@ -62,17 +62,17 @@ def open(self): def open(self, value: Optional[bool]): self._set_attr("open", value) - # remove_current_snackbar - @property - def remove_current_snackbar(self): - return self._get_attr( - "removeCurrentSnackBar", data_type="bool", def_value=False - ) - - @remove_current_snackbar.setter - @beartype - def remove_current_snackbar(self, value: Optional[bool]): - self._set_attr("removeCurrentSnackBar", value) + # # remove_current_snackbar + # @property + # def remove_current_snackbar(self): + # return self._get_attr( + # "removeCurrentSnackBar", data_type="bool", def_value=False + # ) + + # @remove_current_snackbar.setter + # @beartype + # def remove_current_snackbar(self, value: Optional[bool]): + # self._set_attr("removeCurrentSnackBar", value) # content @property diff --git a/sdk/python/flet/switch.py b/sdk/python/flet/switch.py index be829e0ab..96380d790 100644 --- a/sdk/python/flet/switch.py +++ b/sdk/python/flet/switch.py @@ -1,3 +1,4 @@ +from enum import auto from typing import Optional from beartype import beartype @@ -23,6 +24,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -32,7 +34,10 @@ def __init__( label: str = None, label_position: LabelPosition = None, value: bool = None, + autofocus: bool = None, on_change=None, + on_focus=None, + on_blur=None, ): ConstrainedControl.__init__( self, @@ -41,6 +46,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -48,7 +54,10 @@ def __init__( self.value = value self.label = label self.label_position = label_position + self.autofocus = autofocus self.on_change = on_change + self.on_focus = on_focus + self.on_blur = on_blur def _get_control_name(self): return "switch" @@ -82,6 +91,16 @@ def label_position(self): def label_position(self, value: LabelPosition): self._set_attr("labelPosition", value) + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) + # on_change @property def on_change(self): @@ -90,3 +109,21 @@ def on_change(self): @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) diff --git a/sdk/python/flet/tabs.py b/sdk/python/flet/tabs.py index b75e77bef..af5a9e6f2 100644 --- a/sdk/python/flet/tabs.py +++ b/sdk/python/flet/tabs.py @@ -1,6 +1,7 @@ -from typing import List, Optional +from typing import Optional from beartype import beartype +from beartype.typing import List from flet.constrained_control import ConstrainedControl from flet.control import Control, OptionalNumber diff --git a/sdk/python/flet/text.py b/sdk/python/flet/text.py index 08a5d07ec..9e8728304 100644 --- a/sdk/python/flet/text.py +++ b/sdk/python/flet/text.py @@ -40,6 +40,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -53,6 +54,7 @@ def __init__( style: str = None, overflow: TextOverflow = None, selectable: bool = None, + no_wrap: bool = None, color: str = None, bgcolor: str = None, ): @@ -64,6 +66,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -74,6 +77,7 @@ def __init__( self.size = size self.weight = weight self.italic = italic + self.no_wrap = no_wrap self.style = style self.overflow = overflow self.selectable = selectable @@ -142,6 +146,16 @@ def italic(self): def italic(self, value: Optional[bool]): self._set_attr("italic", value) + # no_wrap + @property + def no_wrap(self): + return self._get_attr("italic", data_type="noWrap", def_value=False) + + @no_wrap.setter + @beartype + def no_wrap(self, value: Optional[bool]): + self._set_attr("noWrap", value) + # selectable @property def selectable(self): diff --git a/sdk/python/flet/text_button.py b/sdk/python/flet/text_button.py index 82e2e3270..7ec1b2fec 100644 --- a/sdk/python/flet/text_button.py +++ b/sdk/python/flet/text_button.py @@ -16,6 +16,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -25,6 +26,7 @@ def __init__( icon: str = None, icon_color: str = None, content: Control = None, + autofocus: bool = None, on_click=None, ): ConstrainedControl.__init__( @@ -34,6 +36,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -43,6 +46,7 @@ def __init__( self.icon = icon self.icon_color = icon_color self.content = content + self.autofocus = autofocus self.on_click = on_click def _get_control_name(self): @@ -99,3 +103,13 @@ def content(self): @beartype def content(self, value: Optional[Control]): self.__content = value + + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) diff --git a/sdk/python/flet/textfield.py b/sdk/python/flet/textfield.py index 922498aa3..9968af6b1 100644 --- a/sdk/python/flet/textfield.py +++ b/sdk/python/flet/textfield.py @@ -35,6 +35,7 @@ def __init__( height: OptionalNumber = None, expand: int = None, opacity: OptionalNumber = None, + tooltip: str = None, visible: bool = None, disabled: bool = None, data: any = None, @@ -68,8 +69,11 @@ def __init__( read_only: bool = None, shift_enter: bool = None, text_align: TextAlign = None, + autofocus: bool = None, on_change=None, on_submit=None, + on_focus=None, + on_blur=None, ): FormFieldControl.__init__( self, @@ -78,6 +82,7 @@ def __init__( height=height, expand=expand, opacity=opacity, + tooltip=tooltip, visible=visible, disabled=disabled, data=data, @@ -109,8 +114,11 @@ def __init__( self.shift_enter = shift_enter self.password = password self.can_reveal_password = can_reveal_password + self.autofocus = autofocus self.on_change = on_change self.on_submit = on_submit + self.on_focus = on_focus + self.on_blur = on_blur def _get_control_name(self): return "textfield" @@ -204,6 +212,16 @@ def can_reveal_password(self): def can_reveal_password(self, value: Optional[bool]): self._set_attr("canRevealPassword", value) + # autofocus + @property + def autofocus(self): + return self._get_attr("autofocus", data_type="bool", def_value=False) + + @autofocus.setter + @beartype + def autofocus(self, value: Optional[bool]): + self._set_attr("autofocus", value) + # on_change @property def on_change(self): @@ -225,3 +243,21 @@ def on_submit(self): @on_submit.setter def on_submit(self, handler): self._add_event_handler("submit", 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) diff --git a/sdk/python/playground/buttons.py b/sdk/python/playground/buttons.py index 4ca7fe372..418f26bb6 100644 --- a/sdk/python/playground/buttons.py +++ b/sdk/python/playground/buttons.py @@ -31,7 +31,11 @@ def main(page: Page): Text("Elevated buttons", style="headlineMedium"), ElevatedButton("Normal button"), ElevatedButton("Disabled button", disabled=True), - ElevatedButton("Button with icon", icon="chair_outlined"), + ElevatedButton( + "Button with icon and tooltip", + icon="chair_outlined", + tooltip="Hey, click me!", + ), ElevatedButton( "Button with colorful icon", icon="park_rounded", diff --git a/sdk/python/playground/hello.py b/sdk/python/playground/hello.py new file mode 100644 index 000000000..88108298f --- /dev/null +++ b/sdk/python/playground/hello.py @@ -0,0 +1,9 @@ +import flet +from flet import Page, Text + + +def main(page: Page): + page.add(Text("Hello, world!")) + + +flet.app(target=main) diff --git a/sdk/python/playground/icons-browser.py b/sdk/python/playground/icons-browser.py new file mode 100644 index 000000000..be3532ffd --- /dev/null +++ b/sdk/python/playground/icons-browser.py @@ -0,0 +1,120 @@ +import os + +import flet +from flet import ( + Column, + Container, + GridView, + Icon, + IconButton, + Page, + Row, + SnackBar, + Text, + TextButton, + TextField, + alignment, + colors, + icons, +) + +# logging.basicConfig(level=logging.DEBUG) + +# fetch all icon constants from icons.py module +icons_list = [] +list_started = False +for key, value in vars(icons).items(): + if key == "TEN_K": + list_started = True + if list_started: + icons_list.append(value) + +os.environ["FLET_WS_MAX_MESSAGE_SIZE"] = "8000000" + + +def main(page: Page): + page.title = "Flet icons browser" + page.theme_mode = "light" + + search_txt = TextField( + expand=1, + hint_text="Enter keyword and press search button", + autofocus=True, + on_submit=lambda e: display_icons(e.control.value), + ) + search_results = GridView( + expand=1, + runs_count=10, + max_extent=150, + spacing=5, + run_spacing=5, + child_aspect_ratio=1, + ) + status_bar = Text() + + def copy_to_clipboard(e): + icon_key = e.control.data + print("Copy to clipboard:", icon_key) + page.clipboard = e.control.data + page.snack_bar = SnackBar(Text(f"Copied {icon_key}"), open=True) + page.update() + + def display_icons(search_term: str): + + # clean search results + search_results.clean() + + # add matching icons + for i in range(0, len(icons_list)): + if search_term != "" and search_term in icons_list[i]: + icon_name = icons_list[i] + icon_key = f"icons.{icon_name.upper()}" + search_results.controls.append( + TextButton( + content=Container( + content=Column( + [ + Icon(name=icon_name, size=30), + Text( + value=icon_name, + size=12, + width=100, + no_wrap=True, + text_align="center", + color=colors.ON_SURFACE_VARIANT, + ), + ], + spacing=5, + alignment="center", + horizontal_alignment="center", + ), + alignment=alignment.center, + ), + tooltip=f"{icon_key}\nClick to copy to a clipboard", + on_click=copy_to_clipboard, + data=icon_key, + ) + ) + + # update page on every 500 icons added + if i > 0 and i % 500 == 0: + status_bar.value = f"Icons found: {len(search_results.controls)}" + page.update() + status_bar.value = f"Icons found: {len(search_results.controls)}" + if len(search_results.controls) == 0: + search_results.controls.append( + Text(f'No icons found with text "{search_term}".') + ) + page.update() + + def search_click(e): + display_icons(search_txt.value) + + page.add( + Row([search_txt, IconButton(icon=icons.SEARCH, on_click=search_click)]), + search_results, + status_bar, + ) + + +flet.app(name="test1", port=8550, target=main, view=flet.WEB_BROWSER) diff --git a/sdk/python/playground/replace-test.py b/sdk/python/playground/replace-test.py new file mode 100644 index 000000000..5fb19fd99 --- /dev/null +++ b/sdk/python/playground/replace-test.py @@ -0,0 +1,18 @@ +import time + +import flet +from flet import Container, ElevatedButton, Text + + +def main(page): + + c = Container(content=Text("A")) + + def btn_click(e): + c.content = Text(str(time.time())) + page.update() + + page.add(c, ElevatedButton("Replace!", on_click=btn_click)) + + +flet.app(name="test1", port=8550, target=main, view=flet.WEB_BROWSER) diff --git a/sdk/python/playground/simple-snack.py b/sdk/python/playground/simple-snack.py new file mode 100644 index 000000000..a3c029942 --- /dev/null +++ b/sdk/python/playground/simple-snack.py @@ -0,0 +1,35 @@ +import logging + +import flet +from flet import ElevatedButton, SnackBar, Text + +logging.basicConfig(level=logging.DEBUG) + + +class Data: + def __init__(self) -> None: + self.counter = 0 + + +d = Data() + + +def main(page): + + page.snack_bar = SnackBar( + content=Text("Hello, world!"), + # remove_current_snackbar=True, + action="Alright!", + ) + + def on_click(e): + # page.snack_bar.content.value = f"Hello, world: {d.counter}" + page.snack_bar = SnackBar(Text(f"Hello {d.counter}")) + page.snack_bar.open = True + d.counter += 1 + page.update() + + page.add(ElevatedButton("Open SnackBar", on_click=on_click)) + + +flet.app(name="test1", port=8550, target=main, view=flet.WEB_BROWSER) diff --git a/sdk/python/playground/snackbar.py b/sdk/python/playground/snackbar.py index 75327930a..f6b080408 100644 --- a/sdk/python/playground/snackbar.py +++ b/sdk/python/playground/snackbar.py @@ -23,13 +23,14 @@ def action_click(e): page.snack_bar = SnackBar( content=Text("Hello, world!"), - remove_current_snackbar=True, + # remove_current_snackbar=True, action="Alright!", on_action=action_click, ) def on_click(e): - page.snack_bar.content.value = f"Hello, world: {d.counter}" + # page.snack_bar.content.value = f"Hello, world: {d.counter}" + page.snack_bar.content = Text(f"Hello, world: {d.counter}") page.snack_bar.open = True d.counter += 1 page.update() diff --git a/sdk/python/playground/todo.py b/sdk/python/playground/todo.py index a7e01c1fb..4261ccc79 100644 --- a/sdk/python/playground/todo.py +++ b/sdk/python/playground/todo.py @@ -177,4 +177,4 @@ def main(page: Page): page.add(app.view) -flet.app(name="test1", port=8550, target=main, view=flet.FLET_APP) +flet.app(name="test1", port=8550, target=main, view=flet.WEB_BROWSER) diff --git a/sdk/python/playground/visibility-test.py b/sdk/python/playground/visibility-test.py new file mode 100644 index 000000000..1794a8468 --- /dev/null +++ b/sdk/python/playground/visibility-test.py @@ -0,0 +1,21 @@ +from time import sleep + +import flet +from flet import Page, Text + + +def main(page: Page): + txt1 = Text("Line 1") + txt2 = Text("Line 2") + txt3 = Text("Line 3") + + page.add(txt1, txt2, txt3) + + sleep(4) + + txt2.visible = False + # page.content.pop(1) + page.update() + + +flet.app(name="test1", port=8550, target=main, view=flet.FLET_APP)