diff --git a/packages/flet/lib/flet.dart b/packages/flet/lib/flet.dart index 704f9f46b..ec32a4a3f 100644 --- a/packages/flet/lib/flet.dart +++ b/packages/flet/lib/flet.dart @@ -19,23 +19,41 @@ export 'src/models/page_size_view_model.dart'; export 'src/utils.dart'; export 'src/utils/alignment.dart'; export 'src/utils/animations.dart'; +export 'src/utils/auto_complete.dart'; +export 'src/utils/autofill.dart'; +export 'src/utils/badge.dart'; export 'src/utils/borders.dart'; export 'src/utils/box.dart'; +export 'src/utils/browser_context_menu.dart'; export 'src/utils/buttons.dart'; +export 'src/utils/charts.dart'; +export 'src/utils/client_storage.dart'; +export 'src/utils/clipboard.dart'; export 'src/utils/collections.dart'; export 'src/utils/colors.dart'; +export 'src/utils/cupertino_colors.dart'; +export 'src/utils/cupertino_icons.dart'; export 'src/utils/dash_path.dart'; export 'src/utils/debouncer.dart'; +export 'src/utils/desktop.dart'; +export 'src/utils/dismissible.dart'; export 'src/utils/drawing.dart'; export 'src/utils/edge_insets.dart'; +export 'src/utils/form_field.dart'; export 'src/utils/gradient.dart'; export 'src/utils/icons.dart'; export 'src/utils/images.dart'; +export 'src/utils/launch_url.dart'; +export 'src/utils/locale.dart'; +export 'src/utils/markdown.dart'; +export 'src/utils/material_icons.dart'; export 'src/utils/material_state.dart'; export 'src/utils/menu.dart'; export 'src/utils/mouse.dart'; +export 'src/utils/networking.dart'; export 'src/utils/numbers.dart'; export 'src/utils/others.dart'; +export 'src/utils/overlay_style.dart'; export 'src/utils/platform.dart'; export 'src/utils/platform_utils_non_web.dart' if (dart.library.js) "src/utils/platform_utils_web.dart"; @@ -45,4 +63,5 @@ export 'src/utils/text.dart'; export 'src/utils/textfield.dart'; export 'src/utils/theme.dart'; export 'src/utils/time.dart'; +export 'src/utils/tooltip.dart'; export 'src/utils/transforms.dart'; diff --git a/packages/flet/lib/src/controls/create_control.dart b/packages/flet/lib/src/controls/create_control.dart index 2c8bb8843..c8f2d4fe0 100644 --- a/packages/flet/lib/src/controls/create_control.dart +++ b/packages/flet/lib/src/controls/create_control.dart @@ -117,7 +117,6 @@ import 'textfield.dart'; import 'time_picker.dart'; import 'transparent_pointer.dart'; import 'vertical_divider.dart'; -import 'window_drag_area.dart'; Widget createControl(Control? parent, String id, bool parentDisabled, {Widget? nextChild, bool? parentAdaptive}) { @@ -979,13 +978,6 @@ Widget createWidget( parentAdaptive: parentAdaptive, children: controlView.children, ); - case "windowdragarea": - return WindowDragAreaControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); case "linechart": return LineChartControl( key: key, diff --git a/packages/flet/lib/src/controls/file_picker.dart b/packages/flet/lib/src/controls/file_picker.dart index 9a9dbcde6..64f5a7285 100644 --- a/packages/flet/lib/src/controls/file_picker.dart +++ b/packages/flet/lib/src/controls/file_picker.dart @@ -177,7 +177,7 @@ class _FilePickerControlState extends State sendEvent(); }); } - // saveFile + // getDirectoryPath else if (state?.toLowerCase() == "getdirectorypath" && !kIsWeb) { FilePicker.platform .getDirectoryPath( diff --git a/packages/flet/lib/src/controls/window_drag_area.dart b/packages/flet/lib/src/controls/window_drag_area.dart deleted file mode 100644 index e213b104d..000000000 --- a/packages/flet/lib/src/controls/window_drag_area.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:window_manager/window_manager.dart'; - -import '../models/control.dart'; -import 'create_control.dart'; -import 'error.dart'; - -class WindowDragAreaControl extends StatelessWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - - const WindowDragAreaControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); - - @override - Widget build(BuildContext context) { - debugPrint("WindowDragArea build: ${control.id}"); - - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool maximizable = control.attrBool("maximizable", true)!; - bool disabled = control.isDisabled || parentDisabled; - - if (contentCtrls.isEmpty) { - return const ErrorControl( - "WindowDragArea.content must be provided and visible"); - } - - return constrainedControl( - context, - WindowDragArea( - maximizable: maximizable, - child: createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive)), - parent, - control); - } -} - -class WindowDragArea extends StatelessWidget { - final Widget child; - final bool maximizable; - - const WindowDragArea( - {super.key, required this.child, required this.maximizable}); - - @override - Widget build(BuildContext context) { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onPanStart: (details) { - windowManager.startDragging(); - }, - onDoubleTap: maximizable - ? () async { - bool isMaximized = await windowManager.isMaximized(); - if (!isMaximized) { - windowManager.maximize(); - } else { - windowManager.unmaximize(); - } - } - : null, - child: child, - ); - } -} diff --git a/packages/flet/lib/src/reducers.dart b/packages/flet/lib/src/reducers.dart index 8b606dd3f..fead8e084 100644 --- a/packages/flet/lib/src/reducers.dart +++ b/packages/flet/lib/src/reducers.dart @@ -5,8 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:device_info_plus/device_info_plus.dart'; - - import 'actions.dart'; import 'flet_control_backend.dart'; import 'models/app_state.dart'; @@ -97,7 +95,8 @@ AppState appReducer(AppState state, dynamic action) { try { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - if (androidInfo.systemFeatures.contains('android.software.leanback')) { + if (androidInfo.systemFeatures + .contains('android.software.leanback')) { platformValue = "android_tv"; } } on Exception catch (e) { @@ -324,6 +323,9 @@ AppState appReducer(AppState state, dynamic action) { case "windowToFront": windowToFront(); break; + case "windowStartDragging": + windowStartDragging(); + break; } var clientStoragePrefix = "clientStorage:"; if (action.payload.methodName.startsWith(clientStoragePrefix)) { diff --git a/packages/flet/lib/src/utils/desktop.dart b/packages/flet/lib/src/utils/desktop.dart index 981e2c972..6dfefcb01 100644 --- a/packages/flet/lib/src/utils/desktop.dart +++ b/packages/flet/lib/src/utils/desktop.dart @@ -235,6 +235,12 @@ Future windowToFront() async { } } +Future windowStartDragging() async { + if (isDesktopPlatform()) { + await windowManager.startDragging(); + } +} + Future blurWindow() async { if (isDesktopPlatform() && (defaultTargetPlatform == TargetPlatform.windows || diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py index b2c186ab2..f060f3cf2 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py @@ -10,19 +10,10 @@ from typing import Optional, cast import flet.version -import flet_cli.utils.processes as processes import yaml from flet.utils import cleanup_path, copy_tree, is_windows, slugify from flet.utils.platform_utils import get_bool_env_var from flet.version import update_version -from flet_cli.commands.base import BaseCommand -from flet_cli.utils.hash_stamp import HashStamp -from flet_cli.utils.merge import merge_dict -from flet_cli.utils.project_dependencies import ( - get_poetry_dependencies, - get_project_dependencies, -) -from flet_cli.utils.pyproject_toml import load_pyproject_toml from packaging import version from packaging.requirements import Requirement from rich.console import Console, Group @@ -33,6 +24,16 @@ from rich.table import Column, Table from rich.theme import Theme +import flet_cli.utils.processes as processes +from flet_cli.commands.base import BaseCommand +from flet_cli.utils.hash_stamp import HashStamp +from flet_cli.utils.merge import merge_dict +from flet_cli.utils.project_dependencies import ( + get_poetry_dependencies, + get_project_dependencies, +) +from flet_cli.utils.pyproject_toml import load_pyproject_toml + PYODIDE_ROOT_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.2/full" DEFAULT_TEMPLATE_URL = "gh:flet-dev/flet-build-template" @@ -75,7 +76,6 @@ def __init__(self, parser: argparse.ArgumentParser) -> None: self.python_module_name = None self.get_pyproject = None self.python_app_path = None - self.no_rich_output = None self.emojis = {} self.dart_exe = None self.verbose = False @@ -1145,7 +1145,9 @@ def setup_template_data(self): "target_arch": ( target_arch if isinstance(target_arch, list) - else [target_arch] if isinstance(target_arch, str) else [] + else [target_arch] + if isinstance(target_arch, str) + else [] ), "info_plist": info_plist, "macos_entitlements": macos_entitlements, diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 8df93d480..86163d2a8 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -400,13 +400,24 @@ AppLifecycleState, AppView, BlendMode, + BorderRadiusValue, Brightness, ClipBehavior, + ColorEnums, + ColorValue, + ControlEventType, ControlState, + ControlStateValue, CrossAxisAlignment, + DateTimeValue, Duration, + DurationValue, + EventType, FloatingActionButtonLocation, FontWeight, + IconEnums, + IconValue, + IconValueOrControl, ImageFit, ImageRepeat, LabelPosition, @@ -416,12 +427,18 @@ MouseCursor, NotchShape, Number, + OffsetValue, OnFocusEvent, + OptionalControlEventCallable, OptionalEventCallable, OptionalNumber, Orientation, PaddingValue, PagePlatform, + PointerDeviceType, + ResponsiveNumber, + RotateValue, + ScaleValue, ScrollMode, StrokeCap, StrokeJoin, diff --git a/sdk/python/packages/flet/src/flet/core/gesture_detector.py b/sdk/python/packages/flet/src/flet/core/gesture_detector.py index 28e70c9cf..1e7010531 100644 --- a/sdk/python/packages/flet/src/flet/core/gesture_detector.py +++ b/sdk/python/packages/flet/src/flet/core/gesture_detector.py @@ -79,37 +79,39 @@ def __init__( exclude_from_semantics: Optional[bool] = None, trackpad_scroll_causes_scale: Optional[bool] = None, allowed_devices: Optional[Set[PointerDeviceType]] = None, - on_tap=None, - on_tap_down=None, - on_tap_up=None, - on_multi_tap=None, - multi_tap_touches=None, - on_multi_long_press=None, - on_secondary_tap=None, - on_secondary_tap_down=None, - on_secondary_tap_up=None, - on_long_press_start=None, - on_long_press_end=None, - on_secondary_long_press_start=None, - on_secondary_long_press_end=None, - on_double_tap=None, - on_double_tap_down=None, - on_horizontal_drag_start=None, - on_horizontal_drag_update=None, - on_horizontal_drag_end=None, - on_vertical_drag_start=None, - on_vertical_drag_update=None, - on_vertical_drag_end=None, - on_pan_start=None, - on_pan_update=None, - on_pan_end=None, - on_scale_start=None, - on_scale_update=None, - on_scale_end=None, - on_hover=None, - on_enter=None, - on_exit=None, - on_scroll=None, + on_tap: OptionalEventCallable["TapEvent"] = None, + on_tap_down: OptionalEventCallable["TapEvent"] = None, + on_tap_up: OptionalEventCallable["TapEvent"] = None, + on_multi_tap: OptionalEventCallable["TapEvent"] = None, + multi_tap_touches: Optional[int] = None, + on_multi_long_press: OptionalEventCallable["LongPressEndEvent"] = None, + on_secondary_tap: OptionalEventCallable["TapEvent"] = None, + on_secondary_tap_down: OptionalEventCallable["TapEvent"] = None, + on_secondary_tap_up: OptionalEventCallable["TapEvent"] = None, + on_long_press_start: OptionalEventCallable["LongPressEndEvent"] = None, + on_long_press_end: OptionalEventCallable["LongPressEndEvent"] = None, + on_secondary_long_press_start: OptionalEventCallable[ + "LongPressEndEvent" + ] = None, + on_secondary_long_press_end: OptionalEventCallable["LongPressEndEvent"] = None, + on_double_tap: OptionalEventCallable["TapEvent"] = None, + on_double_tap_down: OptionalEventCallable["TapEvent"] = None, + on_horizontal_drag_start: OptionalEventCallable["DragStartEvent"] = None, + on_horizontal_drag_update: OptionalEventCallable["DragUpdateEvent"] = None, + on_horizontal_drag_end: OptionalEventCallable["DragEndEvent"] = None, + on_vertical_drag_start: OptionalEventCallable["DragStartEvent"] = None, + on_vertical_drag_update: OptionalEventCallable["DragUpdateEvent"] = None, + on_vertical_drag_end: OptionalEventCallable["DragEndEvent"] = None, + on_pan_start: OptionalEventCallable["DragStartEvent"] = None, + on_pan_update: OptionalEventCallable["DragUpdateEvent"] = None, + on_pan_end: OptionalEventCallable["DragEndEvent"] = None, + on_scale_start: OptionalEventCallable["ScaleStartEvent"] = None, + on_scale_update: OptionalEventCallable["ScaleUpdateEvent"] = None, + on_scale_end: OptionalEventCallable["ScaleEndEvent"] = None, + on_hover: OptionalEventCallable["HoverEvent"] = None, + on_enter: OptionalEventCallable["HoverEvent"] = None, + on_exit: OptionalEventCallable["HoverEvent"] = None, + on_scroll: OptionalEventCallable["ScrollEvent"] = None, # # ConstrainedControl # @@ -329,6 +331,7 @@ def _get_control_name(self): return "gesturedetector" def before_update(self): + super().before_update() self._set_attr_json("allowedDevices", self.__allowed_devices) def _get_children(self): diff --git a/sdk/python/packages/flet/src/flet/core/page.py b/sdk/python/packages/flet/src/flet/core/page.py index b2b3eb8ed..6c9f8fabb 100644 --- a/sdk/python/packages/flet/src/flet/core/page.py +++ b/sdk/python/packages/flet/src/flet/core/page.py @@ -503,6 +503,9 @@ def close(self) -> None: def to_front(self) -> None: self.page._invoke_method("windowToFront") + def start_dragging(self) -> None: + self.page._invoke_method("windowStartDragging") + # Events # on_event @property diff --git a/sdk/python/packages/flet/src/flet/core/window_drag_area.py b/sdk/python/packages/flet/src/flet/core/window_drag_area.py index 314489fd6..6773ffa62 100644 --- a/sdk/python/packages/flet/src/flet/core/window_drag_area.py +++ b/sdk/python/packages/flet/src/flet/core/window_drag_area.py @@ -1,21 +1,11 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class WindowDragArea(ConstrainedControl): +from typing import Any + +from flet.core.control import Control +from flet.core.gesture_detector import DragStartEvent, GestureDetector, TapEvent +from flet.core.types import OptionalEventCallable + + +class WindowDragArea(GestureDetector): """ A control for drag to move, maximize and restore application window. @@ -49,97 +39,39 @@ def main(page: ft.Page): def __init__( self, content: Control, - maximizable: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, + maximizable: bool = True, + on_double_tap: OptionalEventCallable["TapEvent"] = None, + on_pan_start: OptionalEventCallable["DragStartEvent"] = None, + **kwargs: Any, ): - ConstrainedControl.__init__( + GestureDetector.__init__( self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, + content=content, + on_double_tap=self.handle_double_tap, + on_pan_start=self.handle_pan_start, + **kwargs, ) - self.content = content self.maximizable = maximizable - - def _get_control_name(self): - return "windowDragArea" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] + self.on_double_tap = on_double_tap + self.on_pan_start = on_pan_start def before_update(self): super().before_update() - assert self.__content.visible, "content must be visible" - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # maximizable - @property - def maximizable(self) -> bool: - return self._get_attr("maximizable", data_type="bool", def_value=True) - - @maximizable.setter - def maximizable(self, value: Optional[bool]): - self._set_attr("maximizable", value) + assert self.content.visible, "content must be visible" + + def handle_double_tap(self, e: TapEvent): + if self.maximizable and self.page.window.maximizable: + if not self.page.window.maximized: + self.page.window.maximized = True + else: + self.page.window.maximized = False + self.page.update() + + if self.on_double_tap is not None and self.page.window.maximized: + self.on_double_tap(e) + + def handle_pan_start(self, e: DragStartEvent): + self.page.window.start_dragging() + if self.on_pan_start is not None: + self.on_pan_start(e)