Skip to content

Instantly share code, notes, and snippets.

@JMoerman
Created April 29, 2017 10:10
Show Gist options
  • Save JMoerman/6f2fa1494847ce7b7044b99787ccc769 to your computer and use it in GitHub Desktop.
Save JMoerman/6f2fa1494847ce7b7044b99787ccc769 to your computer and use it in GitHub Desktop.
port of https://blog.gtk.org/2017/04/23/drag-and-drop-in-lists/ with scrolling and more advanced drag and drop
using Gtk;
class DragListBox : ListBox {
private ListBoxRow? hover_row;
private ListBoxRow? drag_row;
private bool top = false;
private int hover_top;
private int hover_bottom;
private bool should_scroll = false;
private bool scrolling = false;
private bool scroll_up;
private const int SCROLL_STEP_SIZE = 8;
private const int SCROLL_DISTANCE = 30;
private const int SCROLL_DELAY = 50;
public Adjustment? vadjustment {
public set {
_vadjustment = value;
if (_vadjustment == null) {
should_scroll = false;
}
}
public get {
return _vadjustment;
}
}
private Adjustment? _vadjustment;
const TargetEntry[] entries = {
{ "GTK_LIST_BOX_ROW", Gtk.TargetFlags.SAME_APP, 0}
};
public DragListBox () {
drag_dest_set (this, Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE);
}
void row_drag_begin (Widget widget, Gdk.DragContext context) {
ListBoxRow row;
Allocation alloc;
Cairo.Surface surface;
Cairo.Context cr;
int x, y;
DragListBox parent;
row = (ListBoxRow) widget.get_ancestor (typeof (ListBoxRow));
row.get_allocation (out alloc);
surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, alloc.width, alloc.height);
cr = new Cairo.Context (surface);
parent = row.get_parent () as DragListBox;
if (parent != null)
parent.drag_row = row;
row.get_style_context ().add_class ("drag-icon");
row.draw (cr);
row.get_style_context ().remove_class ("drag-icon");
widget.translate_coordinates (row, 0, 0, out x, out y);
surface.set_device_offset (-x, -y);
drag_set_icon_surface (context, surface);
}
public override bool drag_motion (
Gdk.DragContext context, int x, int y, uint time_
) {
if (y > hover_top || y < hover_bottom) {
Allocation alloc;
var row = get_row_at_y (y);
bool old_top = top;
row.get_allocation (out alloc);
int hover_row_y = alloc.y;
int hover_row_height = alloc.height;
if (row != drag_row) {
if (y < hover_row_y + hover_row_height/2) {
hover_top = hover_row_y;
hover_bottom = hover_top + hover_row_height/2;
row.get_style_context ().add_class ("drag-hover-top");
row.get_style_context ().remove_class ("drag-hover-bottom");
top = true;
} else {
hover_top = hover_row_y + hover_row_height/2;
hover_bottom = hover_row_y + hover_row_height;
row.get_style_context ().add_class ("drag-hover-bottom");
row.get_style_context ().remove_class ("drag-hover-top");
top = false;
}
}
if (hover_row != null && hover_row != row) {
if (old_top)
hover_row.get_style_context ().remove_class ("drag-hover-top");
else
hover_row.get_style_context ().remove_class ("drag-hover-bottom");
}
hover_row = row;
}
check_scroll (y);
if(should_scroll && !scrolling) {
scrolling = true;
Timeout.add (SCROLL_DELAY, scroll);
}
return true;
}
public override void drag_leave (Gdk.DragContext context, uint time_) {
should_scroll = false;
}
void check_scroll (int y) {
if (vadjustment == null) {
return;
}
double vadjustment_min = vadjustment.value;
double vadjustment_max = vadjustment.page_size + vadjustment_min;
double show_min = double.max(0, y - SCROLL_DISTANCE);
double show_max = double.min(vadjustment.upper, y + SCROLL_DISTANCE);
if(vadjustment_min > show_min) {
should_scroll = true;
scroll_up = true;
} else if (vadjustment_max < show_max){
should_scroll = true;
scroll_up = false;
} else {
should_scroll = false;
}
}
bool scroll () {
if (should_scroll) {
if(scroll_up) {
vadjustment.value -= SCROLL_STEP_SIZE;
} else {
vadjustment.value += SCROLL_STEP_SIZE;
}
} else {
scrolling = false;
}
return should_scroll;
}
void row_drag_data_get (
Widget widget, Gdk.DragContext context, SelectionData selection_data,
uint info, uint time_
) {
uchar[] data = new uchar[(sizeof (Widget))];
((Widget[])data)[0] = widget;
selection_data.set (
Gdk.Atom.intern_static_string ("GTK_LIST_BOX_ROW"), 32, data
);
}
public override void drag_data_received (
Gdk.DragContext context, int x, int y,
SelectionData selection_data, uint info, uint time_
) {
Widget handle;
ListBoxRow row;
int index = 0;
if (hover_row != null) {
if (top) {
index = hover_row.get_index () - 1;
hover_row.get_style_context ().remove_class ("drag-hover-top");
} else {
index = hover_row.get_index ();
hover_row.get_style_context ().remove_class ("drag-hover-bottom");
}
handle = ((Widget[])selection_data.get_data ())[0];
row = (ListBoxRow) handle.get_ancestor (typeof (ListBoxRow));
if (row != hover_row) {
row.get_parent ().remove (row);
insert (row, index);
}
}
drag_row = null;
}
public ListBoxRow create_row (string text) {
ListBoxRow row;
EventBox handle;
Box box;
Label label;
Image image;
row = new ListBoxRow ();
box = new Box (Orientation.HORIZONTAL, 10);
box.margin_start = 10;
box.margin_end = 10;
row.add (box);
handle = new EventBox ();
image = new Image.from_icon_name ("view-list-symbolic", IconSize.MENU);
handle.add (image);
box.add (handle);
label = new Gtk.Label (text);
box.pack_end (label, true);
drag_source_set (
handle, Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.MOVE
);
handle.drag_begin.connect (row_drag_begin);
handle.drag_data_get.connect (row_drag_data_get);
return row;
}
}
const string css =
".drag-icon { " +
" background: white; " +
" border: 1px solid black; " +
"}" +
".drag-hover-top {" +
" background: linear-gradient(to bottom, rgba(0,0,0,0.65) 0%,rgba(0,0,0,0) 35%); " +
"}" +
".drag-hover-bottom {" +
" background: linear-gradient(to bottom, rgba(0,0,0,0) 65%,rgba(0,0,0,0.65) 100%); " +
"}";
int main (string[] args) {
Window window;
DragListBox list;
ScrolledWindow sw;
ListBoxRow row;
string text;
CssProvider provider;
Gtk.init (ref args);
provider = new CssProvider ();
try {
provider.load_from_data (css);
} catch (Error e) {
warning (e.message);
}
Gtk.StyleContext.add_provider_for_screen (
Gdk.Screen.get_default (), provider, STYLE_PROVIDER_PRIORITY_APPLICATION
);
window = new Window ();
window.set_default_size (-1, 300);
window.destroy.connect (Gtk.main_quit);
sw = new ScrolledWindow (null, null);
sw.hexpand = true;
sw.set_policy (PolicyType.NEVER, PolicyType.ALWAYS);
window.add (sw);
list = new DragListBox ();
list.set_selection_mode (SelectionMode.NONE);
sw.add (list);
for (int i = 0; i < 20; i++) {
text = "Row %d".printf (i);
row = list.create_row (text);
list.insert (row, -1);
}
list.vadjustment = sw.vadjustment;
window.show_all ();
Gtk.main ();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment