Skip to content

Instantly share code, notes, and snippets.

@osa1
Last active January 4, 2024 11:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save osa1/6f2d55b87726ce1c6232a37a9705509b to your computer and use it in GitHub Desktop.
Save osa1/6f2d55b87726ce1c6232a37a9705509b to your computer and use it in GitHub Desktop.
listbox-dnd
// 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