Skip to content

Commit 551cb54

Browse files
committed
examples: Add example for custom class structs and virtual methods
1 parent 11a3692 commit 551cb54

File tree

7 files changed

+529
-0
lines changed

7 files changed

+529
-0
lines changed

examples/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ path = "gio_cancellable_future/main.rs"
4444
[[bin]]
4545
name = "object_subclass"
4646
path = "object_subclass/main.rs"
47+
48+
[[bin]]
49+
name = "virtual_methods"
50+
path = "virtual_methods/main.rs"

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Consists of various examples of how to use the `gtk-rs-core` libraries. Note tha
77
- [GIO Futures Await](./gio_futures_await/)
88
- [GIO Task](./gio_task/)
99
- [GIO Resources](./resources)
10+
- [Object Subclassing](./object_subclass)
11+
- [Interfaces and Virtual Methods](./virtual_methods)
1012

1113
## License
1214

examples/virtual_methods/cat.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use glib::prelude::*;
2+
use glib::subclass::prelude::*;
3+
4+
use super::pet::*;
5+
use super::purrable::*;
6+
7+
mod imp {
8+
use std::cell::Cell;
9+
10+
use super::*;
11+
12+
#[derive(Default)]
13+
pub struct Cat {
14+
fed: Cell<bool>,
15+
pub(super) purring: Cell<bool>,
16+
}
17+
18+
#[glib::object_subclass]
19+
impl ObjectSubclass for Cat {
20+
const NAME: &'static str = "Cat";
21+
type Type = super::Cat;
22+
type Interfaces = (Purrable,);
23+
type ParentType = Pet;
24+
}
25+
26+
impl ObjectImpl for Cat {}
27+
impl PurrableImpl for Cat {
28+
fn is_purring(&self) -> bool {
29+
if self.purring.get() {
30+
println!("Cat::is_purring: *purr*");
31+
true
32+
} else {
33+
println!("Cat::is_purring: Chaining up to parent_is_purring");
34+
self.parent_is_purring()
35+
}
36+
}
37+
}
38+
impl PetImpl for Cat {
39+
/// Override the parent behaviour of `pet` to indicate a successful pet
40+
/// if we have been sufficiently fed
41+
fn pet(&self) -> bool {
42+
if self.fed.get() {
43+
println!("Cat::pet: *purr*");
44+
self.purring.set(true);
45+
true
46+
} else {
47+
println!("Cat::pet: *mrrp*");
48+
false
49+
}
50+
}
51+
52+
fn feed(&self) {
53+
println!("Cat::feed: *mreeeow*");
54+
self.parent_feed();
55+
56+
// Remember that we have been fed
57+
self.fed.set(true);
58+
}
59+
}
60+
61+
impl Cat {
62+
pub(super) fn meow(&self) {
63+
// We can't be meowing and purring at the same time
64+
self.purring.set(false);
65+
println!("Cat::meow: *meow* *meow*");
66+
}
67+
}
68+
}
69+
70+
glib::wrapper! {
71+
/// The `Cat` class ties the interface and the superclass together
72+
pub struct Cat(ObjectSubclass<imp::Cat>)
73+
@extends Pet,
74+
@implements Purrable;
75+
}
76+
77+
/// Public methods of `Cat` classes
78+
pub trait CatExt: IsA<Cat> {
79+
/// A regular public method.
80+
///
81+
/// Resets the purring state.
82+
fn meow(&self) {
83+
self.upcast_ref::<Cat>().imp().meow();
84+
}
85+
}
86+
87+
impl<T: IsA<Cat>> CatExt for T {}
88+
89+
impl Default for Cat {
90+
fn default() -> Self {
91+
glib::Object::new()
92+
}
93+
}
94+
95+
/// Cat is also subclassable, but does not have any vfuncs.
96+
///
97+
/// By convention we still create an empty `CatImpl` trait, this allows us to add
98+
/// 'protected' cat methods only available to be called by other Cats later.
99+
pub trait CatImpl: PetImpl {}
100+
101+
/// To make this class subclassable we need to implement IsSubclassable
102+
unsafe impl<Obj: CatImpl + PetImpl> IsSubclassable<Obj> for Cat {}

