Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions internal/compiler/generator/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ pub fn generate(
#[allow(unused_imports)]
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)* #(#global_exports,)*};
#[allow(unused_imports)]
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
pub use slint::{ComponentHandle as _, GlobalComponentHandle as _, Global as _, ModelExt as _};
})
}

Expand Down Expand Up @@ -1570,9 +1570,30 @@ fn generate_global(

impl<'a> #public_component_id<'a> {
#property_and_callback_accessors

#[allow(unused)]
pub fn as_weak(&self) -> slint::GlobalWeak<#inner_component_id> {
let inner = ::core::pin::Pin::into_inner(self.0.clone());
slint::GlobalWeak::new(sp::Rc::downgrade(&inner))
}
}
#(pub type #aliases<'a> = #public_component_id<'a>;)*
#getters

impl slint::GlobalComponentHandle for #inner_component_id {
type Global<'a> = #public_component_id<'a>;
type WeakInner = sp::Weak<#inner_component_id>;
type PinnedInner = ::core::pin::Pin<sp::Rc<#inner_component_id>>;

fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> sp::Option<Self::PinnedInner> {
let inner = ::core::pin::Pin::new(inner.upgrade()?);
Some(inner)
}

fn to_self<'a>(inner: &'a Self::PinnedInner) -> Self::Global<'a> {
#public_component_id(inner)
}
}
)
});

Expand All @@ -1581,7 +1602,7 @@ fn generate_global(
#[const_field_offset(sp::const_field_offset)]
#[repr(C)]
#[pin]
#pub_token struct #inner_component_id {
pub struct #inner_component_id {
#(#pub_token #declared_property_vars: sp::Property<#declared_property_types>,)*
#(#pub_token #declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
#(#pub_token #change_tracker_names : sp::ChangeTracker,)*
Expand Down
167 changes: 167 additions & 0 deletions internal/core/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,173 @@ mod weak_handle {

pub use weak_handle::*;

/// This trait provides the necessary functionality for allowing creating strongly-referenced
/// clones and conversion into a weak pointer for a Global slint component.
///
/// This trait is implemented by the [generated component](index.html#generated-components)
pub trait GlobalComponentHandle {
/// The type for the public global component interface.
#[doc(hidden)]
type Global<'a>;
/// The internal Inner type for `Weak<Self>::inner`.
#[doc(hidden)]
type WeakInner: Clone + Default;
/// The internal Inner type for the 'Pin<sp::Rc<InnerSelf>'.
#[doc(hidden)]
type PinnedInner: Clone;

/// Internal function used when upgrading a weak reference to a strong one.
#[doc(hidden)]
fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> Option<Self::PinnedInner>
where
Self: Sized;

/// Internal function used when upgrading a weak reference to a strong one.
fn to_self<'a>(inner: &'a Self::PinnedInner) -> Self::Global<'a>
where
Self: Sized;
}

pub use global_weak_handle::*;

mod global_weak_handle {
use super::*;

/// Struct that's used to hold weak references of a [Slint global component](index.html#generated-components)
///
/// In order to create a GlobalWeak, you should call .as_weak() on the global component instance.
pub struct GlobalWeak<T: GlobalComponentHandle> {
inner: T::WeakInner,
#[cfg(feature = "std")]
thread: std::thread::ThreadId,
}

/// Struct that's used to hold a strong reference of a Slint global component
pub struct GlobalStrong<T: GlobalComponentHandle>(T::PinnedInner);

impl<T: GlobalComponentHandle> GlobalStrong<T> {
/// Get the actual global component
pub fn to_global<'a>(&'a self) -> T::Global<'a> {
T::to_self(&self.0)
}
}

impl<T: GlobalComponentHandle> Default for GlobalWeak<T> {
fn default() -> Self {
Self {
inner: T::WeakInner::default(),
#[cfg(feature = "std")]
thread: std::thread::current().id(),
}
}
}

impl<T: GlobalComponentHandle> Clone for GlobalWeak<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
#[cfg(feature = "std")]
thread: self.thread,
}
}
}

