Skip to content

Commit 1e1ba93

Browse files
authored
Merge pull request #465 from Cogitri/virtual-methods-example
examples: add virtual_methods
2 parents 9d4dcb3 + b02f2cd commit 1e1ba93

File tree

7 files changed

+328
-0
lines changed

7 files changed

+328
-0
lines changed

examples/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,7 @@ path = "text_viewer/main.rs"
104104
name = "video_player"
105105
path = "video_player/main.rs"
106106

107+
[[bin]]
108+
name = "virtual_methods"
109+
path = "virtual_methods/main.rs"
107110

examples/virtual_methods/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Virtual methods
2+
3+
Example for how to create a base widget which exposes virtual methods that other widgets can override.
4+
In this case, a `BaseButton` class is defined which is a gtk::Button with two additional methods:
5+
6+
* sync_method`: Synchronous method that updates the button label to `BaseButton sync`, or `BaseButton sync {extra_text}`. This showcases how arguments
7+
can be passed to virtual methods. The `Option<String>` has to be boxed to be FFI-safe.
8+
* `async_method`: Asynchronous method that updates the button label to `BaseButton async`
9+
10+
`DerivedButton` overrides the two methods. During construction `sync_method` is called in
11+
`BaseButton` which will set the label to `BaseButton sync` for `BaseButton` and to
12+
`DerivedButton sync` for `DerivedButton`.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
// `extern "C"` & use FFI-safe types (usually raw pointers).
19+
pub sync_method: Option<unsafe fn(&BaseButtonInstance, extra_text: Option<String>)>,
20+
pub async_method: Option<unsafe fn(&BaseButtonInstance) -> PinnedFuture>,
21+
}
22+
23+
unsafe impl ClassStruct for BaseButtonClass {
24+
type Type = BaseButton;
25+
}
26+
27+
#[derive(Debug, Default)]
28+
pub struct BaseButton;
29+
30+
// Virtual method default implementation trampolines
31+
fn sync_method_default_trampoline(this: &BaseButtonInstance, extra_text: Option<String>) {
32+
BaseButton::from_instance(this).sync_method(this, extra_text)
33+
}
34+
35+
fn async_method_default_trampoline(this: &BaseButtonInstance) -> PinnedFuture {
36+
BaseButton::from_instance(this).async_method(this)
37+
}
38+
39+
pub(super) unsafe fn base_button_sync_method(
40+
this: &BaseButtonInstance,
41+
extra_text: Option<String>,
42+
) {
43+
let klass = &*(this.class() as *const _ as *const BaseButtonClass);
44+
(klass.sync_method.unwrap())(this, extra_text)
45+
}
46+
47+
pub(super) unsafe fn base_button_async_method(this: &BaseButtonInstance) -> PinnedFuture {
48+
let klass = &*(this.class() as *const _ as *const BaseButtonClass);
49+
klass.async_method.unwrap()(this)
50+
}
51+
52+
/// Default implementations of our sync_method and async_method.
53+
impl BaseButton {
54+
fn sync_method(&self, obj: &super::BaseButton, extra_text: Option<String>) {
55+
if let Some(text) = extra_text {
56+
obj.set_label(&format!("BaseButton sync: {}", text));
57+
} else {
58+
obj.set_label("BaseButton sync");
59+
}
60+
}
61+
62+
fn async_method(&self, obj: &super::BaseButton) -> PinnedFuture {
63+
Box::pin(gio::GioFuture::new(
64+
obj,
65+
glib::clone!(@weak obj => move |_, _, send| {
66+
obj.set_label("BaseButton async");
67+
send.resolve(Ok(()));
68+
}),
69+
))
70+
}
71+
}
72+
73+
#[glib::object_subclass]
74+
impl ObjectSubclass for BaseButton {
75+
const NAME: &'static str = "ExampleBaseButton";
76+
type ParentType = gtk::Button;
77+
type Type = super::BaseButton;
78+
type Class = BaseButtonClass;
79+
80+
fn class_init(klass: &mut Self::Class) {
81+
klass.sync_method = Some(sync_method_default_trampoline);
82+
klass.async_method = Some(async_method_default_trampoline);
83+
}
84+
}
85+
86+
impl ObjectImpl for BaseButton {
87+
fn constructed(&self, obj: &Self::Type) {
88+
// For demo purposes, call the sync_method during construction to set the button label
89+
obj.sync_method(Some(String::from("Sync extra text")));
90+
}
91+
}
92+
93+
impl WidgetImpl for BaseButton {}
94+
impl ButtonImpl for BaseButton {}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/// Public part of the BaseButton
2+
mod imp;
3+
4+
pub use self::imp::PinnedFuture;
5+
use gtk::{
6+
glib::{self, subclass::prelude::*},
7+
prelude::*,
8+
subclass::prelude::*,
9+
};
10+
11+
glib::wrapper! {
12+
pub struct BaseButton(ObjectSubclass<imp::BaseButton>)
13+
@extends gtk::Widget, gtk::Button;
14+
}
15+
16+
impl BaseButton {
17+
pub fn new() -> Self {
18+
glib::Object::new(&[]).expect("Failed to create BaseButton")
19+
}
20+
}
21+
22+
impl Default for BaseButton {
23+
fn default() -> Self {
24+
Self::new()
25+
}
26+
}
27+
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+
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+
unsafe { imp::base_button_sync_method(self.upcast_ref::<BaseButton>(), extra_text) }
40+
}
41+
42+
fn async_method(&self) -> PinnedFuture {
43+
unsafe { imp::base_button_async_method(self.upcast_ref::<BaseButton>()) }
44+
}
45+
}
46+
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)
52+
}
53+
54+
fn async_method(&self, obj: &BaseButton) -> PinnedFuture {
55+
self.parent_async_method(obj)
56+
}
57+
}
58+
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>) {
66+
unsafe {
67+
let data = Self::type_data();
68+
let parent_class = data.as_ref().parent_class() as *mut imp::BaseButtonClass;
69+
if let Some(ref f) = (*parent_class).sync_method {
70+
f(obj, extra_text)
71+
} else {
72+
unimplemented!()
73+
}
74+
}
75+
}
76+
77+
fn parent_async_method(&self, obj: &BaseButton) -> PinnedFuture {
78+
unsafe {
79+
let data = Self::type_data();
80+
let parent_class = data.as_ref().parent_class() as *mut imp::BaseButtonClass;
81+
if let Some(ref f) = (*parent_class).async_method {
82+
f(obj)
83+
} else {
84+
unimplemented!()
85+
}
86+
}
87+
}
88+
}
89+
90+
/// Make the BaseButton subclassable
91+
unsafe impl<T: BaseButtonImpl> IsSubclassable<T> for BaseButton {
92+
fn class_init(class: &mut glib::Class<Self>) {
93+
<gtk::Button as IsSubclassable<T>>::class_init(class.upcast_ref_mut());
94+
95+
let klass = class.as_mut();
96+
klass.sync_method = Some(sync_method_trampoline::<T>);
97+
klass.async_method = Some(async_method_trampoline::<T>);
98+
}
99+
100+
fn instance_init(instance: &mut glib::subclass::InitializingObject<T>) {
101+
<gtk::Button as IsSubclassable<T>>::instance_init(instance);
102+
}
103+
}
104+
105+
// Virtual method implementation trampolines
106+
unsafe fn sync_method_trampoline<T>(this: &BaseButton, extra_text: Option<String>)
107+
where
108+
T: ObjectSubclass + BaseButtonImpl,
109+
{
110+
let instance = &*(this as *const _ as *const T::Instance);
111+
let imp = instance.impl_();
112+
imp.sync_method(this, extra_text)
113+
}
114+
115+
unsafe fn async_method_trampoline<T>(this: &BaseButton) -> PinnedFuture
116+
where
117+
T: ObjectSubclass + BaseButtonImpl,
118+
{
119+
let instance = &*(this as *const _ as *const T::Instance);
120+
let imp = instance.impl_();
121+
imp.async_method(this)
122+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::base_button::*;
2+
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
3+
4+
#[derive(Debug, Default)]
5+
pub struct DerivedButton {}
6+
7+
#[glib::object_subclass]
8+
impl ObjectSubclass for DerivedButton {
9+
const NAME: &'static str = "ExampleDerivedButton";
10+
type ParentType = BaseButton;
11+
type Type = super::DerivedButton;
12+
}
13+
14+
impl ObjectImpl for DerivedButton {}
15+
impl WidgetImpl for DerivedButton {}
16+
impl ButtonImpl for DerivedButton {}
17+
18+
/// Implement the base trait and override the functions
19+
impl BaseButtonImpl for DerivedButton {
20+
fn sync_method(&self, obj: &BaseButton, extra_text: Option<String>) {
21+
if let Some(text) = extra_text {
22+
obj.set_label(&format!("DerivedButton sync {}", text));
23+
} else {
24+
obj.set_label("DerivedButton sync");
25+
}
26+
}
27+
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");
32+
send.resolve(Ok(()));
33+
}))
34+
}
35+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
mod imp;
2+
3+
use gtk::glib;
4+
5+
glib::wrapper! {
6+
pub struct DerivedButton(ObjectSubclass<imp::DerivedButton>)
7+
@extends gtk::Widget, gtk::Button, crate::base_button::BaseButton;
8+
}
9+
10+
impl DerivedButton {
11+
pub fn new() -> Self {
12+
glib::Object::new(&[]).expect("Failed to create DerivedButton")
13+
}
14+
}
15+
16+
impl Default for DerivedButton {
17+
fn default() -> Self {
18+
Self::new()
19+
}
20+
}

examples/virtual_methods/main.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
mod base_button;
2+
mod derived_button;
3+
4+
use crate::base_button::BaseButtonExt;
5+
use gtk::{
6+
glib::{self, clone},
7+
prelude::*,
8+
};
9+
10+
fn main() {
11+
let application = gtk::Application::new(
12+
Some("com.github.gtk-rs.examples.virtual_methods"),
13+
Default::default(),
14+
);
15+
16+
application.connect_activate(|app| {
17+
let win = gtk::ApplicationWindow::new(app);
18+
let boxed = gtk::Box::new(gtk::Orientation::Horizontal, 6);
19+
let base_button = base_button::BaseButton::new();
20+
let derived_button = derived_button::DerivedButton::new();
21+
22+
base_button.connect_clicked(|b| {
23+
let ctx = glib::MainContext::default();
24+
ctx.spawn_local(clone!(@weak b => async move {
25+
b.async_method().await.unwrap();
26+
}));
27+
});
28+
derived_button.connect_clicked(|b| {
29+
let ctx = glib::MainContext::default();
30+
ctx.spawn_local(clone!(@weak b => async move {
31+
b.async_method().await.unwrap();
32+
}));
33+
});
34+
35+
boxed.append(&base_button);
36+
boxed.append(&derived_button);
37+
win.set_child(Some(&boxed));
38+
win.show();
39+
});
40+
41+
application.run();
42+
}

0 commit comments

Comments
 (0)