From af843d204d3c489d04120150063da3093c1901a4 Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 08:37:19 +0200 Subject: [PATCH] yoda: direct drag-drop reorder on the toolbar itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 + ToolbarReorder message) is already shared, so both paths mutate the same state. --- src/app.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1dbf6e6..c742244 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6807,25 +6807,44 @@ impl Application for App { // Yoda phase 3: Dolphin-style quick actions toolbar. Items are // rendered from self.config.toolbar (Vec) — 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> = 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::(tooltip) + .drag_content(move || ToolbarActionPayload(action.to_u8())); + widget::dnd_destination( + source, + vec![std::borrow::Cow::Borrowed(TOOLBAR_MIME)], ) + .data_received_for::( + move |payload: Option| { + 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();