Created
April 29, 2017 10:10
-
-
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
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
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