Skip to content

v1: rework WindowDragArea #5349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/flet/lib/src/controls/window_drag_area.dart
Original file line number Diff line number Diff line change
@@ -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);
}
}
4 changes: 3 additions & 1 deletion packages/flet/lib/src/flet_core_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion packages/flet/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
85 changes: 42 additions & 43 deletions sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py
Original file line number Diff line number Diff line change
@@ -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)