Skip to content

Commit 0d46d3f

Browse files
sdroegebilelmoussaoui
authored andcommitted
examples: Clean up, modernize and simplify virtual methods example
1 parent ac50eb6 commit 0d46d3f

File tree

3 files changed

+141
-115
lines changed

3 files changed

+141
-115
lines changed

examples/virtual_methods/base_button/imp.rs

Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,33 @@
1-
use super::BaseButtonExt;
2-
use gtk::{
3-
gio,
4-
glib::{self, Error},
5-
prelude::*,
6-
subclass::prelude::*,
7-
};
8-
use std::{future::Future, pin::Pin};
9-
10-
pub type BaseButtonInstance = super::BaseButton;
11-
pub type PinnedFuture = Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>>;
12-
13-
/// GObject glue code for our BaseButtonClass which holds the function pointers to our virtual functions.
14-
#[repr(C)]
15-
pub struct BaseButtonClass {
16-
pub parent_class: gtk::ffi::GtkButtonClass,
17-
// If these functions are meant to be called from C, you need to make these functions
18-
// `unsafe extern "C" fn` & use FFI-safe types (usually raw pointers).
19-
pub sync_method: fn(&BaseButtonInstance, extra_text: Option<String>),
20-
pub async_method: fn(&BaseButtonInstance) -> PinnedFuture,
21-
}
22-
23-
unsafe impl ClassStruct for BaseButtonClass {
24-
type Type = BaseButton;
25-
}
1+
/// Implementation of `BaseButton`.
2+
use super::{BaseButtonExt, PinnedFuture};
3+
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
264

275
#[derive(Debug, Default)]
286
pub struct BaseButton;
297

