From fe9f2d314e97429b21dc27879927b88bb7e22f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 15 Oct 2023 11:28:03 +0300 Subject: [PATCH] examples: Clean up, modernize and simplify virtual methods example --- examples/virtual_methods/base_button/imp.rs | 88 ++++------ examples/virtual_methods/base_button/mod.rs | 157 ++++++++++++------ .../virtual_methods/derived_button/imp.rs | 11 +- 3 files changed, 141 insertions(+), 115 deletions(-) diff --git a/examples/virtual_methods/base_button/imp.rs b/examples/virtual_methods/base_button/imp.rs index ae898783e288..0d1e7a606605 100644 --- a/examples/virtual_methods/base_button/imp.rs +++ b/examples/virtual_methods/base_button/imp.rs @@ -1,54 +1,20 @@ -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> + '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), - 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) { - 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) { - 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) { + /// 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 { @@ -56,14 +22,12 @@ impl BaseButton { } } - 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> { + let obj = self.obj(); + Box::pin(gio::GioFuture::new(&*obj, |obj, _, send| { + obj.set_label("BaseButton async"); + send.resolve(Ok(())); + })) } } @@ -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(); } } diff --git a/examples/virtual_methods/base_button/mod.rs b/examples/virtual_methods/base_button/mod.rs index f3beb7072a65..f9042ce619a5 100644 --- a/examples/virtual_methods/base_button/mod.rs +++ b/examples/virtual_methods/base_button/mod.rs @@ -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 = Pin>>; glib::wrapper! { + /// Public type for the `BaseButton` instances. pub struct BaseButton(ObjectSubclass) @extends gtk::Widget, gtk::Button; } impl BaseButton { + /// Creates a new instance of `BaseButton`. pub fn new() -> Self { glib::Object::new() } @@ -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); - 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 { + /// 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::().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> BaseButtonExt for O { - fn sync_method(&self, extra_text: Option) { - imp::base_button_sync_method(self.upcast_ref::(), 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::(); + (obj.class().as_ref().sync_method)(obj, extra_text); } - fn async_method(&self) -> PinnedFuture { - imp::base_button_async_method(self.upcast_ref::()) + /// 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> { + let obj = self.upcast_ref::(); + (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) { - self.parent_sync_method(obj, extra_text) +impl> 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> { + self.parent_async_method() } } -pub trait BaseButtonImplExt: ObjectSubclass { - fn parent_sync_method(&self, obj: &BaseButton, extra_text: Option); - fn parent_async_method(&self, obj: &BaseButton) -> PinnedFuture; -} - -impl BaseButtonImplExt for T { - fn parent_sync_method(&self, obj: &BaseButton, extra_text: Option) { +/// 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> { 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 BaseButtonImplExt for T {} + +/// This allows to implement subclasses of `BaseButton`. unsafe impl IsSubclassable 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::parent_class_init::(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::; - klass.async_method = async_method_trampoline::; + klass.sync_method = |obj, extra_text| unsafe { + let imp = obj.unsafe_cast_ref::().imp(); + imp.sync_method(extra_text) + }; + klass.async_method = |obj| unsafe { + let imp = obj.unsafe_cast_ref::().imp(); + imp.async_method() + }; } } -// Virtual method implementation trampolines -fn sync_method_trampoline(this: &BaseButton, extra_text: Option) -where - T: ObjectSubclass + BaseButtonImpl, -{ - let imp = this.dynamic_cast_ref::().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>, +} + +/// 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(this: &BaseButton) -> PinnedFuture -where - T: ObjectSubclass + BaseButtonImpl, -{ - let imp = this.dynamic_cast_ref::().unwrap().imp(); - imp.async_method(this) +/// Deref directly to the parent class' class struct. +impl std::ops::Deref for Class { + type Target = glib::Class<<::Type as ObjectSubclass>::ParentType>; + + fn deref(&self) -> &Self::Target { + unsafe { &*(&self.parent_class as *const _ as *const _) } + } } diff --git a/examples/virtual_methods/derived_button/imp.rs b/examples/virtual_methods/derived_button/imp.rs index 8ffeaabea2bf..a06dcbed5eb0 100644 --- a/examples/virtual_methods/derived_button/imp.rs +++ b/examples/virtual_methods/derived_button/imp.rs @@ -17,7 +17,8 @@ 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) { + 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 { @@ -25,10 +26,10 @@ impl BaseButtonImpl for DerivedButton { } } - 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> { + let obj = self.obj(); + Box::pin(gio::GioFuture::new(&*obj, |obj, _, send| { + obj.set_label("DerivedButton async"); send.resolve(Ok(())); })) }