rhx / SwiftGtk

A Swift wrapper around gtk-3.x and gtk-4.x that is largely auto-generated from gobject-introspection
https://rhx.github.io/SwiftGtk/
BSD 2-Clause "Simplified" License
317 stars 26 forks source link

Cannot add widgets to containers, etc, due to type problems #36

Closed Kiwijane3 closed 3 years ago

Kiwijane3 commented 3 years ago

In the current version of the wrapper, it isn't possible to use variables defined as WidgetProtocol, or similar protocol types, because of the typing produced by gir2swift. A variety of functions do not work with Protocols, because they are defined as generics, and protocols cannot fulfill generic constraints, and they cannot be directly set as variables, because variables are defined as Ref. This issue could likely be resolved by redefining functions and variables to simply use the appropriate protocols directly, unless generics are specifically needed. The issue can be worked around instantiating a Ref using the appropriate pointer property of the variable.

rhx commented 3 years ago

Generally, this should work, as long as the type hierarchy is recorded correctly in the .gir files. For example, see the following snippet from SwiftHelloGtk:

    let label = Label(str: "Hello, SwiftGtk")
    window.add(widget: label)

This works, because a Label is also a Widget. As long as a type implements a given interface (e.g. a Label class that's a subclass of Widget), this should work. Can you give a specific example where this didn't work for you?

Kiwijane3 commented 3 years ago

The issue specifically occurs when using a variable declared as conforming to a protocol; Label works because it's declared as a concrete type. It it was instead explicitly declared as being of type LabelProtocol, the error would occur. Being able to use these protocols in types declarations is useful for instances where reference counted and non-reference counted types might be used; For instance, I encountered this issue in a MVC framework I'm developing, where I tried to use WidgetProtocol as the type of a controller's root widget.

rhx commented 3 years ago

Yes, that's also possible: the most efficient way is to use a generic. This way the compiler will know what the concrete type is and doesn't incur the extra overhead of using a Protocol witness table. If you really want to full type erasure (with the overhead this brings), you can do still do this, e.g.:

    let someLabel = Label(str: "Hello, SwiftGtk")
    // ...
    let anyWidget: WidgetProtocol = someLabel
    window.add(widget: WidgetRef(anyWidget.widget_ptr))

Wrapping the pointer from anyWidget in a WidgetRef is necessary, because in Swift, Protocols don't conform to themselves. In practice, a WidgetRef is just a type-safe version of the underlying pointer, so this should not create any extra code (other than looking up and calling the getter for widget_ptr in the Protocol witness table). IMHO a better way that should give you almost the same benefits but get rid of all the witness table overhead is to use WidgetRef directly, e.g.:

    let someLabel = Label(str: "Hello, SwiftGtk")
    // ...
    let widgetRef: WidgetRef = WidgetRef(someLabel)
    window.add(widget: widgetRef)

The only thing you will lose with this approach is the ability to do run-time introspection, i.e. anyWidget is Label will return true, but widgetRef is Label will return false.

[Edit: fix typo]

rhx commented 3 years ago

Of course, the following code above

let widgetRef: WidgetRef = WidgetRef(someLabel)

was only written that way to make clear what the types are, so you can rewrite this as:

let widgetRef = WidgetRef(someLabel)
Kiwijane3 commented 3 years ago

Right, that makes sense from an optimisation perspective. I'll continue using the reference-counted versions in the controllers; It makes sense for a controller to have a reference, in my view.

rhx commented 3 years ago

Yes, if you want Swift to do the reference-counting for you, use Widget (not WidgetRef, where you would have to call ref() and unref() yourself). You can also combine this with weak if you don't want to own the widget, e.g.:

    weak var weakWidget: Widget? = someLabel