diff --git a/examples/Cargo.toml b/examples/Cargo.toml index fe87cf32e1f0..6691e637cfb5 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -44,3 +44,7 @@ path = "gio_cancellable_future/main.rs" [[bin]] name = "object_subclass" path = "object_subclass/main.rs" + +[[bin]] +name = "virtual_methods" +path = "virtual_methods/main.rs" diff --git a/examples/README.md b/examples/README.md index 5f2b955a4ecd..b6a0896a3d04 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,6 +7,8 @@ Consists of various examples of how to use the `gtk-rs-core` libraries. Note tha - [GIO Futures Await](./gio_futures_await/) - [GIO Task](./gio_task/) - [GIO Resources](./resources) +- [Object Subclassing](./object_subclass) +- [Interfaces and Virtual Methods](./virtual_methods) ## License diff --git a/examples/virtual_methods/cat.rs b/examples/virtual_methods/cat.rs new file mode 100644 index 000000000000..1c26639f0e69 --- /dev/null +++ b/examples/virtual_methods/cat.rs @@ -0,0 +1,102 @@ +use glib::prelude::*; +use glib::subclass::prelude::*; + +use super::pet::*; +use super::purrable::*; + +mod imp { + use std::cell::Cell; + + use super::*; + + #[derive(Default)] + pub struct Cat { + fed: Cell, + pub(super) purring: Cell, + } + + #[glib::object_subclass] + impl ObjectSubclass for Cat { + const NAME: &'static str = "Cat"; + type Type = super::Cat; + type Interfaces = (Purrable,); + type ParentType = Pet; + } + + impl ObjectImpl for Cat {} + impl PurrableImpl for Cat { + fn is_purring(&self) -> bool { + if self.purring.get() { + println!("Cat::is_purring: *purr*"); + true + } else { + println!("Cat::is_purring: Chaining up to parent_is_purring"); + self.parent_is_purring() + } + } + } + impl PetImpl for Cat { + /// Override the parent behaviour of `pet` to indicate a successful pet + /// if we have been sufficiently fed + fn pet(&self) -> bool { + if self.fed.get() { + println!("Cat::pet: *purr*"); + self.purring.set(true); + true + } else { + println!("Cat::pet: *mrrp*"); + false + } + } + + fn feed(&self) { + println!("Cat::feed: *mreeeow*"); + self.parent_feed(); + + // Remember that we have been fed + self.fed.set(true); + } + } + + impl Cat { + pub(super) fn meow(&self) { + // We can't be meowing and purring at the same time + self.purring.set(false); + println!("Cat::meow: *meow* *meow*"); + } + } +} + +glib::wrapper! { + /// The `Cat` class ties the interface and the superclass together + pub struct Cat(ObjectSubclass) + @extends Pet, + @implements Purrable; +} + +/// Public methods of `Cat` classes +pub trait CatExt: IsA { + /// A regular public method. + /// + /// Resets the purring state. + fn meow(&self) { + self.upcast_ref::().imp().meow(); + } +} + +impl> CatExt for T {} + +impl Default for Cat { + fn default() -> Self { + glib::Object::new() + } +} + +/// Cat is also subclassable, but does not have any vfuncs. +/// +/// By convention we still create an empty `CatImpl` trait, this allows us to add +/// 'protected' cat methods only available to be called by other Cats later. +pub trait CatImpl: PetImpl {} + +/// To make this class subclassable we need to implement IsSubclassable +unsafe impl IsSubclassable for Cat {} diff --git a/examples/virtual_methods/main.rs b/examples/virtual_methods/main.rs new file mode 100644 index 000000000000..56c6ce50c3fb --- /dev/null +++ b/examples/virtual_methods/main.rs @@ -0,0 +1,41 @@ +mod cat; +mod pet; +mod purrable; +mod tabby_cat; + +use cat::*; +use glib::object::Cast; +use pet::*; +use purrable::*; +use tabby_cat::*; + +/// This example provides a class [`Pet`] with the virtual methods [`PetImpl::pet`] and +/// [`PetImpl::feed`], an interface [`Purrable`] with the method [`PurrableImpl::is_purring`], +/// an implementation class [`Cat`] to tie them all together, and a trivial subclass [`TabbyCat`] +/// to show that chaining up vfuncs works as expected. +fn main() { + println!("\n=== Cat implementation ==="); + // Instantiate the subclass `Cat`` + let cat = Cat::default(); + cat.meow(); + dbg!(cat.pet()); + dbg!(cat.is_purring()); + + cat.feed(); + dbg!(cat.pet()); + dbg!(cat.is_purring()); + cat.meow(); + dbg!(cat.is_purring()); + + println!("\n=== Tabby Cat implementation ==="); + // Now instantiate the subclass `TabbyCat` and ensure that the parent class + // functionality still works as expected and all methods chain up correctly. + let tabby_cat = TabbyCat::default(); + tabby_cat.feed(); + tabby_cat.pet(); + + // Even if we cast this as `Purrable` this calls the implementation in `TabbyCat` + let purrable = tabby_cat.upcast_ref::(); + dbg!(purrable.is_purring()); + tabby_cat.meow(); +} diff --git a/examples/virtual_methods/pet.rs b/examples/virtual_methods/pet.rs new file mode 100644 index 000000000000..26409b382de9 --- /dev/null +++ b/examples/virtual_methods/pet.rs @@ -0,0 +1,198 @@ +use glib::prelude::*; +use glib::subclass::prelude::*; + +/// The ffi module includes the exported C API of the object. +#[allow(dead_code)] +pub mod ffi { + /// The instance pointer is used for references to the object. + #[repr(C)] + pub struct Instance(std::ffi::c_void); + + /// Custom class struct for the [`Pet`](super::Pet) class. + /// + /// The class struct is used to implement the vtable for method dispatch. The first field + /// *must* be a pointer to the parent type. In our case, this is + /// [`GObjectClass`](glib::gobject_ffi::GObjectClass) + #[derive(Copy, Clone, Debug)] + #[repr(C)] + pub struct Class { + /// The first field in a class struct must always be the parent class struct + parent_class: glib::gobject_ffi::GObjectClass, + + /// Virtual method for the [`PetImpl::pet`](super::PetImpl::pet) trait method + pub(super) pet: fn(&super::Pet) -> bool, + + /// Virtual method for the [`PetImpl::feed`](super::PetImpl::feed) trait method + pub(super) feed: fn(&super::Pet), + } + + /// Every class struct is required to implement the `ClassStruct` trait + /// + /// Safety: This impl is unsafe because it requires the struct to be `repr(C)` and + /// the first field must be the parent class. + unsafe impl glib::subclass::types::ClassStruct for Class { + type Type = super::imp::Pet; + } + + /// Implement Deref to the parent class for convenience. + impl std::ops::Deref for Class { + type Target = glib::gobject_ffi::GObjectClass; + + fn deref(&self) -> &Self::Target { + &self.parent_class + } + } +} + +/// Private (implementation) module of the class. This is not exported. +mod imp { + use glib::subclass::prelude::*; + + /// The Pet implementation struct + #[derive(Default)] + pub struct Pet {} + + #[glib::object_subclass] + impl ObjectSubclass for Pet { + /// This name is exported to the gobject type system and must be unique between all loaded + /// shared libraries. + /// + /// Usually this is achieved by adding a short prefix to all names coming from a + /// particular app / library. + const NAME: &'static str = "Pet"; + + /// The [`Pet`](super::Pet) class is abstract and instances to it can't be created. + /// + /// If an instance of this class were instantiated it would panic. + const ABSTRACT: bool = true; + + type Type = super::Pet; + + /// Override the class struct with the custom [class](super::ffi::Class) + type Class = super::ffi::Class; + + /// Initialize the [class struct](super::ffi::Class) with the default implementations of the + /// virtual methods. + fn class_init(klass: &mut Self::Class) { + klass.pet = |obj| obj.imp().pet_default(); + klass.feed = |obj| obj.imp().feed_default(); + } + } + + impl ObjectImpl for Pet {} + + impl Pet { + /// Default implementation of [`PetImpl::pet`](super::PetImpl::pet) + fn pet_default(&self) -> bool { + // The default behaviour is unsuccessful pets + println!("Pet::pet_default"); + false + } + + /// Default implementation of [`PetImpl::feed`](super::PetImpl::feed) + fn feed_default(&self) { + println!("Pet::feed_default"); + } + } +} + +glib::wrapper! { + /// The `Pet` class acts as a base class for pets and provides two virtual methods. + pub struct Pet(ObjectSubclass); +} + +/// The `PetExt` trait contains public methods of all [`Pet`] objects +/// +/// These methods need to call the appropriate vfunc from the vtable. +pub trait PetExt: IsA { + /// Calls the [`PetImpl::pet`] vfunc + fn pet(&self) -> bool { + let this = self.upcast_ref(); + let class = this.class(); + + (class.as_ref().pet)(this) + } + + /// Calls the [`PetImpl::feed`] vfunc + fn feed(&self) { + let this = self.upcast_ref(); + let class = this.class(); + + (class.as_ref().feed)(this) + } +} + +/// Implement PetExt for all [`Pet`] subclasses (and `Pet` itself) +impl> PetExt for T {} + +/// The `PetImpl` trait contains overridable virtual function definitions for [`Pet`] objects. +pub trait PetImpl: ObjectImpl { + /// Default implementation of a virtual method. + /// + /// This always calls into the implementation of the parent class so that if + /// the subclass does not explicitly implement it, the behaviour of its + /// parent class will be preserved. + fn pet(&self) -> bool { + self.parent_pet() + } + + /// Default implementation of a virtual method. + /// + /// This always calls into the implementation of the parent class so that if + /// the subclass does not explicitly implement it, the behaviour of its + /// parent class will be preserved. + fn feed(&self) { + self.parent_feed(); + } +} + +/// The `PetImplExt` trait contains non-overridable methods for subclasses to use. +/// +/// These are supposed to be called only from inside implementations of `Pet` subclasses. +pub trait PetImplExt: PetImpl { + /// Chains up to the parent implementation of [`PetImpl::pet`] + fn parent_pet(&self) -> bool { + let data = Self::type_data(); + let parent_class = unsafe { &*(data.as_ref().parent_class() as *const ffi::Class) }; + let pet = parent_class.pet; + + pet(unsafe { self.obj().unsafe_cast_ref() }) + } + + /// Chains up to the parent implementation of [`PetImpl::feed`] + fn parent_feed(&self) { + let data = Self::type_data(); + let parent_class = unsafe { &*(data.as_ref().parent_class() as *const ffi::Class) }; + let feed = parent_class.feed; + + feed(unsafe { self.obj().unsafe_cast_ref() }); + } +} + +/// The `PetImplExt` trait is implemented for all subclasses that have [`Pet`] in the class hierarchy +impl PetImplExt for T {} + +/// To make this class subclassable we need to implement IsSubclassable +unsafe impl IsSubclassable for Pet { + /// Override the virtual method function pointers in subclasses to call directly into the + /// `PetImpl` of the subclass. + /// + /// Note that this is only called for actual subclasses and not `Pet` itself: `Pet` does + /// not implement `PetImpl` and handles this inside `ObjectSubclass::class_init()` for + /// providing the default implementation of the virtual methods. + fn class_init(class: &mut glib::Class) { + Self::parent_class_init::(class); + + let klass = class.as_mut(); + + klass.pet = |obj| { + let this = unsafe { obj.unsafe_cast_ref::<::Type>().imp() }; + PetImpl::pet(this) + }; + + klass.feed = |obj| { + let this = unsafe { obj.unsafe_cast_ref::<::Type>().imp() }; + PetImpl::feed(this); + }; + } +} diff --git a/examples/virtual_methods/purrable.rs b/examples/virtual_methods/purrable.rs new file mode 100644 index 000000000000..46e24dd15c19 --- /dev/null +++ b/examples/virtual_methods/purrable.rs @@ -0,0 +1,123 @@ +use glib::prelude::*; +use glib::subclass::prelude::*; + +/// The ffi module includes the exported C API of the interface. +#[allow(dead_code)] +pub mod ffi { + /// The instance pointer is used for references to the interface. + #[repr(C)] + pub struct Instance(std::ffi::c_void); + + /// Custom class struct for the [`super::Purrable`] interface. + /// + /// The class struct is used to implement the vtable for method dispatch. The first field *must* + /// be a pointer to the parent type. For all interfaces this is required to be + /// [`glib::gobject_ffi::GTypeInterface`] + #[derive(Copy, Clone)] + #[repr(C)] + pub struct Interface { + /// The first field in a class struct must always be the parent class struct + parent_type: glib::gobject_ffi::GTypeInterface, + + /// Virtual method for the [`super::PurrableImpl::is_purring`] method + pub(super) is_purring: fn(&super::Purrable) -> bool, + } + + /// Safety: This impl is unsafe because it requires the struct to be `repr(C)` and + /// the first field must be [`glib::gobject_ffi::GTypeInterface`]. + unsafe impl glib::subclass::types::InterfaceStruct for Interface { + type Type = super::iface::Purrable; + } +} + +/// The private module includes the interface default methods and ties together class and public type +mod iface { + use glib::subclass::prelude::*; + + pub enum Purrable {} + + #[glib::object_interface] + impl ObjectInterface for Purrable { + const NAME: &'static str = "Purrable"; + + type Interface = super::ffi::Interface; + + /// Initialize the class struct with the default implementations of the + /// virtual methods. + fn interface_init(klass: &mut Self::Interface) { + klass.is_purring = |_| Self::is_purring_default(); + } + } + + impl Purrable { + /// Default implementation of [`PurrableImpl::is_purring`](super::PurrableImpl::is_purring) + fn is_purring_default() -> bool { + println!("Purrable::is_purring_default: Not purring"); + false + } + } +} + +glib::wrapper! { + /// The `Purrable` interface provides a virtual method `is_purring` + pub struct Purrable(ObjectInterface); +} + +/// The `PurrableExt` trait contains public methods of all [`Purrable`] objects +/// +/// These methods need to call the appropriate vfunc from the vtable. +pub trait PurrableExt: IsA { + /// Return the current purring status + fn is_purring(&self) -> bool { + let this = self.upcast_ref::(); + let class = this.interface::().unwrap(); + (class.as_ref().is_purring)(this) + } +} + +impl> PurrableExt for T {} + +/// The `PurrableImpl` trait contains virtual function definitions for [`Purrable`] objects. +pub trait PurrableImpl: ObjectImpl { + /// Return the current purring status. + /// + /// The default implementation chains up to the parent implementation, + /// if we didn't do this *all* subclasses of `Purrable` classes would + /// need to implement these methods manually. + fn is_purring(&self) -> bool { + self.parent_is_purring() + } +} + +/// The `PurrableImplExt` trait contains non-overridable methods for subclasses to use. +/// +/// These are supposed to be called only from inside implementations of `Pet` subclasses. +pub trait PurrableImplExt: PurrableImpl { + /// Chains up to the parent implementation of [`PurrableExt::is_purring`] + fn parent_is_purring(&self) -> bool { + let data = Self::type_data(); + let parent_class = + unsafe { &*(data.as_ref().parent_interface::() as *const ffi::Interface) }; + let is_purring = parent_class.is_purring; + + is_purring(unsafe { self.obj().unsafe_cast_ref() }) + } +} + +/// The `PurrableImplExt` trait is implemented for all classes that implement [`Purrable`]. +impl PurrableImplExt for T {} + +/// To make this interface implementable we need to implement [`IsImplementable`] +unsafe impl IsImplementable for Purrable { + fn interface_init(iface: &mut glib::Interface) { + let klass = iface.as_mut(); + + // Set the virtual method + klass.is_purring = |obj| { + // (Down-)cast the object as the concrete type + let this = unsafe { obj.unsafe_cast_ref::<::Type>().imp() }; + // Call the trait method + PurrableImpl::is_purring(this) + }; + } +} diff --git a/examples/virtual_methods/tabby_cat.rs b/examples/virtual_methods/tabby_cat.rs new file mode 100644 index 000000000000..d6f44848215d --- /dev/null +++ b/examples/virtual_methods/tabby_cat.rs @@ -0,0 +1,56 @@ +use glib::subclass::prelude::*; + +use super::cat::{Cat, CatImpl}; +use super::pet::{Pet, PetImpl}; +use super::purrable::{Purrable, PurrableImpl, PurrableImplExt}; + +mod imp { + use crate::PetImplExt; + + use super::*; + + #[derive(Default)] + pub struct TabbyCat {} + + #[glib::object_subclass] + impl ObjectSubclass for TabbyCat { + const NAME: &'static str = "TabbyCat"; + type Type = super::TabbyCat; + type ParentType = Cat; + + /// We override a method of [`PurrableImpl`], so we must re-declare + /// that we conform to the interface. Otherwise our implementation + /// methods do not end up in the vtable. + type Interfaces = (Purrable,); + } + + impl ObjectImpl for TabbyCat {} + impl PetImpl for TabbyCat { + fn feed(&self) { + println!("TabbyCat::feed"); + self.parent_feed() + } + } + impl CatImpl for TabbyCat {} + impl PurrableImpl for TabbyCat { + fn is_purring(&self) -> bool { + println!("TabbyCat::is_purring"); + self.parent_is_purring() + } + } + impl TabbyCat {} +} + +glib::wrapper! { + pub struct TabbyCat(ObjectSubclass) + @extends Cat, Pet, + @implements Purrable; +} + +impl TabbyCat {} + +impl Default for TabbyCat { + fn default() -> Self { + glib::Object::new() + } +}