From 0eacd255e8bb6c930a9cce792ad4419c5c4b58d9 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Mon, 25 Jan 2021 16:26:44 +0100 Subject: [PATCH] Add drag and drop to work editor --- src/editors/work.rs | 37 +++++++++++++++------------------ src/widgets/list.rs | 50 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/editors/work.rs b/src/editors/work.rs index 814f73e..c275887 100644 --- a/src/editors/work.rs +++ b/src/editors/work.rs @@ -74,6 +74,7 @@ impl WorkEditor { instrument_frame.set_child(Some(&instrument_list.widget)); let part_list = List::new(); + part_list.set_enable_dnd(true); structure_frame.set_child(Some(&part_list.widget)); let (id, composer, instruments, structure) = match work { @@ -208,29 +209,17 @@ impl WorkEditor { this.part_list.set_make_widget_cb(clone!(@strong this => move |index| { let pos = &this.structure.borrow()[index]; - let drag_source = gtk::DragSource::new(); - drag_source.set_content(Some(&gdk::ContentProvider::new_for_value(&(index as u32).to_value()))); - - let drop_target = gtk::DropTarget::new(glib::Type::U32, gdk::DragAction::MOVE); - - drop_target.connect_property_value_notify(clone!(@strong this => move |drop_target| { - println!("{:?} -> {:?}", drop_target.get_value(), index); - })); - - let handle = gtk::Image::from_icon_name(Some("open-menu-symbolic")); - handle.add_controller(&drag_source); - let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); delete_button.set_valign(gtk::Align::Center); delete_button.connect_clicked(clone!(@strong this => move |_| { - let length = { - let mut structure = this.structure.borrow_mut(); - structure.remove(index); - structure.len() - }; + let length = { + let mut structure = this.structure.borrow_mut(); + structure.remove(index); + structure.len() + }; - this.part_list.update(length); + this.part_list.update(length); })); let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); @@ -279,11 +268,9 @@ impl WorkEditor { let row = libhandy::ActionRow::new(); row.set_activatable(true); row.set_title(Some(&pos.get_title())); - row.add_prefix(&handle); row.add_suffix(&delete_button); row.add_suffix(&edit_button); row.set_activatable_widget(Some(&edit_button)); - row.add_controller(&drop_target); if let PartOrSection::Part(_) = pos { // TODO: Replace with better solution to differentiate parts and sections. @@ -293,6 +280,16 @@ impl WorkEditor { row.upcast() })); + this.part_list.set_move_cb(clone!(@strong this => move |old_index, new_index| { + let length = { + let mut structure = this.structure.borrow_mut(); + structure.swap(old_index, new_index); + structure.len() + }; + + this.part_list.update(length); + })); + add_part_button.connect_clicked(clone!(@strong this => move |_| { let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 4f4d90d..f95ac5c 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -1,7 +1,7 @@ use super::indexed_list_model::{IndexedListModel, ItemIndex}; use glib::clone; use gtk::prelude::*; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::rc::Rc; /// A simple list of widgets. @@ -9,8 +9,10 @@ pub struct List { pub widget: gtk::ListBox, model: IndexedListModel, filter: gtk::CustomFilter, + enable_dnd: Cell, make_widget_cb: RefCell gtk::Widget>>>, filter_cb: RefCell bool>>>, + move_cb: RefCell>>, } impl List { @@ -31,8 +33,10 @@ impl List { widget, model, filter, + enable_dnd: Cell::new(false), make_widget_cb: RefCell::new(None), filter_cb: RefCell::new(None), + move_cb: RefCell::new(None), }); this.filter.set_filter_func(clone!(@strong this => move |index| { @@ -47,8 +51,39 @@ impl List { this.widget.bind_model(Some(&filter_model), clone!(@strong this => move |index| { let index = index.downcast_ref::().unwrap().get() as usize; if let Some(cb) = &*this.make_widget_cb.borrow() { - cb(index) + let widget = cb(index); + + if this.enable_dnd.get() { + let drag_source = gtk::DragSource::new(); + + drag_source.connect_drag_begin(clone!(@strong widget => move |_, drag| { + // TODO: Replace with a better solution. + let paintable = gtk::WidgetPaintable::new(Some(&widget)); + gtk::DragIcon::set_from_paintable(drag, &paintable, 0, 0); + })); + + let drag_value = (index as u32).to_value(); + drag_source.set_content(Some(&gdk::ContentProvider::new_for_value(&drag_value))); + + let drop_target = gtk::DropTarget::new(glib::Type::U32, gdk::DragAction::COPY); + + drop_target.connect_drop(clone!(@strong this => move |_, value, _, _| { + if let Some(cb) = &*this.move_cb.borrow() { + let old_index: u32 = value.get_some().unwrap(); + cb(old_index as usize, index); + true + } else { + false + } + })); + + widget.add_controller(&drag_source); + widget.add_controller(&drop_target); + } + + widget } else { + // This shouldn't be reachable under normal circumstances. gtk::Label::new(None).upcast() } })); @@ -56,6 +91,11 @@ impl List { this } + /// Whether the list should support drag and drop. + pub fn set_enable_dnd(&self, enable: bool) { + self.enable_dnd.set(enable); + } + /// Set the closure to be called to construct widgets for the items. pub fn set_make_widget_cb gtk::Widget + 'static>(&self, cb: F) { self.make_widget_cb.replace(Some(Box::new(cb))); @@ -67,6 +107,12 @@ impl List { self.filter_cb.replace(Some(Box::new(cb))); } + /// Set the closure to be called to when the use has dragged an item to a + /// new position. + pub fn set_move_cb(&self, cb: F) { + self.move_cb.replace(Some(Box::new(cb))); + } + /// Select an item by its index. If the index is out of range, nothing will happen. pub fn select(&self, index: usize) { let row = self.widget.get_row_at_index(index as i32);