Search code examples
gtkoverridingdrawingvirtual-functionspygobject

Pygobject / Gtk draw on top of widgets


Is it possible in pygobject (or Gtk) to draw on top of the children widgets of a Gtk.Container widget (for example a VBox)?

I know I can connect to the 'draw' signal of any widget for custom drawing. But the callback is called before the normal widget drawing.

So any "custom" drawing is left behind the children of the container, unless the callback function connected to the draw signal return True, in which case the signal is not propagated and the container does not draw its children; but that is not what I want.

I need to draw after the container has drawn its children.

I know that other than responding to the draw signal, I can override the do_draw method in a subclass, but then again the container does not draw its children. I would have to call the parent class draw method but I don't know how.

I tried to call super().do_draw and super().draw() but I got stack overflow error, meaning that my do_draw function is calling itself.

Do you know some solution?


Solution

  • Method names like do_something in python GObjects are treated as implementations of a corresponding something virtual function in one of the base C classes/interfaces. Metaclass wizadry/hackery is used to implement this virtual function registration whenever you subclass GObject.Object. I believe you want to know how to chain up to a parent's virtual function implementation in PyGObject.

    My guess is that when you call super().draw(), you're just calling a wrapper draw() function that will then forward the call to the child class's virtual function implementation.

    When you access super().do_draw, you are retrieving a VFuncInfo. This is a callable wrapper object that will invoke the appropriate virtual function for you. It uses the owner argument from __get__() (descriptor protocol) to determine which child class implementation to use when you __call__(). As such, super().do_draw() will also again refer to your subclass's implementation. You can manually specify the class whose vfunc implementation you actually want by calling VFuncInfo.invoke() directly, e.g.

    super().do_draw.invoke(Gtk.VBox, self, *args, **kwargs)
    

    There's nothing stopping you from using a class you don't inherit from, though bad things probably happen.

    Or you can access do_draw via the parent class so the owner argument in the descriptor protocol is assigned as desired and you can just __call__():

    Gtk.VBox.do_draw(self, *args, **kwargs)
    

    Demo:

    import cairo
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    
    class MyVBox(Gtk.VBox):
        def do_draw(self, cr: cairo.Context) -> None:
            """Overlay a red rectangle in the top-left corner."""
            Gtk.VBox.do_draw(self, cr)
            # If you don't want to hard-code the parent class:
            #  __class__.mro()[1].do_draw(self, cr)
            cr.rectangle(10, 10, 64, 64)
            cr.set_source_rgba(1., 0., 0.)
            cr.fill()
    
    window = Gtk.Window()
    vbox = MyVBox()
    window.add(vbox)
    
    button = Gtk.Button(label="Button")
    label = Gtk.Label(label="^^^ Click me ^^^")
    vbox.add(button)
    vbox.add(label)
    
    window.show_all()
    
    button.connect('clicked', lambda *_: print("Button clicked"))
    window.connect('delete-event', Gtk.main_quit)
    Gtk.main()
    

    Whether this is the best way to draw on top of widgets, I don't know.