diff --git a/packages/flet/lib/src/controls/window_drag_area.dart b/packages/flet/lib/src/controls/window_drag_area.dart new file mode 100644 index 000000000..69bb1d2a5 --- /dev/null +++ b/packages/flet/lib/src/controls/window_drag_area.dart @@ -0,0 +1,61 @@ +import 'package:flet/src/extensions/control.dart'; +import 'package:flet/src/utils/events.dart'; +import 'package:flet/src/utils/numbers.dart'; +import 'package:flutter/material.dart'; +import 'package:window_manager/window_manager.dart'; + +import '../models/control.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; + +class WindowDragAreaControl extends StatelessWidget { + final Control control; + + const WindowDragAreaControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("WindowDragArea build: ${control.id}"); + + var content = control.buildWidget("content"); + + if (content == null) { + return const ErrorControl( + "WindowDragArea.content must be provided and visible"); + } + + final wda = GestureDetector( + behavior: HitTestBehavior.translucent, + onPanStart: (DragStartDetails details) { + windowManager.startDragging(); + if (control.getBool("on_drag_start", false)!) { + control.triggerEvent("drag_start", details.toMap()); + } + }, + onPanEnd: (DragEndDetails details) { + if (control.getBool("on_drag_end", false)!) { + control.triggerEvent("drag_end", details.toMap()); + } + }, + onDoubleTap: control.getBool("maximizable", true)! + ? () async { + final isMaximized = await windowManager.isMaximized(); + if (isMaximized) { + windowManager.unmaximize(); + } else { + windowManager.maximize(); + } + + // trigger event + if (control.getBool("on_double_tap", false)!) { + control.triggerEvent( + "double_tap", isMaximized ? "unmaximize" : "maximize"); + } + } + : null, + child: content, + ); + + return ConstrainedControl(control: control, child: wda); + } +} diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index 51c99fea2..e5d10598e 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -104,6 +104,7 @@ import 'controls/transparent_pointer.dart'; import 'controls/vertical_divider.dart'; import 'controls/view.dart'; import 'controls/window.dart'; +import 'controls/window_drag_area.dart'; import 'flet_extension.dart'; import 'flet_service.dart'; import 'models/control.dart'; @@ -331,7 +332,8 @@ class FletCoreExtension extends FletExtension { return CupertinoRadioControl(key: key, control: control); case "Window": return WindowControl(key: key, control: control); - + case "WindowDragArea": + return WindowDragAreaControl(key: key, control: control); case "Pagelet": return PageletControl(key: key, control: control); default: diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 8f0ae8930..c0175514e 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: provider: ^6.1.2 web_socket_channel: ^3.0.2 msgpack_dart: ^1.0.1 - window_manager: ^0.4.3 + window_manager: ^0.5.0 window_to_front: ^0.0.3 collection: ^1.19.0 flutter_svg: 2.1.0 diff --git a/sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py b/sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py index 9968d089c..346f26b51 100644 --- a/sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py +++ b/sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py @@ -1,60 +1,59 @@ from typing import Any - +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl from flet.controls.control import Control -from flet.controls.core.gesture_detector import ( - DragStartEvent, - GestureDetector, - TapEvent, -) +from flet.controls.core.window import WindowEvent +from flet.controls.events import DragStartEvent +from flet.controls.events import DragEndEvent from flet.controls.types import OptionalEventCallable -class WindowDragArea(GestureDetector): +@control("WindowDragArea") +class WindowDragArea(ConstrainedControl): """ A control for drag to move, maximize and restore application window. - When you have hidden the title bar with `page.window_title_bar_hidden`, you can add + When you have hidden the title bar with `page.window_title_bar_hidden`, you can add this control to move the window position. Online docs: https://flet.dev/docs/controls/windowdragarea """ - def __init__( - self, - content: Control, - maximizable: bool = True, - on_double_tap: OptionalEventCallable["TapEvent"] = None, - on_pan_start: OptionalEventCallable["DragStartEvent"] = None, - **kwargs: Any, - ): - GestureDetector.__init__( - self, - content=content, - on_double_tap=self.handle_double_tap, - on_pan_start=self.handle_pan_start, - **kwargs, - ) - - self.maximizable = maximizable - self.on_double_tap = on_double_tap - self.on_pan_start = on_pan_start + content: Control + """ + The content of this `WindowDragArea`. + """ + + maximizable: bool = True + """ + Whether double-clicking on the `WindowDragArea` should maximize/maximize the app's window. + Defaults to `True`. + """ + + on_double_tap: OptionalEventCallable[WindowEvent] = None + """ + Fires when the `WindowDragArea` is double-tapped and `maximizable=True`. + + Event handler argument is of type `WindowEvent`, + with its `type` property being one of the following: `WindowEventType.MAXIMIZE`, `WindowEventType.UNMAXIMIZE` + """ + + on_drag_start: OptionalEventCallable[DragStartEvent] = None + """ + Fires when a pointer has contacted the screen and has begun to move/drag. + + Event handler argument is of type + [`DragStartEvent`](https://flet.dev/docs/reference/types/dragstartevent). + """ + + on_drag_end: OptionalEventCallable[DragEndEvent] = None + """ + Fires when a pointer that was previously in contact with the screen and moving/dragging is no longer in contact with the screen. + + Event handler argument is of type + [`DragEndEvent`](https://flet.dev/docs/reference/types/dragendevent). + """ def before_update(self): super().before_update() 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)