-
-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples: Add example for custom class structs and virtual methods
- Loading branch information
Showing
7 changed files
with
526 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; | ||
} | ||
} |
Oops, something went wrong.