30-
// Virtual method default implementation trampolines
31-
fn sync_method_default_trampoline(this: &BaseButtonInstance, extra_text: Option<String>) {
32-
this.imp().sync_method(this, extra_text)
33-
}
34-
35-
fn async_method_default_trampoline(this: &BaseButtonInstance) -> PinnedFuture {
36-
this.imp().async_method(this)
37-
}
38-
39-
pub(super) fn base_button_sync_method(this: &BaseButtonInstance, extra_text: Option<String>) {
40-
let klass = this.class();
41-
(klass.as_ref().sync_method)(this, extra_text)
42-
}
43-
44-
pub(super) fn base_button_async_method(this: &BaseButtonInstance) -> PinnedFuture {
45-
let klass = this.class();
46-
(klass.as_ref().async_method)(this)
47-
}
48-
49-
/// Default implementations of our sync_method and async_method.
508
impl BaseButton {
51-
fn sync_method(&self, obj: &super::BaseButton, extra_text: Option<String>) {
9+
/// Implementation for non-virtual methods.
10+
pub(super) fn non_virtual_method(&self) {
11+
let obj = self.obj();
12+
obj.set_label("Non-virtual method called");
13+
}
14+
15+
/// Default implementations virtual methods.
16+
fn sync_method_default(&self, extra_text: Option<&str>) {
17+
let obj = self.obj();
5218
if let Some(text) = extra_text {
5319
obj.set_label(&format!("BaseButton sync: {text}"));
5420
} else {
5521
obj.set_label("BaseButton sync");
5622
}
5723
}
5824

59-
fn async_method(&self, obj: &super::BaseButton) -> PinnedFuture {
60-
Box::pin(gio::GioFuture::new(
61-
obj,
62-
glib::clone!(@weak obj => move |_, _, send| {
63-
obj.set_label("BaseButton async");
64-
send.resolve(Ok(()));
65-
}),
66-
))
25+
fn async_method_default(&self) -> PinnedFuture<Result<(), glib::Error>> {
26+
let obj = self.obj();
27+
Box::pin(gio::GioFuture::new(&*obj, |obj, _, send| {
28+
obj.set_label("BaseButton async");
29+
send.resolve(Ok(()));
30+
}))
6731
}
6832
}
6933

@@ -72,20 +36,24 @@ impl ObjectSubclass for BaseButton {
7236
const NAME: &'static str = "ExampleBaseButton";
7337
type ParentType = gtk::Button;
7438
type Type = super::BaseButton;
75-
type Class = BaseButtonClass;
39+
type Class = super::Class;
7640

41+
/// Initialize the class struct with the default implementations of the virtual methods.
7742
fn class_init(klass: &mut Self::Class) {
78-
klass.sync_method = sync_method_default_trampoline;
79-
klass.async_method = async_method_default_trampoline;
43+
klass.sync_method = |obj, extra_text| {
44+
obj.imp().sync_method_default(extra_text);
45+
};
46+
klass.async_method = |obj| obj.imp().async_method_default();
8047
}
8148
}
8249

8350
impl ObjectImpl for BaseButton {
8451
fn constructed(&self) {
8552
self.parent_constructed();
86-
// For demo purposes, call the sync_method during construction to set the button label
87-
self.obj()
88-
.sync_method(Some(String::from("Sync extra text")));
53+
54+
// For demo purposes, call the `non_virtual_method()` during construction to set the button
55+
// label
56+
self.obj().non_virtual_method();
8957
}
9058
}
9159

Lines changed: 107 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1-
/// Public part of the BaseButton
1+
/// Public API of `BaseButton`.
22
mod imp;
33

4-
pub use self::imp::PinnedFuture;
54
use gtk::{
65
glib::{self, subclass::prelude::*},
76
prelude::*,
87
subclass::prelude::*,
98
};
9+
use std::{future::Future, pin::Pin};
10+
11+
/// Type alias for pinned boxed futures that output `T`.
12+
pub type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
1013

1114
glib::wrapper! {
15+
/// Public type for the `BaseButton` instances.
1216
pub struct BaseButton(ObjectSubclass<imp::BaseButton>)
1317
@extends gtk::Widget, gtk::Button;
1418
}
1519

1620
impl BaseButton {
21+
/// Creates a new instance of `BaseButton`.
1722
pub fn new() -> Self {
1823
glib::Object::new()
1924
}
@@ -25,84 +30,136 @@ impl Default for BaseButton {
2530
}
2631
}
2732

28-
/// Public trait that implements our functions for everything that derives from BaseButton
29-
pub trait BaseButtonExt {
30-
fn sync_method(&self, extra_text: Option<String>);
31-
fn async_method(&self) -> PinnedFuture;
32-
}
33+
/// Public trait that implements our functions for everything that derives from `BaseButton`.
34+
///
35+
/// These are public methods that can be called on any instance.
36+
pub trait BaseButtonExt: IsA<BaseButton> {
37+
/// Caller for a non-virtual method on `BaseButton`.
38+
///
39+
/// This directly calls the method inside the implementation module.
40+
fn non_virtual_method(&self) {
41+
self.upcast_ref::<BaseButton>().imp().non_virtual_method();
42+
}
3343

34-
/// We call into imp::BaseButton_$method_name for each function. These will retrieve the
35-
/// correct class (the base class for the BaseButton or the derived class for DerivedButton)
36-
/// and call the correct implementation of the function.
37-
impl<O: IsA<BaseButton>> BaseButtonExt for O {
38-
fn sync_method(&self, extra_text: Option<String>) {
39-
imp::base_button_sync_method(self.upcast_ref::<BaseButton>(), extra_text)
44+
/// Caller for a virtual method on `BaseButton`.
45+
///
46+
/// This retrieves this instance's class and calls the function pointer in it.
47+
fn sync_method(&self, extra_text: Option<&str>) {
48+
let obj = self.upcast_ref::<BaseButton>();
49+
(obj.class().as_ref().sync_method)(obj, extra_text);
4050
}
4151

42-
fn async_method(&self) -> PinnedFuture {
43-
imp::base_button_async_method(self.upcast_ref::<BaseButton>())
52+
/// Caller for an async virtual method on `BaseButton`.
53+
///
54+
/// This retrieves this instance's class and calls the function pointer in it.
55+
///
56+
/// Once async functions in traits are supported this should become one.
57+
fn async_method(&self) -> PinnedFuture<Result<(), glib::Error>> {
58+
let obj = self.upcast_ref::<BaseButton>();
59+
(obj.class().as_ref().async_method)(obj)
4460
}
4561
}
4662

47-
/// The BaseButtonImpl that each derived private struct has to implement. See derived_button/imp.rs for how
48-
/// to override functions.
49-
pub trait BaseButtonImpl: ButtonImpl + ObjectImpl + 'static {
50-
fn sync_method(&self, obj: &BaseButton, extra_text: Option<String>) {
51-
self.parent_sync_method(obj, extra_text)
63+
impl<O: IsA<BaseButton>> BaseButtonExt for O {}
64+
65+
/// The `BaseButtonImpl` trait that each derived implementation struct has to implement.
66+
///
67+
/// See `derived_button/imp.rs` for how to override virtual methods.
68+
pub trait BaseButtonImpl: ButtonImpl {
69+
/// Default implementation of a virtual method.
70+
///
71+
/// This always calls into the implementation of the parent class so that if the subclass does
72+
/// not explicitly implement it, the behaviour of its parent class will be preserved.
73+
fn sync_method(&self, extra_text: Option<&str>) {
74+
self.parent_sync_method(extra_text)
5275
}
5376

54-
fn async_method(&self, obj: &BaseButton) -> PinnedFuture {
55-
self.parent_async_method(obj)
77+
/// Default implementation of an async virtual method.
78+
///
79+
/// This always calls into the implementation of the parent class so that if the subclass does
80+
/// not explicitly implement it, the behaviour of its parent class will be preserved.
81+
fn async_method(&self) -> PinnedFuture<Result<(), glib::Error>> {
82+
self.parent_async_method()
5683
}
5784
}
5885

59-
pub trait BaseButtonImplExt: ObjectSubclass {
60-
fn parent_sync_method(&self, obj: &BaseButton, extra_text: Option<String>);
61-
fn parent_async_method(&self, obj: &BaseButton) -> PinnedFuture;
62-
}
63-
64-
impl<T: BaseButtonImpl> BaseButtonImplExt for T {
65-
fn parent_sync_method(&self, obj: &BaseButton, extra_text: Option<String>) {
86+
/// Public trait with "protected" methods for everything implementing `BaseButton`.
87+
///
88+
/// These are supposed to be called only from inside implementations of `BaseButton` subclasses.
89+
pub trait BaseButtonImplExt: BaseButtonImpl {
90+
/// Retrieves the parent class' implementation of the virtual method and calls it.
91+
fn parent_sync_method(&self, extra_text: Option<&str>) {
6692
unsafe {
6793
let data = Self::type_data();
68-
let parent_class = &*(data.as_ref().parent_class() as *mut imp::BaseButtonClass);
69-
(parent_class.sync_method)(obj, extra_text)
94+
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
95+
(parent_class.sync_method)(self.obj().unsafe_cast_ref(), extra_text)
7096
}
7197
}
7298

73-
fn parent_async_method(&self, obj: &BaseButton) -> PinnedFuture {
99+
/// Retrieves the parent class' implementation of the async virtual method and calls it.
100+
fn parent_async_method(&self) -> PinnedFuture<Result<(), glib::Error>> {
74101
unsafe {
75102
let data = Self::type_data();
76-
let parent_class = &*(data.as_ref().parent_class() as *mut imp::BaseButtonClass);
77-
(parent_class.async_method)(obj)
103+
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
104+
(parent_class.async_method)(self.obj().unsafe_cast_ref())
78105
}
79106
}
80107
}
81108

82-
/// Make the BaseButton subclassable
109+
impl<T: BaseButtonImpl> BaseButtonImplExt for T {}
110+
111+
/// This allows to implement subclasses of `BaseButton`.
83112
unsafe impl<T: BaseButtonImpl> IsSubclassable<T> for BaseButton {
113+
/// Called whenever the class of a `BaseButton` subclass is initialized, i.e. usually right
114+
/// before the first instance of it is created.
84115
fn class_init(class: &mut glib::Class<Self>) {
85116
Self::parent_class_init::<T>(class.upcast_ref_mut());
86117

118+
// Override the virtual method function pointers to call directly into the `BaseButtonImpl`
119+
// of the subclass.
120+
//
121+
// Note that this is only called for actual subclasses and not `BaseButton` itself:
122+
// `BaseButton` does not implement `BaseButtonImpl` and handles this inside
123+
// `ObjectSubclass::class_init()` for providing the default implementation of the virtual
124+
// methods.
87125
let klass = class.as_mut();
88-
klass.sync_method = sync_method_trampoline::<T>;
89-
klass.async_method = async_method_trampoline::<T>;
126+
klass.sync_method = |obj, extra_text| unsafe {
127+
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
128+
imp.sync_method(extra_text)
129+
};
130+
klass.async_method = |obj| unsafe {
131+
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
132+
imp.async_method()
133+
};
90134
}
91135
}
92136

93-
// Virtual method implementation trampolines
94-
fn sync_method_trampoline<T>(this: &BaseButton, extra_text: Option<String>)
95-
where
96-
T: ObjectSubclass + BaseButtonImpl,
97-
{
98-
let imp = this.dynamic_cast_ref::<T::Type>().unwrap().imp();
99-
imp.sync_method(this, extra_text)
137+
/// GObject class struct with the function pointers for the virtual methods.
138+
///
139+
/// This must be `#[repr(C)]`.
140+
#[repr(C)]
141+
pub struct Class {
142+
pub parent_class: gtk::ffi::GtkButtonClass,
143+
144+
// If these functions are meant to be called from C, you need to make these functions
145+
// `unsafe extern "C" fn` & use FFI-safe types (usually raw pointers).
146+
pub sync_method: fn(&BaseButton, extra_text: Option<&str>),
147+
pub async_method: fn(&BaseButton) -> PinnedFuture<Result<(), glib::Error>>,
148+
}
149+
150+
/// Make it possible to use this struct as class struct in an `ObjectSubclass` trait
151+
/// implementation.
152+
///
153+
/// This is `unsafe` to enforce that the struct is `#[repr(C)]`.
154+
unsafe impl ClassStruct for Class {
155+
type Type = imp::BaseButton;
100156
}
101157

102-
fn async_method_trampoline<T>(this: &BaseButton) -> PinnedFuture
103-
where
104-
T: ObjectSubclass + BaseButtonImpl,
105-
{
106-
let imp = this.dynamic_cast_ref::<T::Type>().unwrap().imp();
107-
imp.async_method(this)
158+
/// Deref directly to the parent class' class struct.
159+
impl std::ops::Deref for Class {
160+
type Target = glib::Class<<<Self as ClassStruct>::Type as ObjectSubclass>::ParentType>;
161+
162+
fn deref(&self) -> &Self::Target {
163+
unsafe { &*(&self.parent_class as *const _ as *const _) }
164+
}
108165
}

examples/virtual_methods/derived_button/imp.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@ impl ButtonImpl for DerivedButton {}
1717

1818
/// Implement the base trait and override the functions
1919
impl BaseButtonImpl for DerivedButton {
20-
fn sync_method(&self, obj: &BaseButton, extra_text: Option<String>) {
20+
fn sync_method(&self, extra_text: Option<&str>) {
21+
let obj = self.obj();
2122
if let Some(text) = extra_text {
2223
obj.set_label(&format!("DerivedButton sync {text}"));
2324
} else {
2425
obj.set_label("DerivedButton sync");
2526
}
2627
}
2728

28-
fn async_method(&self, obj: &BaseButton) -> PinnedFuture {
29-
let obj_cloned = obj.clone();
30-
Box::pin(gio::GioFuture::new(obj, move |_, _, send| {
31-
obj_cloned.set_label("DerivedButton async");
29+
fn async_method(&self) -> PinnedFuture<Result<(), glib::Error>> {
30+
let obj = self.obj();
31+
Box::pin(gio::GioFuture::new(&*obj, |obj, _, send| {
32+
obj.set_label("DerivedButton async");
3233
send.resolve(Ok(()));
3334
}))
3435
}

0 commit comments

Comments
 (0)