Search code examples
gtkgtk3vala

How can I draw a rectangle over a widget without stealing its events?


I'm trying to implement a custom docking solution using a Gtk.DrawingArea placed upon a Gtk.Overlay, but when I attempt to show the Gtk.DrawingArea during a widget's drag_motion event it triggers the drag_leave event. The problem here is that I'm using the drag_leave event to hide the overlay, which means that I'm just toggling between visibility states as the cursor is moving. How can I prevent the overlay from triggering the bottom widget's drag_leave event after it has been made visible?

Also, is there a more efficient way to approach docking? Should I be using Gtk.Window instead of Gtk.DrawingArea? What is the best way to draw a simple rectangle over a region and have it move when an item is dragged?

  • I've tried using a Gtk.EventBox instead of a Gtk.DrawingArea
  • I've tried using a Gtk.Window with Gtk.WindowType.POPUP instead of a Gtk.Overlay
  • I've tried using GDL but I would prefer to create my own widgets using vala
  • I've tried calling gtk_overlay_set_overlay_pass_through
  • I've tried calling gdk_window_set_pass_through
using Gtk;
using Gdk;

public const Gtk.TargetEntry[] targets = {
    { "INTEGER", 0, 0 },
};

void main (string[] args) {
    Gtk.init (ref args);

    var window = new Gtk.Window ();
    window.set_default_size (400, 400);
    window.window_position = Gtk.WindowPosition.CENTER;

    var container = new Gtk.Overlay ();

    var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);

    container.add (box);
    var box_overlay = new Gtk.DrawingArea ();
    box_overlay.realize.connect (() => {
        box_overlay.get_window ().set_pass_through (true);
        box_overlay.hide ();
    });

    container.add_overlay (box_overlay);
    container.set_overlay_pass_through (box_overlay, true);

    var dest_drag_widget = new Gtk.Button ();
    dest_drag_widget.vexpand = true;
    var src_drag_widget = new Gtk.Button ();
    src_drag_widget.vexpand = true;

    Gtk.drag_source_set (src_drag_widget, Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY);
    Gtk.drag_dest_set (dest_drag_widget, Gtk.DestDefaults.MOTION, targets, Gdk.DragAction.COPY);

    dest_drag_widget.drag_motion.connect (() => {
        if (!box_overlay.visible) {
            print ("show\n");
            box_overlay.show ();
        }
        return false;
    });
    dest_drag_widget.drag_leave.connect (() => {
        if (box_overlay.visible) {
            print ("hide\n");
            box_overlay.hide ();
        }
    });

    box.add (dest_drag_widget);
    box.add (src_drag_widget);

    window.add (container);
    window.show_all ();

    Gtk.main ();
}

  1. Run the above with valac --pkg gtk+-3.0
  2. Drag bottom button to top and move it around
  3. Notice "show" and "hide" being printed many times.

I expect "show" and "hide" to only be printed once.

Edit:

The overall effect I'm trying to achieve is similar to how the Atom editor positions its panes.


Solution

  • Okay so I came up with the following solution:

    1. Hide the overlay by default
    2. Make the overlay a drag destination alongside the original destination
    3. When user drags over original destination, show the overlay and tell it to handle the drag_motion by passing the original destination's rectangle
    4. Hide the overlay when cursor leaves rectangle

    This probably has some gotchas that I've yet to consider but at least it works.