Last active
January 4, 2024 11:21
-
-
Save osa1/6f2d55b87726ce1c6232a37a9705509b to your computer and use it in GitHub Desktop.
listbox-dnd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A drag-and-drop example. You can drag the list items using the icon on the left, and drop them | |
// to other icons to move the list item. Originally listbox-dnd.c in GTK+ repository. Ported to | |
// gtk-rs by Ömer Sinan Ağacan <omeragacan@gmail.com>. | |
extern crate cairo; | |
extern crate gdk; | |
extern crate gtk; | |
use gdk::{Atom, DragAction, DragContext, ModifierType, Screen}; | |
use gtk::*; | |
use std::cell::RefCell; | |
use std::rc::Rc; | |
fn main() { | |
if gtk::init().is_err() { | |
println!("Failed to initialize GTK."); | |
return; | |
} | |
let css_provider = CssProvider::new(); | |
css_provider.load_from_data(CSS.as_bytes()).unwrap(); | |
StyleContext::add_provider_for_screen( | |
Screen::get_default().as_ref().unwrap(), | |
&css_provider, | |
STYLE_PROVIDER_PRIORITY_APPLICATION, | |
); | |
let window = Window::new(WindowType::Toplevel); | |
window.set_title("First GTK+ Program"); | |
window.set_default_size(350, 500); | |
let list_box = ListBox::new(); | |
list_box.set_selection_mode(SelectionMode::None); | |
window.add(&list_box); | |
window.connect_delete_event(|_, _| { | |
gtk::main_quit(); | |
Inhibit(false) | |
}); | |
// This is where we store the widget being dragged. | |
let widget_being_dragged: Rc<RefCell<Option<Widget>>> = Rc::new(RefCell::new(None)); | |
// Add rows | |
for i in 0..20 { | |
list_box.add(&create_row(i, widget_being_dragged.clone())); | |
} | |
window.show_all(); | |
gtk::main(); | |
} | |
fn create_row(i: i32, widget_being_dragged: Rc<RefCell<Option<Widget>>>) -> ListBoxRow { | |
let list_box_row = ListBoxRow::new(); | |
let box_ = Box::new(Orientation::Horizontal, 5); | |
box_.set_margin_start(10); | |
box_.set_margin_end(10); | |
list_box_row.add(&box_); | |
let event_box = EventBox::new(); | |
let image = Image::new_from_icon_name("open-menu-symbolic", IconSize::Button); | |
event_box.add(&image); | |
box_.add(&event_box); | |
let label_text = format!("Row {}", i); | |
let label = Label::new(Some(label_text.as_str())); | |
box_.add(&label); | |
// Drag stuff | |
// | |
// Make the event box draggable. Triggered with left mouse button. | |
// | |
let drag_targets: [TargetEntry; 1] = [TargetEntry::new( | |
"GTK_LIST_BOX_ROW", // I don't know how this string is used | |
TargetFlags::SAME_APP, | |
0, | |
)]; | |
event_box.drag_source_set(ModifierType::BUTTON1_MASK, &drag_targets, DragAction::MOVE); | |
// drag_begin will be called when we start dragging. drag_begin is where we call rendering the | |
// widget being dragged next to mouse cursor. | |
event_box.connect_drag_begin(drag_begin); | |
// We don't use this, but just to demonstrate that things can be done when the drag ends. | |
event_box.connect_drag_end(drag_end); | |
// See https://developer.gnome.org/gtkmm-tutorial/stable/sec-dnd-signals.html.en | |
// Called when a source is being dragged and the target calls get_data() | |
{ | |
let r = widget_being_dragged.clone(); | |
event_box.connect_drag_data_get(move |w, ctx, sel, i1, i2| { | |
drag_data_get(&mut *r.borrow_mut(), w, ctx, sel, i1, i2) | |
}); | |
} | |
// Set the same box as a drag destination. | |
event_box.drag_dest_set(DestDefaults::ALL, &drag_targets, DragAction::MOVE); | |
// Called when get_data() returns | |
{ | |
let r = widget_being_dragged.clone(); | |
event_box.connect_drag_data_received(move |target, ctx, x, y, sel, info, time| { | |
drag_data_received( | |
r.borrow().as_ref().unwrap(), | |
target, | |
ctx, | |
x, | |
y, | |
sel, | |
info, | |
time, | |
) | |
}); | |
} | |
list_box_row | |
} | |
fn drag_begin<W: IsA<Widget>>(w: &W, ctx: &DragContext) { | |
println!("drag_begin({:?})", w); | |
// Get containing ListBoxRow and render it next to the cursor while being dragged | |
match w.get_ancestor(ListBoxRow::static_type()) { | |
None => { | |
println!("Can't get ancestor"); | |
} | |
Some(ancestor) => { | |
let alloc = ancestor.get_allocation(); | |
let surface: cairo::ImageSurface = | |
cairo::ImageSurface::create(cairo::Format::ARgb32, alloc.width, alloc.height) | |
.unwrap(); | |
let surface_: &cairo::Surface = surface.as_ref(); | |
{ | |
ancestor.get_style_context().add_class("drag-icon"); | |
let surface_ctx = cairo::Context::new(surface_); | |
ancestor.draw(&surface_ctx); | |
ancestor.get_style_context().remove_class("drag-icon"); | |
} | |
let (x, y) = w.translate_coordinates(&ancestor, 0, 0).unwrap(); | |
surface.set_device_offset(-x as f64, -y as f64); | |
ctx.drag_set_icon_surface(surface_); | |
} | |
} | |
} | |
fn drag_end<W: IsA<Widget>>(w: &W, _ctx: &DragContext) { | |
println!("drag_end({:?})", w); | |
} | |
fn drag_data_get<W: IsA<Widget>>( | |
widget_being_dragged: &mut Option<Widget>, | |
w: &W, | |
_ctx: &DragContext, | |
sel: &SelectionData, | |
_: u32, | |
_: u32, | |
) { | |
println!("drag_data_get({:?}", w); | |
*widget_being_dragged = Some(w.as_ref().clone()); | |
// We have to call this to emit drag_data_received() on the target | |
sel.set(&Atom::intern(""), 32, &[]); | |
} | |
fn drag_data_received<W: IsA<Widget>>( | |
widget_being_dragged: &Widget, | |
target: &W, | |
_ctx: &DragContext, | |
_x: i32, | |
_y: i32, | |
_sel: &SelectionData, | |
_info: u32, | |
_time: u32, | |
) { | |
println!("drag_data_received({:?})", target); | |
println!("Widget being dragged: {:?}", widget_being_dragged); | |
// The widget being dragged is a EventBox because that's what we set as draggable. We need to | |
// get its containing ListBoxRow. | |
let source = match widget_being_dragged.get_ancestor(ListBoxRow::static_type()) { | |
None => { | |
println!("Can't get parent of source {:?}!!!", widget_being_dragged); | |
return; | |
} | |
Some(parent) => parent, | |
}; | |
// Downcast it | |
let source = source.downcast_ref::<ListBoxRow>().unwrap(); | |
// Same for the target | |
let target = match target.get_ancestor(ListBoxRow::static_type()) { | |
None => { | |
println!("Can't get parent of target {:?}!!!", widget_being_dragged); | |
return; | |
} | |
Some(parent) => parent, | |
}; | |
let target = target.downcast_ref::<ListBoxRow>().unwrap(); | |
let target_row = target.get_index(); | |
if source == target { | |
println!("Source and target are the same. Aborting drag."); | |
return; | |
} | |
// Remove source from its parent | |
source | |
.get_parent() | |
.unwrap() | |
.downcast_ref::<Container>() | |
.unwrap() | |
.remove(source); | |
// Add it next to target | |
target | |
.get_parent() | |
.unwrap() | |
.downcast_ref::<ListBox>() | |
.unwrap() | |
.insert(source, target_row); | |
} | |
static CSS: &'static str = " | |
.drag-icon { | |
background: white; | |
border: 1px solid black; | |
} | |
"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment