diff --git a/packages/flet/lib/src/controls/create_control.dart b/packages/flet/lib/src/controls/create_control.dart index a0df9be610..3e1f41b3a1 100644 --- a/packages/flet/lib/src/controls/create_control.dart +++ b/packages/flet/lib/src/controls/create_control.dart @@ -93,6 +93,7 @@ import 'progress_ring.dart'; import 'radio.dart'; import 'radio_group.dart'; import 'range_slider.dart'; +import 'reorderable_draggable.dart'; import 'reorderable_list_view.dart'; import 'responsive_row.dart'; import 'row.dart'; @@ -717,6 +718,15 @@ Widget createWidget( parentDisabled: parentDisabled, parentAdaptive: parentAdaptive, backend: backend); + case "reorderabledraggable": + return ReorderableDraggableControl( + key: key, + parent: parent, + control: controlView.control, + children: controlView.children, + parentDisabled: parentDisabled, + parentAdaptive: parentAdaptive, + ); case "gridview": return GridViewControl( key: key, diff --git a/packages/flet/lib/src/controls/reorderable_draggable.dart b/packages/flet/lib/src/controls/reorderable_draggable.dart new file mode 100644 index 0000000000..c55562f0e6 --- /dev/null +++ b/packages/flet/lib/src/controls/reorderable_draggable.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import 'create_control.dart'; +import 'error.dart'; + +class ReorderableDraggableControl extends StatefulWidget { + final Control? parent; + final Control control; + final bool parentDisabled; + final List children; + final bool? parentAdaptive; + + const ReorderableDraggableControl( + {super.key, + this.parent, + required this.control, + required this.children, + required this.parentDisabled, + required this.parentAdaptive}); + + @override + State createState() => _ListViewControlState(); +} + +class _ListViewControlState extends State { + @override + Widget build(BuildContext context) { + debugPrint("ReorderableDraggableControl build: ${widget.control.id}"); + + bool? adaptive = + widget.control.attrBool("adaptive") ?? widget.parentAdaptive; + + var index = widget.control.attrInt("index"); + if (index == null) { + return const ErrorControl("ReorderableDraggable.index is invalid"); + } + var content = widget.children.where((c) => c.isVisible).firstOrNull; + if (content == null) { + return const ErrorControl( + "ReorderableDraggable.content must be set and visible"); + } + + return ReorderableDragStartListener( + index: index, + child: createControl(widget.control, content.id, widget.parentDisabled, + parentAdaptive: adaptive)); + } +} diff --git a/packages/flet/lib/src/controls/reorderable_list_view.dart b/packages/flet/lib/src/controls/reorderable_list_view.dart index 15bbe088f2..0981c995de 100644 --- a/packages/flet/lib/src/controls/reorderable_list_view.dart +++ b/packages/flet/lib/src/controls/reorderable_list_view.dart @@ -49,7 +49,7 @@ class _ListViewControlState extends State { @override Widget build(BuildContext context) { - debugPrint("ListViewControl build: ${widget.control.id}"); + debugPrint("ReorderableDraggableControl build: ${widget.control.id}"); bool disabled = widget.control.isDisabled || widget.parentDisabled; bool? adaptive = @@ -64,7 +64,11 @@ class _ListViewControlState extends State { widget.control.attrBool("firstItemPrototype", false)!; var padding = parseEdgeInsets(widget.control, "padding"); var reverse = widget.control.attrBool("reverse", false)!; + var showDefaultDragHandles = + widget.control.attrBool("showDefaultDragHandles", true)!; var anchor = widget.control.attrDouble("anchor", 0.0)!; + var mouseCursor = + parseMouseCursor(widget.control.attrString("mouseCursor")); var clipBehavior = parseClip(widget.control.attrString("clipBehavior"), Clip.hardEdge)!; List ctrls = widget.children @@ -126,11 +130,13 @@ class _ListViewControlState extends State { clipBehavior: clipBehavior, reverse: reverse, cacheExtent: cacheExtent, + buildDefaultDragHandles: showDefaultDragHandles, scrollDirection: scrollDirection, shrinkWrap: shrinkWrap, padding: padding, itemCount: ctrls.length, itemExtent: itemExtent, + mouseCursor: mouseCursor, anchor: anchor, header: header, footer: footer, @@ -151,6 +157,7 @@ class _ListViewControlState extends State { cacheExtent: cacheExtent, reverse: reverse, clipBehavior: clipBehavior, + buildDefaultDragHandles: showDefaultDragHandles, scrollDirection: scrollDirection, shrinkWrap: shrinkWrap, padding: padding, @@ -158,6 +165,7 @@ class _ListViewControlState extends State { header: header, footer: footer, itemExtent: itemExtent, + mouseCursor: mouseCursor, prototypeItem: prototypeItem, autoScrollerVelocityScalar: autoScrollerVelocityScalar, mouseCursor: mouseCursor, 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 ce5cee63ab..b3bda35cde 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 @@ -5,7 +5,6 @@ import re import shutil import sys -import time from pathlib import Path from typing import Optional, cast @@ -1145,7 +1144,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 f1f8156186..c105a1d9c4 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -5,6 +5,7 @@ from flet.core.animated_switcher import AnimatedSwitcher, AnimatedSwitcherTransition from flet.core.animation import Animation, AnimationCurve from flet.core.app_bar import AppBar +from flet.core.reorderable_draggable import ReorderableDraggable from flet.core.audio import ( Audio, AudioDurationChangeEvent, diff --git a/sdk/python/packages/flet/src/flet/core/reorderable_draggable.py b/sdk/python/packages/flet/src/flet/core/reorderable_draggable.py new file mode 100644 index 0000000000..c32c67a8e2 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/core/reorderable_draggable.py @@ -0,0 +1,117 @@ +from typing import Any, Optional, Union + +from flet.core.adaptive_control import AdaptiveControl +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, + PaddingValue, + ResponsiveNumber, + RotateValue, + ScaleValue, +) + + +class ReorderableDraggable(ConstrainedControl, AdaptiveControl): + def __init__( + self, + index: int, + content: Control, + # + # ConstrainedControl + # + ref: Optional[Ref] = None, + key: Optional[str] = None, + width: OptionalNumber = None, + height: 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, + visible: Optional[bool] = None, + disabled: Optional[bool] = None, + data: Any = None, + rtl: Optional[bool] = None, + # + # Adaptive + # + adaptive: Optional[bool] = None, + ): + ConstrainedControl.__init__( + self, + ref=ref, + key=key, + width=width, + height=height, + 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, + visible=visible, + disabled=disabled, + data=data, + rtl=rtl, + ) + + AdaptiveControl.__init__(self, adaptive=adaptive) + + self.content = content + self.index = index + + def _get_control_name(self): + return "reorderabledraggable" + + def before_update(self): + super().before_update() + assert self.__content.visible, "content must be visible" + + def _get_children(self): + self.__content._set_attr_internal("n", "content") + return [self.__content] + + # index + @property + def index(self) -> int: + return self._get_attr("index", data_type="int") + + @index.setter + def index(self, value: int): + self._set_attr("index", value) + + # content + @property + def content(self) -> Control: + return self.__content + + @content.setter + def content(self, value: Control): + self.__content = value diff --git a/sdk/python/packages/flet/src/flet/core/reorderable_list_view.py b/sdk/python/packages/flet/src/flet/core/reorderable_list_view.py index 548af0183d..aa65a4b82c 100644 --- a/sdk/python/packages/flet/src/flet/core/reorderable_list_view.py +++ b/sdk/python/packages/flet/src/flet/core/reorderable_list_view.py @@ -18,6 +18,7 @@ ResponsiveNumber, RotateValue, ScaleValue, + MouseCursor, ) @@ -52,6 +53,7 @@ def __init__( header: Optional[Control] = None, footer: Optional[Control] = None, build_controls_on_demand: Optional[bool] = None, + show_default_drag_handles: Optional[bool] = None, mouse_cursor: Optional[MouseCursor] = None, on_reorder: OptionalEventCallable[OnReorderEvent] = None, on_reorder_start: OptionalEventCallable[OnReorderEvent] = None, @@ -147,6 +149,8 @@ def __init__( self.on_reorder = on_reorder self.anchor = anchor self.auto_scroller_velocity_scalar = auto_scroller_velocity_scalar + self.show_default_drag_handles = show_default_drag_handles + self.mouse_cursor = mouse_cursor self.on_reorder_start = on_reorder_start self.on_reorder_end = on_reorder_end self.mouse_cursor = mouse_cursor @@ -213,6 +217,27 @@ def footer(self) -> Optional[Control]: def footer(self, value: Optional[Control]): self.__footer = value + # show_default_drag_handles + @property + def show_default_drag_handles(self) -> Optional[bool]: + return self._get_attr( + "showDefaultDragHandles", data_type="bool", def_value=True + ) + + @show_default_drag_handles.setter + def show_default_drag_handles(self, value: Optional[bool]): + self._set_attr("showDefaultDragHandles", value) + + # mouse_cursor + @property + def mouse_cursor(self) -> Optional[MouseCursor]: + return self.__mouse_cursor + + @mouse_cursor.setter + def mouse_cursor(self, value: Optional[MouseCursor]): + self.__mouse_cursor = value + self._set_enum_attr("mouseCursor", value, MouseCursor) + # on_reorder @property def on_reorder(self) -> OptionalEventCallable[OnReorderEvent]: