Skip to content

Commit

Permalink
examples: Add example for custom class structs and virtual methods
Browse files Browse the repository at this point in the history
  • Loading branch information
felinira committed May 10, 2024
1 parent 11a3692 commit 26f4219
Show file tree
Hide file tree
Showing 7 changed files with 526 additions and 0 deletions.
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
102 changes: 102 additions & 0 deletions examples/virtual_methods/cat.rs
Original file line number Diff line number Diff line change
@@ -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<bool>,
pub(super) purring: Cell<bool>,
}

#[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<imp::Cat>)
@extends Pet,
@implements Purrable;
}

/// Public methods of `Cat` classes
pub trait CatExt: IsA<Cat> {
/// A regular public method.
///
/// Resets the purring state.
fn meow(&self) {
self.upcast_ref::<Cat>().imp().meow();
}
}

impl<T: IsA<Cat>> 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<Obj: CatImpl + PetImpl> IsSubclassable<Obj> for Cat {}
41 changes: 41 additions & 0 deletions examples/virtual_methods/main.rs
Original file line number Diff line number Diff line change
@@ -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::<Purrable>();
dbg!(purrable.is_purring());
tabby_cat.meow();
}
198 changes: 198 additions & 0 deletions examples/virtual_methods/pet.rs
Original file line number Diff line number Diff line change
@@ -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<imp::Pet>);
}

/// 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<Pet> {
/// 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<T: IsA<Pet>> 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<T: PetImpl> PetImplExt for T {}

/// To make this class subclassable we need to implement IsSubclassable
unsafe impl<Obj: PetImpl> IsSubclassable<Obj> 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>) {
Self::parent_class_init::<Obj>(class);

let klass = class.as_mut();

klass.pet = |obj| {
let this = unsafe { obj.unsafe_cast_ref::<<Obj as ObjectSubclass>::Type>().imp() };
PetImpl::pet(this)
};

klass.feed = |obj| {
let this = unsafe { obj.unsafe_cast_ref::<<Obj as ObjectSubclass>::Type>().imp() };
PetImpl::feed(this);
};
}
}
Loading

0 comments on commit 26f4219

Please sign in to comment.