segmented_button: fix internal tab reorder end-to-end

Two independent bugs prevented tab drag-and-drop reorder from working
on cosmic-comp (and likely other compositors):

1. allow_reorder required DndAction::Move to be negotiated via
   OfferEvent::SelectedAction, which cosmic-comp does not always emit
   for self-drops (the SelectedAction event either never arrives or
   arrives with DndAction::empty()). Add a fallback: accept self-drops
   whenever state.dragging_tab is set. dragging_tab is only populated
   by start_tab_drag on this same widget, so this is safe; mime match
   and on_reorder presence are checked below.

2. reorder_event_for_drop preferred drop_hint.side over positional
   swap, producing counter-intuitive no-ops: dropping A (pos 0) on the
   left half of B (pos 1) resolved to "Before B" which, after removing
   A, lands at pos 0 again — the tab appeared not to move. Always use
   default_insert_position, which derives direction from dragged vs
   target positions (Konsole/Firefox/Chrome-style swap semantics).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lionel DARNIS 2026-04-22 11:09:46 +02:00
parent 108441ef61
commit a322516f33

View file

@ -406,11 +406,12 @@ where
{ {
return None; return None;
} }
let position = state // Always use positional swap (Konsole/Firefox/Chrome semantics):
.drop_hint // dropping onto any part of a different tab swaps it with the dragged
.filter(|hint| hint.entity == target) // tab. drop_hint.side-based Before/After is counter-intuitive: dropping
.map(|hint| InsertPosition::from(hint.side)) // A (pos 0) on the left half of B (pos 1) resolves to "Before B" which,
.unwrap_or_else(|| self.default_insert_position(dragged, target)); // after removing A, lands at pos 0 — so the tab appears not to move.
let position = self.default_insert_position(dragged, target);
Some(ReorderEvent { Some(ReorderEvent {
dragged, dragged,
target, target,
@ -1201,7 +1202,14 @@ where
.dnd_state .dnd_state
.drag_offer .drag_offer
.as_ref() .as_ref()
.is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); .is_some_and(|offer| offer.selected_action.contains(DndAction::Move))
// Self-drop fallback: some compositors (cosmic-comp
// observed) do not emit OfferEvent::SelectedAction for
// internal drags, leaving selected_action empty.
// dragging_tab is only set by start_tab_drag on this
// same widget, so this covers the self-drop case
// safely; mime and on_reorder are checked below.
|| state.dragging_tab.is_some();
let pending_reorder = if allow_reorder let pending_reorder = if allow_reorder
&& self.on_reorder.is_some() && self.on_reorder.is_some()
&& self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type) && self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type)