|
5 | 5 |
|
6 | 6 | import slint |
7 | 7 | from slint import DataTransfer, ListModel |
| 8 | +from slint.language import DragAction, DropEvent |
8 | 9 |
|
9 | 10 | TaskData = slint.loader.kanban.TaskData |
10 | 11 |
|
11 | 12 |
|
12 | 13 | # What we attach to each `DataTransfer` via `set_user_data`. A copy of the |
13 | | -# `TaskData` plus the row it came from, so `source-column-of` can answer the |
14 | | -# .slint side and `move-task` knows what to remove on a move. |
| 14 | +# `TaskData` plus the row it came from, so `can-drop` recognizes our own |
| 15 | +# payloads and `dropped` knows what to remove on a move. |
15 | 16 | @dataclass |
16 | 17 | class DragPayload: |
17 | 18 | task: TaskData |
@@ -50,56 +51,53 @@ def make_data( |
50 | 51 | transfer.user_data = DragPayload(task, source_column, source_index) |
51 | 52 | return transfer |
52 | 53 |
|
53 | | - @slint.callback(global_name="Api", name="source-column-of") |
54 | | - def source_column_of(self, data: DataTransfer) -> int: |
55 | | - payload = data.user_data |
56 | | - return payload.source_column if isinstance(payload, DragPayload) else -1 |
| 54 | + @slint.callback(global_name="Api", name="can-drop") |
| 55 | + def can_drop( |
| 56 | + self, event: DropEvent, target_column: int, target_index: int |
| 57 | + ) -> DragAction: |
| 58 | + if isinstance(event.data.user_data, DragPayload): |
| 59 | + # Our own card: accept whatever modifier the user is holding. |
| 60 | + return event.proposed_action |
| 61 | + if event.data.has_plaintext: |
| 62 | + # External plaintext drop: always treated as a copy. |
| 63 | + return DragAction.copy |
| 64 | + return DragAction.none |
57 | 65 |
|
58 | | - @slint.callback(global_name="Api", name="has-plaintext") |
59 | | - def has_plaintext(self, data: DataTransfer) -> bool: |
60 | | - return data.has_plaintext |
61 | | - |
62 | | - @slint.callback(global_name="Api", name="add-task") |
63 | | - def add_task( |
64 | | - self, data: DataTransfer, target_column: int, target_index: int |
65 | | - ) -> None: |
| 66 | + @slint.callback(global_name="Api", name="dropped") |
| 67 | + def dropped(self, event: DropEvent, target_column: int, target_index: int) -> None: |
66 | 68 | if not 0 <= target_column < len(self._columns): |
67 | 69 | return |
68 | | - payload = data.user_data |
69 | | - if isinstance(payload, DragPayload): |
70 | | - self._columns[target_column].insert(target_index, payload.task) |
71 | | - return |
72 | | - text = data.fetch_plaintext() |
73 | | - if text is not None: |
74 | | - self._columns[target_column].insert(target_index, TaskData(title=text)) |
| 70 | + payload = event.data.user_data |
75 | 71 |
|
76 | | - @slint.callback(global_name="Api", name="move-task") |
77 | | - def move_task( |
78 | | - self, data: DataTransfer, target_column: int, target_index: int |
79 | | - ) -> None: |
80 | | - payload = data.user_data |
81 | | - if not isinstance(payload, DragPayload): |
82 | | - return |
83 | | - if not 0 <= target_column < len(self._columns): |
84 | | - return |
85 | | - source = payload.source_column |
86 | | - source_index = payload.source_index |
87 | | - |
88 | | - if source == target_column: |
89 | | - # Same-column reorder. Drops at the source slot or immediately |
90 | | - # after it are no-ops; otherwise remove the source first and |
91 | | - # adjust the target index for the shift that the removal causes. |
92 | | - if target_index == source_index or target_index == source_index + 1: |
| 72 | + if isinstance(payload, DragPayload): |
| 73 | + if event.proposed_action != DragAction.move: |
| 74 | + # Anything that isn't an explicit move is treated as a copy. |
| 75 | + self._columns[target_column].insert(target_index, payload.task) |
93 | 76 | return |
94 | | - task = payload.task |
95 | | - del self._columns[source][source_index] |
96 | | - adjusted = target_index - 1 if target_index > source_index else target_index |
97 | | - self._columns[target_column].insert(adjusted, task) |
| 77 | + source = payload.source_column |
| 78 | + source_index = payload.source_index |
| 79 | + |
| 80 | + if source == target_column: |
| 81 | + # Same-column reorder. Drops at the source slot or immediately |
| 82 | + # after it are no-ops; otherwise remove the source first and |
| 83 | + # adjust the target index for the shift that the removal causes. |
| 84 | + if target_index == source_index or target_index == source_index + 1: |
| 85 | + return |
| 86 | + task = payload.task |
| 87 | + del self._columns[source][source_index] |
| 88 | + adjusted = ( |
| 89 | + target_index - 1 if target_index > source_index else target_index |
| 90 | + ) |
| 91 | + self._columns[target_column].insert(adjusted, task) |
| 92 | + else: |
| 93 | + # Cross-column move. Source and target are independent models, |
| 94 | + # so the order of operations doesn't affect index stability. |
| 95 | + del self._columns[source][source_index] |
| 96 | + self._columns[target_column].insert(target_index, payload.task) |
98 | 97 | else: |
99 | | - # Cross-column move. Source and target are independent models, so |
100 | | - # the order of operations doesn't affect index stability. |
101 | | - del self._columns[source][source_index] |
102 | | - self._columns[target_column].insert(target_index, payload.task) |
| 98 | + text = event.data.fetch_plaintext() |
| 99 | + if text is not None: |
| 100 | + self._columns[target_column].insert(target_index, TaskData(title=text)) |
103 | 101 |
|
104 | 102 |
|
105 | 103 | main_window = MainWindow() |
|
0 commit comments