spk121 / guile-gi

Bindings for GObject Introspection and libgirepository for Guile
GNU General Public License v3.0
58 stars 7 forks source link

RFC: Support vfunc overrides and interface implementations in user object types #110

Open ghost opened 3 years ago

ghost commented 3 years ago

This is becoming more important with GTK4. Should look something like this (rough sample code):

(use-modules (gi) (gi repository))
(require "Gtk" "4.0")
(load-by-name "Gtk" "Widget")
(load-by-name "Gtk" "BoxLayout")
(load-by-name "Gtk" "Scrollable")
(load-by-name "Gtk" "SizeRequestMode")

(register-type "MyWidget" <GtkWidget> #f #f
  ; class initializer
  #:class-init (lambda (class)
    (widget-class:set-layout-manager-type class <GtkBoxLayout>)
    (widget-class:set-template-from-resource class "/my-widget.ui"))

  ; interface implementation
  (implement-interface <GtkScrollable>
    #:get-border (lambda (self) (list #f #f)))

  ; instance initializer
  #:init (lambda (self)
    (widget:init-template self))

  ; vfunc overrides
  #vfunc-get-request-mode (lambda (self)
    (symbol->size-request-mode 'constant-size))
  #vfunc-measure (lambda (self orientation for-size)
    (list 200 200 #f #f)))
ghost commented 3 years ago

It would also be nice to have a more idiomatic way to declare properties and signals, and to declare templates:

(use-modules (gi) (gi repository))
(load-all "Gtk" "4.0") ; syntax sugar for require and typelib->module

(define-type <MyWidget> <GtkWidget>

  ; long property definition, should be able to take some guile types
  ((my-int-prop <integer> #:nick "My Int Prop" #:description "Long Description"
                          #:default 1 #:min 0 #:max 10)

   ; short property syntax, gtype also works
   (my-uint-prop G_TYPE_UINT "My Uint Prop" "Long Description" 10 0 100)

   ; using #:getter and/or #setter won't allocate internal storage
   (my-object-prop <GtkAdjustment> #:construct? #t
     #:getter (lambda (self) (range:get-adjustment (template-child-ref self 'my-range)))
     #:setter (lambda (self adj) (range:set-adjustment (template-child-ref self 'my-range) adj))))

  ; short signal syntax, return type defaults to none
  ((my-detailed-signal #:flags (detailed))
   ; parameters can take guile types
   (my-param-signal <string> <int> <GtkWidget> #:return-type <int> #:accumulator true-handled))

  ; setup template in class constructor
  (template-resource "/my-widget.ui")
  ; allocates storage for a template child and automatically calls bind_template_child_full
  ; the type is necessary so we can instantiate it before loading the template
  ; the object can later be accessed with template-child-ref
  (template-child my-range <GtkRange>)
  ; automatically calls bind_template_callback_full
  (template-callback range-changed (self range) (display (range:get-value range))))
LordYuuma commented 3 years ago

Note, that stuff like implement-interface won't work as part of register-type, since it's just a procedure. That said, there should be some way to make interface definitions from scheme through register-type.

As for the template stuff, I don't think that what you want can easily be achieved (Guile-GI would have to make use of GtkBuilder, which might not be the same GtkBuilder you want). I'd prefer if you directly worked with the GtkBuilder API there, perhaps even ditching the XML – sxml is a better representation anyway. See the builder-manual example for how to create a builder on your own and use it.

ghost commented 3 years ago

Right, so my thoughts are this should be done with a new macro called define-gtype or something like that to add some syntax sugar, similar to goops define-class.

My idea with supporting this would be to avoid interacting with Gtk from the C code. It could be done similar to way that the python and javascript bindings do it by injecting some code at runtime using "overrides," after the gir typelib is loaded: Overrides/Gtk.py, overrides/Gtk.js. Both of those avoid the linking problem. It could probably also be done only with macros, if it was desired to avoid changing the C code at all.

I agree that SXML should be preferred, but templates are still going to be wanted for Gtk4, some things (such as widget children and the layout system) are really not easy to do through GtkBuilder. For that, the SXML can be converted (maybe even at compile time) and then passed to gtk_widget_class_set_template.

LordYuuma commented 3 years ago

Sure, sugar is always nice to have, but atm we're lacking the groundwork, that this sugar would need.

As for interfacing with Gtk, GLib, etc. it doesn't much matter whether we do it in C or Scheme (though obviously we would prefer Scheme), such overrides already run counter to previous design choices, e.g. the choice to allow limited loading of typelibs.

I'm not quite sure what we would need to/can do to support gtk_widget_class_set_template. To be honest, I think you should be able to do everything you can do with templates through the Builder API directly, but alas, some will still want to use Glade for UI design. If we manage to allocate arbitrarily-sized GObjects (with arbitarily sized privates), we can call stuff like gtk_widget_class_bind_template_child_full, so perhaps there ought to be a way of specifying the layout of the struct we generate. That said, given that all of this is implemented mostly via C macros, I don't have high hopes for it.

ghost commented 3 years ago

To handle this is something that needs to be done at class/instance construct time, so it would be possible to only do the override when a particular type definition of GtkWidget is loaded, and then ensure that the appropriate symbols are referenced only then, using the same typelib as the class. Features can further be gated by checking the loaded version of the library.

I would have to do a bit of hacking to see how the groundwork would look, I'll submit a draft PR if I get a chance to do that, then probably a comparison could be done to see how it compares to GtkBuilder.