Skip to content

Commit

Permalink
examples: Clean up, modernize and simplify virtual methods example
Browse files Browse the repository at this point in the history
  • Loading branch information
sdroege committed Oct 15, 2023
1 parent 39262e1 commit fe9f2d3
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 115 deletions.
88 changes: 28 additions & 60 deletions examples/virtual_methods/base_button/imp.rs
Original file line number Diff line number Diff line change
@@ -1,69 +1,33 @@
use super::BaseButtonExt;
use gtk::{
gio,
glib::{self, Error},
prelude::*,
subclass::prelude::*,
};
use std::{future::Future, pin::Pin};

pub type BaseButtonInstance = super::BaseButton;
pub type PinnedFuture = Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>>;

/// GObject glue code for our BaseButtonClass which holds the function pointers to our virtual functions.
#[repr(C)]
pub struct BaseButtonClass {
pub parent_class: gtk::ffi::GtkButtonClass,
// If these functions are meant to be called from C, you need to make these functions
// `unsafe extern "C" fn` & use FFI-safe types (usually raw pointers).
pub sync_method: fn(&BaseButtonInstance, extra_text: Option<String>),
pub async_method: fn(&BaseButtonInstance) -> PinnedFuture,
}

unsafe impl ClassStruct for BaseButtonClass {
type Type = BaseButton;
}
/// Implementation of `BaseButton`.
use super::{BaseButtonExt, PinnedFuture};
use gtk::{gio, glib, prelude::*, subclass::prelude::*};

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

// Virtual method default implementation trampolines
fn sync_method_default_trampoline(this: &BaseButtonInstance, extra_text: Option<String>) {
this.imp().sync_method(this, extra_text)
}

fn async_method_default_trampoline(this: &BaseButtonInstance) -> PinnedFuture {
this.imp().async_method(this)
}

pub(super) fn base_button_sync_method(this: &BaseButtonInstance, extra_text: Option<String>) {
let klass = this.class();
(klass.as_ref().sync_method)(this, extra_text)
}

pub(super) fn base_button_async_method(this: &BaseButtonInstance) -> PinnedFuture {
let klass = this.class();
(klass.as_ref().async_method)(this)
}