impl<T: GlobalComponentHandle> GlobalWeak<T> {
#[doc(hidden)]
pub fn new(inner: T::WeakInner) -> Self {
Self {
inner,
#[cfg(feature = "std")]
thread: std::thread::current().id(),
}
}

/// Returns a new GlobalStrong struct, where it's possible to get the global component
/// struct interface. If some other instance still holds a strong reference.
/// Otherwise, returns None.
///
/// This also returns None if the current thread is not the thread that created
/// the component
pub fn upgrade(&self) -> Option<GlobalStrong<T>> {
#[cfg(feature = "std")]
if std::thread::current().id() != self.thread {
return None;
}
let inner = T::upgrade_from_weak_inner(&self.inner)?;
Some(GlobalStrong(inner.clone()))
}

/// Convenience function where a given functor is called with the global component
///
/// If the current thread is not the thread that created the component the functor
/// will not be called and this function will do nothing.
pub fn upgrade_in(&self, func: impl FnOnce(T::Global<'_>)) {
#[cfg(feature = "std")]
if std::thread::current().id() != self.thread {
return;
}

if let Some(inner) = T::upgrade_from_weak_inner(&self.inner) {
func(T::to_self(&inner));
}
}

/// Convenience function that combines [`invoke_from_event_loop()`] with [`Self::upgrade()`]
///
/// The given functor will be added to an internal queue and will wake the event loop.
/// On the next iteration of the event loop, the functor will be executed with a `T` as an argument.
///
/// If the component was dropped because there are no more strong reference to the component,
/// the functor will not be called.
/// # Example
/// ```rust
/// # i_slint_backend_testing::init_no_event_loop();
/// slint::slint! {
/// export global MyAppData { in property<int> foo; }
/// export component MyApp inherits Window { /* ... */ }
/// }
/// let ui = MyApp::new().unwrap();
/// let my_app_data = ui.global::<MyAppData>();
/// let my_app_data_weak = my_app_data.as_weak();
///
/// let thread = std::thread::spawn(move || {
/// // ... Do some computation in the thread
/// let foo = 42;
/// # assert!(my_app_data_weak.upgrade().is_none()); // note that upgrade fails in a thread
/// # return; // don't upgrade_in_event_loop in our examples
/// // now forward the data to the main thread using upgrade_in_event_loop
/// my_app_data_weak.upgrade_in_event_loop(move |my_app_data| my_app_data.set_foo(foo));
/// });
/// # thread.join().unwrap(); return; // don't run the event loop in examples
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused. This is docu, not actual example, why the commented-out code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a lot of copy / paste from the same function for Weak - at line 930

/// ui.run().unwrap();
/// ```
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
pub fn upgrade_in_event_loop(
&self,
func: impl FnOnce(T::Global<'_>) + Send + 'static,
) -> Result<(), EventLoopError>
where
T: 'static,
{
let weak_handle = self.clone();
super::invoke_from_event_loop(move || {
if let Some(h) = weak_handle.upgrade() {
func(h.to_global());
}
})
}
}

// Safety: we make sure in upgrade that the thread is the proper one,
// and the Weak only use atomic pointer so it is safe to clone and drop in another thread
#[allow(unsafe_code)]
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
unsafe impl<T: GlobalComponentHandle> Send for GlobalWeak<T> {}
#[allow(unsafe_code)]
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
unsafe impl<T: GlobalComponentHandle> Sync for GlobalWeak<T> {}
}

/// Adds the specified function to an internal queue, notifies the event loop to wake up.
/// Once woken up, any queued up functors will be invoked.
///
Expand Down
4 changes: 2 additions & 2 deletions tests/manual/module-builds/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut bdata = blogica::backend::BData::default();

bdata.colors = slint::ModelRc::new(slint::VecModel::from(
(1..6)
(1..5)
.into_iter()
.map(|_| {
let red = rand::random::<u8>();
Expand All @@ -37,7 +37,7 @@ fn main() -> Result<(), Box<dyn Error>> {
));

bdata.codes = slint::ModelRc::new(slint::VecModel::from(
(1..6)
(1..5)
.into_iter()
.map(|_| slint::SharedString::from(random_word::get(random_word::Lang::En)))
.collect::<Vec<_>>(),
Expand Down
28 changes: 27 additions & 1 deletion tests/manual/module-builds/blogica/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT

pub mod backend {
use slint::SharedString;
use slint::{Model, SharedString};

slint::include_modules!();

Expand All @@ -12,6 +12,32 @@ pub mod backend {
blogica_api.set_code3(SharedString::from("Yet another important thing"));
blogica_api.set_code4(SharedString::from("One more important thing"));

blogica_api.on_update({
let blogica_api = blogica_api.as_weak();
move |bdata| {
{
let blogica_api = blogica_api.upgrade().unwrap();
let blogica_api = blogica_api.to_global();

if bdata.colors.row_count() >= 4 {
blogica_api.set_color1(bdata.colors.row_data(0).unwrap());
blogica_api.set_color2(bdata.colors.row_data(1).unwrap());
blogica_api.set_color3(bdata.colors.row_data(2).unwrap());
blogica_api.set_color4(bdata.colors.row_data(3).unwrap());
}
}

blogica_api.upgrade_in(move |blogica_api| {
if bdata.codes.row_count() >= 4 {
blogica_api.set_code1(bdata.codes.row_data(0).unwrap());
blogica_api.set_code2(bdata.codes.row_data(1).unwrap());
blogica_api.set_code3(bdata.codes.row_data(2).unwrap());
blogica_api.set_code4(bdata.codes.row_data(3).unwrap());
}
});
}
});

blogica_api.set_initialized(true);
}
}
47 changes: 21 additions & 26 deletions tests/manual/module-builds/blogica/ui/blogica.slint
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,34 @@ export struct BData {
export global BLogicAAPI {
in property <bool> initialized: false;

out property <color> color1: #0e3151;
out property <color> color2: #107013;
out property <color> color3: #8a1624;
out property <color> color4: #e4d213;
in property <color> color1: #0e3151;
in property <color> color2: #107013;
in property <color> color3: #8a1624;
in property <color> color4: #e4d213;

in-out property <string> code1: "Important thing";
in-out property <string> code2: "Also important thing";
in-out property <string> code3: "May be an important thingy";
in-out property <string> code4: "Not a important thing";
in property <string> code1: "Important thing";
in property <string> code2: "Also important thing";
in property <string> code3: "May be an important thingy";
in property <string> code4: "Not a important thing";

public function update(bdata:BData) {
if (bdata.colors.length >= 4) {
self.color1 = bdata.colors[0];
self.color2 = bdata.colors[1];
self.color3 = bdata.colors[2];
self.color4 = bdata.colors[3];
}
if (bdata.codes.length >= 4) {
self.code1 = bdata.codes[0];
self.code2 = bdata.codes[1];
self.code3 = bdata.codes[2];
self.code4 = bdata.codes[3];
}
}
callback update(BData);
}

export component BLogicA {
private property <bool> api-initialized <=> BLogicAAPI.initialized;

// Workaround - binding BLogicAAPI.colorN directly to Rectangle background
// property does not work
private property <color> color1 <=> BLogicAAPI.color1;
private property <color> color2 <=> BLogicAAPI.color2;
private property <color> color3 <=> BLogicAAPI.color3;
private property <color> color4 <=> BLogicAAPI.color4;

width: 600px; height: 200px;
Rectangle {
x: 0px; y:0px;
width: 50%; height: 50%;
background: BLogicAAPI.color1;
background: color1;
Text {
text <=> BLogicAAPI.code1;
color: white;
Expand All @@ -53,7 +48,7 @@ export component BLogicA {
Rectangle {
x: root.width / 2; y:0px;
width: 50%; height: 50%;
background: BLogicAAPI.color2;
background: color2;
Text {
text <=> BLogicAAPI.code2;
color: white;
Expand All @@ -65,7 +60,7 @@ export component BLogicA {
Rectangle {
x: 0px; y:root.height / 2;
width: 50%; height: 50%;
background: BLogicAAPI.color3;
background: color3;
Text {
text <=> BLogicAAPI.code3;
color: white;
Expand All @@ -77,7 +72,7 @@ export component BLogicA {
Rectangle {
x: root.width / 2; y: root.height / 2;
width: 50%; height: 50%;
background: BLogicAAPI.color4;
background: color4;
Text {
text <=> BLogicAAPI.code4;
color: white;
Expand Down
Loading
Loading