yoda: direct drag-drop reorder on the toolbar itself

User feedback: going through Settings to reorder is too indirect. Now
each toolbar button is wrapped in dnd_source + dnd_destination so the
user can grab an icon in the live toolbar and drop it onto another
icon to reorder in-place. The underlying icon button keeps firing its
on_press for quick clicks (dnd_source only starts a drag past the
default 8 px motion threshold), so regular clicks continue to run the
associated Action — no mode-switch needed.

Settings retains the ↑↓/add/remove UI as a fallback (discoverability
+ keyboard-friendly) and for the same drag-drop if the user prefers
working from the panel. The config model (Vec<ToolbarAction> +
ToolbarReorder message) is already shared, so both paths mutate the
same state.
This commit is contained in:
Lionel DARNIS 2026-04-24 08:37:19 +02:00
parent 11d435770e
commit af843d204d

View file

@ -6807,25 +6807,44 @@ impl Application for App {
// Yoda phase 3: Dolphin-style quick actions toolbar. Items are
// rendered from self.config.toolbar (Vec<ToolbarAction>) — the user
// picks the set AND the order via drag-drop in Settings. Dispatch
// goes through Action::message so keybinding and toolbar share the
// same code path.
// picks the set AND the order via direct drag-drop on the toolbar.
// Short click = action (shared Action::message dispatch); drag past
// the default 8px threshold = reorder (ToolbarReorder message).
if !self.config.toolbar.is_empty() {
use cosmic::iced::clipboard::dnd::DndAction as DndAct;
let clipboard_has = self.clipboard_has_content();
let buttons: Vec<Element<_>> = self
.config
.toolbar
.iter()
.map(|a| {
let (icon, label, msg) = toolbar_action_ui(*a);
let enabled = !matches!(a, ToolbarAction::Paste) || clipboard_has;
let action = *a;
let (icon, label, msg) = toolbar_action_ui(action);
let enabled = !matches!(action, ToolbarAction::Paste) || clipboard_has;
let btn = widget::button::icon(widget::icon::from_name(icon).size(16));
let btn = if enabled { btn.on_press(msg) } else { btn };
widget::tooltip(
let tooltip = widget::tooltip(
btn,
widget::text::body(label),
widget::tooltip::Position::Bottom,
);
let source = widget::dnd_source::<Message, ToolbarActionPayload>(tooltip)
.drag_content(move || ToolbarActionPayload(action.to_u8()));
widget::dnd_destination(
source,
vec![std::borrow::Cow::Borrowed(TOOLBAR_MIME)],
)
.data_received_for::<ToolbarActionPayload>(
move |payload: Option<ToolbarActionPayload>| {
match payload.and_then(|p| ToolbarAction::from_u8(p.0)) {
Some(src) if src != action => {
Message::ToolbarReorder { src, target: action }
}
_ => Message::ToolbarReorder { src: action, target: action },
}
},
)
.action(DndAct::Move)
.into()
})
.collect();