/// Default implementations of our sync_method and async_method.
impl BaseButton {
fn sync_method(&self, obj: &super::BaseButton, extra_text: Option<String>) {
/// Implementation for non-virtual methods.
pub(super) fn non_virtual_method(&self) {
let obj = self.obj();
obj.set_label("Non-virtual method called");
}

/// Default implementations virtual methods.
fn sync_method_default(&self, extra_text: Option<&str>) {
let obj = self.obj();
if let Some(text) = extra_text {
obj.set_label(&format!("BaseButton sync: {text}"));
} else {
obj.set_label("BaseButton sync");
}
}

fn async_method(&self, obj: &super::BaseButton) -> PinnedFuture {
Box::pin(gio::GioFuture::new(
obj,
glib::clone!(@weak obj => move |_, _, send| {
obj.set_label("BaseButton async");
send.resolve(Ok(()));
}),
))
fn async_method_default(&self) -> PinnedFuture<Result<(), glib::Error>> {
let obj = self.obj();
Box::pin(gio::GioFuture::new(&*obj, |obj, _, send| {
obj.set_label("BaseButton async");
send.resolve(Ok(()));
}))
}
}

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

/// Initialize the class struct with the default implementations of the virtual methods.
fn class_init(klass: &mut Self::Class) {
klass.sync_method = sync_method_default_trampoline;
klass.async_method = async_method_default_trampoline;
klass.sync_method = |obj, extra_text| {
obj.imp().sync_method_default(extra_text);
};
klass.async_method = |obj| obj.imp().async_method_default();
}
}

impl ObjectImpl for BaseButton {
fn constructed(&self) {
self.parent_constructed();
// For demo purposes, call the sync_method during construction to set the button label
self.obj()
.sync_method(Some(String::from("Sync extra text")));

// For demo purposes, call the `non_virtual_method()` during construction to set the button
// label
self.obj().non_virtual_method();
}
}

Expand Down
157 changes: 107 additions & 50 deletions examples/virtual_methods/base_button/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
/// Public part of the BaseButton
/// Public API of `BaseButton`.
mod imp;

pub use self::imp::PinnedFuture;
use gtk::{
glib::{self, subclass::prelude::*},
prelude::*,
subclass::prelude::*,
};
use std::{future::Future, pin::Pin};

/// Type alias for pinned boxed futures that output `T`.
pub type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;

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

impl BaseButton {
/// Creates a new instance of `BaseButton`.
pub fn new() -> Self {
glib::Object::new()
}
Expand All @@ -25,84 +30,136 @@ impl Default for BaseButton {
}
}

/// Public trait that implements our functions for everything that derives from BaseButton
pub trait BaseButtonExt {
fn sync_method(&self, extra_text: Option<String>);
fn async_method(&self) -> PinnedFuture;
}
/// Public trait that implements our functions for everything that derives from `BaseButton`.
///
/// These are public methods that can be called on any instance.
pub trait BaseButtonExt: IsA<BaseButton> {
/// Caller for a non-virtual method on `BaseButton`.
///
/// This directly calls the method inside the implementation module.
fn non_virtual_method(&self) {
self.upcast_ref::<BaseButton>().imp().non_virtual_method();
}

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

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

/// The BaseButtonImpl that each derived private struct has to implement. See derived_button/imp.rs for how
/// to override functions.
pub trait BaseButtonImpl: ButtonImpl + ObjectImpl + 'static {
fn sync_method(&self, obj: &BaseButton, extra_text: Option<String>) {
self.parent_sync_method(obj, extra_text)
impl<O: IsA<BaseButton>> BaseButtonExt for O {}

/// The `BaseButtonImpl` trait that each derived implementation struct has to implement.
///
/// See `derived_button/imp.rs` for how to override virtual methods.
pub trait BaseButtonImpl: ButtonImpl {
/// 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 sync_method(&self, extra_text: Option<&str>) {
self.parent_sync_method(extra_text)
}

fn async_method(&self, obj: &BaseButton) -> PinnedFuture {
self.parent_async_method(obj)
/// Default implementation of an async 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 async_method(&self) -> PinnedFuture<Result<(), glib::Error>> {
self.parent_async_method()
}
}

pub trait BaseButtonImplExt: ObjectSubclass {
fn parent_sync_method(&self, obj: &BaseButton, extra_text: Option<String>);
fn parent_async_method(&self, obj: &BaseButton) -> PinnedFuture;
}

impl<T: BaseButtonImpl> BaseButtonImplExt for T {
fn parent_sync_method(&self, obj: &BaseButton, extra_text: Option<String>) {
/// Public trait with "protected" methods for everything implementing `BaseButton`.
///
/// These are supposed to be called only from inside implementations of `BaseButton` subclasses.
pub trait BaseButtonImplExt: BaseButtonImpl {
/// Retrieves the parent class' implementation of the virtual method and calls it.
fn parent_sync_method(&self, extra_text: Option<&str>) {
unsafe {
let data = Self::type_data();
let parent_class = &*(data.as_ref().parent_class() as *mut imp::BaseButtonClass);
(parent_class.sync_method)(obj, extra_text)
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
(parent_class.sync_method)(self.obj().unsafe_cast_ref(), extra_text)
}
}

fn parent_async_method(&self, obj: &BaseButton) -> PinnedFuture {
/// Retrieves the parent class' implementation of the async virtual method and calls it.
fn parent_async_method(&self) -> PinnedFuture<Result<(), glib::Error>> {
unsafe {
let data = Self::type_data();
let parent_class = &*(data.as_ref().parent_class() as *mut imp::BaseButtonClass);
(parent_class.async_method)(obj)
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
(parent_class.async_method)(self.obj().unsafe_cast_ref())
}
}
}

/// Make the BaseButton subclassable
impl<T: BaseButtonImpl> BaseButtonImplExt for T {}

/// This allows to implement subclasses of `BaseButton`.
unsafe impl<T: BaseButtonImpl> IsSubclassable<T> for BaseButton {
/// Called whenever the class of a `BaseButton` subclass is initialized, i.e. usually right
/// before the first instance of it is created.
fn class_init(class: &mut glib::Class<Self>) {
Self::parent_class_init::<T>(class.upcast_ref_mut());

// Override the virtual method function pointers to call directly into the `BaseButtonImpl`
// of the subclass.
//
// Note that this is only called for actual subclasses and not `BaseButton` itself:
// `BaseButton` does not implement `BaseButtonImpl` and handles this inside
// `ObjectSubclass::class_init()` for providing the default implementation of the virtual
// methods.
let klass = class.as_mut();
klass.sync_method = sync_method_trampoline::<T>;
klass.async_method = async_method_trampoline::<T>;
klass.sync_method = |obj, extra_text| unsafe {
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
imp.sync_method(extra_text)
};
klass.async_method = |obj| unsafe {
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
imp.async_method()
};
}
}

// Virtual method implementation trampolines
fn sync_method_trampoline<T>(this: &BaseButton, extra_text: Option<String>)
where
T: ObjectSubclass + BaseButtonImpl,
{
let imp = this.dynamic_cast_ref::<T::Type>().unwrap().imp();
imp.sync_method(this, extra_text)
/// GObject class struct with the function pointers for the virtual methods.
///
/// This must be `#[repr(C)]`.
#[repr(C)]
pub struct Class {
pub parent_class: gtk::ffi::GtkButtonClass,

// If these functions are meant to be called from C, you need to make these functions
// `unsafe extern "C" fn` & use FFI-safe types (usually raw pointers).
pub sync_method: fn(&BaseButton, extra_text: Option<&str>),
pub async_method: fn(&BaseButton) -> PinnedFuture<Result<(), glib::Error>>,
}

/// Make it possible to use this struct as class struct in an `ObjectSubclass` trait
/// implementation.
///
/// This is `unsafe` to enforce that the struct is `#[repr(C)]`.
unsafe impl ClassStruct for Class {
type Type = imp::BaseButton;
}

fn async_method_trampoline<T>(this: &BaseButton) -> PinnedFuture
where
T: ObjectSubclass + BaseButtonImpl,
{
let imp = this.dynamic_cast_ref::<T::Type>().unwrap().imp();
imp.async_method(this)
/// Deref directly to the parent class' class struct.
impl std::ops::Deref for Class {
type Target = glib::Class<<<Self as ClassStruct>::Type as ObjectSubclass>::ParentType>;

fn deref(&self) -> &Self::Target {
unsafe { &*(&self.parent_class as *const _ as *const _) }
}
}
11 changes: 6 additions & 5 deletions examples/virtual_methods/derived_button/imp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ impl ButtonImpl for DerivedButton {}

/// Implement the base trait and override the functions
impl BaseButtonImpl for DerivedButton {
fn sync_method(&self, obj: &BaseButton, extra_text: Option<String>) {
fn sync_method(&self, extra_text: Option<&str>) {
let obj = self.obj();
if let Some(text) = extra_text {
obj.set_label(&format!("DerivedButton sync {text}"));
} else {
obj.set_label("DerivedButton sync");
}
}

fn async_method(&self, obj: &BaseButton) -> PinnedFuture {
let obj_cloned = obj.clone();
Box::pin(gio::GioFuture::new(obj, move |_, _, send| {
obj_cloned.set_label("DerivedButton async");
fn async_method(&self) -> PinnedFuture<Result<(), glib::Error>> {
let obj = self.obj();
Box::pin(gio::GioFuture::new(&*obj, |obj, _, send| {
obj.set_label("DerivedButton async");
send.resolve(Ok(()));
}))
}
Expand Down

0 comments on commit fe9f2d3

Please sign in to comment.