examples/virtual_methods/main.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
mod cat;
2+
mod pet;
3+
mod purrable;
4+
mod tabby_cat;
5+
6+
use cat::*;
7+
use glib::object::Cast;
8+
use pet::*;
9+
use purrable::*;
10+
use tabby_cat::*;
11+
12+
/// This example provides a class [`Pet`] with the virtual methods [`PetImpl::pet`] and
13+
/// [`PetImpl::feed`], an interface [`Purrable`] with the method [`PurrableImpl::is_purring`],
14+
/// an implementation class [`Cat`] to tie them all together, and a trivial subclass [`TabbyCat`]
15+
/// to show that chaining up vfuncs works as expected.
16+
fn main() {
17+
println!("\n=== Cat implementation ===");
18+
// Instantiate the subclass `Cat``
19+
let cat = Cat::default();
20+
cat.meow();
21+
dbg!(cat.pet());
22+
dbg!(cat.is_purring());
23+
24+
cat.feed();
25+
dbg!(cat.pet());
26+
dbg!(cat.is_purring());
27+
cat.meow();
28+
dbg!(cat.is_purring());
29+
30+
println!("\n=== Tabby Cat implementation ===");
31+
// Now instantiate the subclass `TabbyCat` and ensure that the parent class
32+
// functionality still works as expected and all methods chain up correctly.
33+
let tabby_cat = TabbyCat::default();
34+
tabby_cat.feed();
35+
tabby_cat.pet();
36+
37+
// Even if we cast this as `Purrable` this calls the implementation in `TabbyCat`
38+
let purrable = tabby_cat.upcast_ref::<Purrable>();
39+
dbg!(purrable.is_purring());
40+
tabby_cat.meow();
41+
}

