Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Add `ft.use_dialog()` hook for declarative dialog management from within `@ft.component` functions, with frozen-diff reactive updates and automatic open/close lifecycle ([#6335](https://github.com/flet-dev/flet/pull/6335)) by @FeodorFitsner.
* Add `scrollable`, `pin_leading_to_top`, and `pin_trailing_to_bottom` properties to `NavigationRail` for scrollable content with optional pinned leading/trailing controls ([#1923](https://github.com/flet-dev/flet/issues/1923), [#6356](https://github.com/flet-dev/flet/pull/6356)) by @ndonkoHenri.
* Add `Page.pop_views_until()` to pop multiple views and return a result to the destination view ([#6326](https://github.com/flet-dev/flet/issues/6326), [#6347](https://github.com/flet-dev/flet/pull/6347)) by @brunobrown.
* Add `local_position` and `global_position` to `DragTargetEvent`, deprecating `x`, `y`, and `offset` ([#6387](https://github.com/flet-dev/flet/issues/6387), [#6401](https://github.com/flet-dev/flet/pull/6401)) by @ndonkoHenri.

### Improvements

Expand Down
50 changes: 35 additions & 15 deletions packages/flet/lib/src/controls/drag_target.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ import 'draggable.dart';

class DragTargetEvent {
final int srcId;
final double x;
final double y;
final Offset localPosition;
final Offset globalPosition;

DragTargetEvent({
required this.srcId,
required this.x,
required this.y,
required this.localPosition,
required this.globalPosition,
});

Map<String, dynamic> toMap() =>
<String, dynamic>{'src_id': srcId, 'x': x, 'y': y};
Map<String, dynamic> toMap() => <String, dynamic>{
'src_id': srcId,
'l': {'x': localPosition.dx, 'y': localPosition.dy},
'g': {'x': globalPosition.dx, 'y': globalPosition.dy},
};
}

class DragTargetControl extends StatelessWidget {
Expand All @@ -37,22 +40,28 @@ class DragTargetControl extends StatelessWidget {
return const ErrorControl("DragTarget.content must be visible");
}

BuildContext? dragTargetContext;

return DragTarget<DraggableData>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
dragTargetContext = context;
return content;
},
onMove: (DragTargetDetails<DraggableData> details) {
final globalPosition = details.offset;
final localPosition =
_getLocalPosition(dragTargetContext, globalPosition);
control.triggerEvent(
"move",
DragTargetEvent(
srcId: details.data.id,
x: details.offset.dx,
y: details.offset.dy)
.toMap());
srcId: details.data.id,
localPosition: localPosition,
globalPosition: globalPosition,
).toMap());
},
onWillAcceptWithDetails: (DragTargetDetails<DraggableData> details) {
var groupMatch = details.data.group == group;
Expand All @@ -61,17 +70,28 @@ class DragTargetControl extends StatelessWidget {
return groupMatch;
},
onAcceptWithDetails: (DragTargetDetails<DraggableData> details) {
final globalPosition = details.offset;
final localPosition =
_getLocalPosition(dragTargetContext, globalPosition);
control.triggerEvent(
"accept",
DragTargetEvent(
srcId: details.data.id,
x: details.offset.dx,
y: details.offset.dy)
.toMap());
srcId: details.data.id,
localPosition: localPosition,
globalPosition: globalPosition,
).toMap());
},
onLeave: (DraggableData? data) {
control.triggerEvent("leave", {"src_id", data?.id});
control.triggerEvent("leave", {"src_id": data?.id});
},
);
}

Offset _getLocalPosition(BuildContext? context, Offset globalPosition) {
final renderObject = context?.findRenderObject();
if (renderObject is! RenderBox || !renderObject.hasSize) {
return globalPosition;
}
return renderObject.globalToLocal(globalPosition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ def handle_drag_will_accept(e: ft.DragWillAcceptEvent):
e.control.update()

def handle_drag_accept(e: ft.DragTargetEvent):
src = page.get_control(e.src_id)
e.control.content.bgcolor = src.content.bgcolor
e.control.content.bgcolor = e.src.content.bgcolor
e.control.content.border = None
e.control.update()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import flet as ft


def main(page: ft.Page):
def refresh_position(e: ft.DragTargetEvent):
lp_label.value = (
f"local_position: ({e.local_position.x:.1f}, {e.local_position.y:.1f})"
)
gp_label.value = (
f"global_position: ({e.global_position.x:.1f}, {e.global_position.y:.1f})"
)
target.update()

def handle_will_accept(e: ft.DragWillAcceptEvent):
target.content.border = ft.Border.all(3, ft.Colors.RED)
target.update()

def handle_move(e: ft.DragTargetEvent):
refresh_position(e)

def handle_accept(e: ft.DragTargetEvent):
refresh_position(e)

def handle_leave(e: ft.DragTargetLeaveEvent):
target.content.border = ft.Border.all(3, ft.Colors.BLUE_GREY_300)
target.update()

page.add(
ft.SafeArea(
content=ft.Column(
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.Row(
spacing=24,
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.Draggable(
group="demo",
content_feedback=ft.Container(
width=40,
height=40,
border_radius=18,
alignment=ft.Alignment.CENTER,
bgcolor=ft.Colors.with_opacity(0.5, ft.Colors.RED),
border=ft.Border.all(
1.5,
ft.Colors.with_opacity(
0.35, ft.Colors.BLUE_500
),
),
shadow=ft.BoxShadow(
blur_radius=16,
color=ft.Colors.with_opacity(
0.16, ft.Colors.BLUE_500
),
offset=ft.Offset(0, 6),
),
content=ft.Icon(
ft.Icons.OPEN_WITH_ROUNDED,
color=ft.Colors.BLUE_600,
),
),
content=ft.Container(
width=72,
height=72,
border_radius=12,
bgcolor=ft.Colors.BLUE_500,
alignment=ft.Alignment.CENTER,
content=ft.Text("Drag me", color=ft.Colors.WHITE),
),
),
target := ft.DragTarget(
group="demo",
on_will_accept=handle_will_accept,
on_move=handle_move,
on_accept=handle_accept,
on_leave=handle_leave,
content=ft.Container(
width=290,
height=180,
padding=16,
border=ft.Border.all(2, ft.Colors.BLUE_GREY_300),
border_radius=12,
bgcolor=ft.Colors.BLUE_GREY_50,
content=ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.Text(
"Drop here",
size=16,
weight=ft.FontWeight.BOLD,
),
lp_label := ft.Text(
"local_position: -",
text_align=ft.TextAlign.CENTER,
),
gp_label := ft.Text(
"global_position: -",
text_align=ft.TextAlign.CENTER,
),
],
),
),
),
],
),
],
),
)
)


if __name__ == "__main__":
ft.run(main)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[project]
name = "drag-target-positions"
version = "1.0.0"
description = "Shows DragTarget local_position and global_position values updating live while a draggable moves inside the target."
requires-python = ">=3.10"
keywords = ["draggable", "drag target", "drag position", "local position", "global position"]
authors = [{ name = "Flet team", email = "hello@flet.dev" }]
dependencies = ["flet"]

[dependency-groups]
dev = ["flet-cli", "flet-desktop", "flet-web"]

[tool.flet.gallery]
categories = ["Utility/Draggable"]

[tool.flet.metadata]
title = "DragTarget positions"
controls = ["SafeArea", "Column", "Row", "Draggable", "DragTarget", "Container", "Text", "Icon"]
layout_pattern = "single-panel"
complexity = "basic"
features = ["live drag coordinates", "local_position", "global_position", "drop target feedback"]

[tool.flet]
org = "dev.flet"
company = "Flet"
copyright = "Copyright (C) 2023-2026 by Flet"
Loading
Loading