examples/virtual_methods/pet.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use glib::prelude::*;
2+
use glib::subclass::prelude::*;
3+
4+
/// The ffi module includes the exported C API of the object.
5+
#[allow(dead_code)]
6+
pub mod ffi {
7+
/// The instance pointer is used for references to the object.
8+
#[repr(C)]
9+
pub struct Instance(std::ffi::c_void);
10+
11+
/// Custom class struct for the [`Pet`](super::Pet) class.
12+
///
13+
/// The class struct is used to implement the vtable for method dispatch. The first field
14+
/// *must* be a pointer to the parent type. In our case, this is
15+
/// [`GObjectClass`](glib::gobject_ffi::GObjectClass)
16+
#[derive(Copy, Clone, Debug)]
17+
#[repr(C)]
18+
pub struct Class {
19+
/// The first field in a class struct must always be the parent class struct
20+
parent_class: glib::gobject_ffi::GObjectClass,
21+
22+
/// Virtual method for the [`PetImpl::pet`](super::PetImpl::pet) trait method
23+
pub(super) pet: fn(&super::Pet) -> bool,
24+
25+
/// Virtual method for the [`PetImpl::feed`](super::PetImpl::feed) trait method
26+
pub(super) feed: fn(&super::Pet),
27+
}
28+
29+
/// Every class struct is required to implement the `ClassStruct` trait
30+
///
31+
/// Safety: This impl is unsafe because it requires the struct to be `repr(C)` and
32+
/// the first field must be the parent class.
33+
unsafe impl glib::subclass::types::ClassStruct for Class {
34+
type Type = super::imp::Pet;
35+
}
36+
37+
/// Implement Deref to the parent class for convenience.
38+
impl std::ops::Deref for Class {
39+
type Target = glib::gobject_ffi::GObjectClass;
40+
41+
fn deref(&self) -> &Self::Target {
42+
&self.parent_class
43+
}
44+
}
45+
}
46+
47+
/// Private (implementation) module of the class. This is not exported.
48+
mod imp {
49+
use glib::subclass::prelude::*;
50+
51+
/// The Pet implementation struct
52+
#[derive(Default)]
53+
pub struct Pet {}
54+
55+
#[glib::object_subclass]
56+
impl ObjectSubclass for Pet {
57+
/// This name is exported to the gobject type system and must be unique between all loaded
58+
/// shared libraries.
59+
///
60+
/// Usually this is achieved by adding a short prefix to all names coming from a
61+
/// particular app / library.
62+
const NAME: &'static str = "Pet";
63+
64+
/// The [`Pet`](super::Pet) class is abstract and instances to it can't be created.
65+
///
66+
/// If an instance of this class were instantiated it would panic.
67+
const ABSTRACT: bool = true;
68+
69+
type Type = super::Pet;
70+
71+
/// Override the class struct with the custom [class](super::ffi::Class)
72+
type Class = super::ffi::Class;
73+
74+
/// Initialize the [class struct](super::ffi::Class) with the default implementations of the
75+
/// virtual methods.
76+
fn class_init(klass: &mut Self::Class) {
77+
klass.pet = |obj| obj.imp().pet_default();
78+
klass.feed = |obj| obj.imp().feed_default();
79+
}
80+
}
81+
82+
impl ObjectImpl for Pet {}
83+
84+
impl Pet {
85+
/// Default implementation of [`PetImpl::pet`](super::PetImpl::pet)
86+
fn pet_default(&self) -> bool {
87+
// The default behaviour is unsuccessful pets
88+
println!("Pet::pet_default");
89+
false
90+
}
91+
92+
/// Default implementation of [`PetImpl::feed`](super::PetImpl::feed)
93+
fn feed_default(&self) {
94+
println!("Pet::feed_default");
95+
}
96+
}
97+
}
98+
99+
glib::wrapper! {
100+
/// The `Pet` class acts as a base class for pets and provides two virtual methods.
101+
pub struct Pet(ObjectSubclass<imp::Pet>);
102+
}
103+
104+
/// The `PetExt` trait contains public methods of all [`Pet`] objects
105+
///
106+
/// These methods need to call the appropriate vfunc from the vtable.
107+
pub trait PetExt: IsA<Pet> {
108+
/// Calls the [`PetImpl::pet`] vfunc
109+
fn pet(&self) -> bool {
110+
let this = self.upcast_ref();
111+
let class = this.class();
112+
113+
(class.as_ref().pet)(this)
114+
}
115+
116+
/// Calls the [`PetImpl::feed`] vfunc
117+
fn feed(&self) {
118+
let this = self.upcast_ref();
119+
let class = this.class();
120+
121+
(class.as_ref().feed)(this)
122+
}
123+
}
124+
125+
/// Implement PetExt for all [`Pet`] subclasses (and `Pet` itself)
126+
impl<T: IsA<Pet>> PetExt for T {}
127+
128+
/// The `PetImpl` trait contains overridable virtual function definitions for [`Pet`] objects.
129+
pub trait PetImpl: ObjectImpl {
130+
/// Default implementation of a virtual method.
131+
///
132+
/// This always calls into the implementation of the parent class so that if
133+
/// the subclass does not explicitly implement it, the behaviour of its
134+
/// parent class will be preserved.
135+
fn pet(&self) -> bool {
136+
self.parent_pet()
137+
}
138+
139+
/// Default implementation of a virtual method.
140+
///
141+
/// This always calls into the implementation of the parent class so that if
142+
/// the subclass does not explicitly implement it, the behaviour of its
143+
/// parent class will be preserved.
144+
fn feed(&self) {
145+
self.parent_feed();
146+
}
147+
}
148+
149+
/// The `PetImplExt` trait contains non-overridable methods for subclasses to use.
150+
///
151+
/// These are supposed to be called only from inside implementations of `Pet` subclasses.
152+
pub trait PetImplExt: PetImpl {
153+
/// Chains up to the parent implementation of [`PetImpl::pet`]
154+
fn parent_pet(&self) -> bool {
155+
let data = Self::type_data();
156+
let parent_class = unsafe { &*(data.as_ref().parent_class() as *const ffi::Class) };
157+
let pet = parent_class.pet;
158+
159+
pet(unsafe { self.obj().unsafe_cast_ref() })
160+
}
161+
162+
/// Chains up to the parent implementation of [`PetImpl::feed`]
163+
fn parent_feed(&self) {
164+
let data = Self::type_data();
165+
let parent_class = unsafe { &*(data.as_ref().parent_class() as *const ffi::Class) };
166+
let feed = parent_class.feed;
167+
168+
feed(unsafe { self.obj().unsafe_cast_ref() });
169+
}
170+
}
171+
172+
/// The `PetImplExt` trait is implemented for all subclasses that have [`Pet`] in the class hierarchy
173+
impl<T: PetImpl> PetImplExt for T {}
174+
175+
/// To make this class subclassable we need to implement IsSubclassable
176+
unsafe impl<Obj: PetImpl> IsSubclassable<Obj> for Pet {
177+
/// Override the virtual method function pointers in subclasses to call directly into the
178+
/// `PetImpl` of the subclass.
179+
///
180+
/// Note that this is only called for actual subclasses and not `Pet` itself: `Pet` does
181+
/// not implement `PetImpl` and handles this inside `ObjectSubclass::class_init()` for
182+
/// providing the default implementation of the virtual methods.
183+
fn class_init(class: &mut glib::Class<Self>) {
184+
Self::parent_class_init::<Obj>(class);
185+
186+
let klass = class.as_mut();
187+
188+
klass.pet = |obj| {
189+
let this = unsafe { obj.unsafe_cast_ref::<<Obj as ObjectSubclass>::Type>().imp() };
190+
PetImpl::pet(this)
191+
};
192+
193+
klass.feed = |obj| {
194+
let this = unsafe { obj.unsafe_cast_ref::<<Obj as ObjectSubclass>::Type>().imp() };
195+
PetImpl::feed(this);
196+
};
197+
}
198+
}

0 commit comments

Comments
 (0)