Skip to content

Commit 7a0c646

Browse files
committed
Implement .as_weak() and .upgrade() for global component instances
The idea to make it easier and more convenient to pass a reference to a Slint global instance into a Rust closure / lambda. Fixes #9389
1 parent 96a26f8 commit 7a0c646

File tree

7 files changed

+279
-51
lines changed

7 files changed

+279
-51
lines changed

internal/compiler/generator/rust.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ pub fn generate(
263263
#[allow(unused_imports)]
264264
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)* #(#global_exports,)*};
265265
#[allow(unused_imports)]
266-
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
266+
pub use slint::{ComponentHandle as _, GlobalComponentHandle as _, Global as _, ModelExt as _};
267267
})
268268
}
269269

@@ -1567,9 +1567,30 @@ fn generate_global(
15671567

15681568
impl<'a> #public_component_id<'a> {
15691569
#property_and_callback_accessors
1570+
1571+
#[allow(unused)]
1572+
pub fn as_weak(&self) -> slint::GlobalWeak<#inner_component_id> {
1573+
let inner = ::core::pin::Pin::into_inner(self.0.clone());
1574+
slint::GlobalWeak::new(sp::Rc::downgrade(&inner))
1575+
}
15701576
}
15711577
#(pub type #aliases<'a> = #public_component_id<'a>;)*
15721578
#getters
1579+
1580+
impl slint::GlobalComponentHandle for #inner_component_id {
1581+
type Global<'a> = #public_component_id<'a>;
1582+
type WeakInner = sp::Weak<#inner_component_id>;
1583+
type PinnedInner = ::core::pin::Pin<sp::Rc<#inner_component_id>>;
1584+
1585+
fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> Option<Self::PinnedInner> {
1586+
let inner = ::core::pin::Pin::new(inner.upgrade()?);
1587+
Some(inner)
1588+
}
1589+
1590+
fn to_self<'a>(inner: &'a Self::PinnedInner) -> Self::Global<'a> {
1591+
#public_component_id(inner)
1592+
}
1593+
}
15731594
)
15741595
});
15751596

@@ -1578,7 +1599,7 @@ fn generate_global(
15781599
#[const_field_offset(sp::const_field_offset)]
15791600
#[repr(C)]
15801601
#[pin]
1581-
#pub_token struct #inner_component_id {
1602+
pub struct #inner_component_id {
15821603
#(#pub_token #declared_property_vars: sp::Property<#declared_property_types>,)*
15831604
#(#pub_token #declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
15841605
#(#pub_token #change_tracker_names : sp::ChangeTracker,)*

internal/core/api.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,173 @@ mod weak_handle {
955955

956956
pub use weak_handle::*;
957957

958+
/// This trait provides the necessary functionality for allowing creating strongly-referenced
959+
/// clones and conversion into/ a weak pointer for a Global slint component.
960+
///
961+
/// This trait is implemented by the [generated component](index.html#generated-components)
962+
pub trait GlobalComponentHandle {
963+
/// The type for the public global component interface.
964+
#[doc(hidden)]
965+
type Global<'a>;
966+
/// The internal Inner type for `Weak<Self>::inner`.
967+
#[doc(hidden)]
968+
type WeakInner: Clone + Default;
969+
/// The internal Inner type for the 'Pin<sp::Rc<InnerSelf>'.
970+
#[doc(hidden)]
971+
type PinnedInner: Clone;
972+
973+
/// Internal function used when upgrading a weak reference to a strong one.
974+
#[doc(hidden)]
975+
fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> Option<Self::PinnedInner>
976+
where
977+
Self: Sized;
978+
979+
/// Internal function used when upgrading a weak reference to a strong one.
980+
fn to_self<'a>(inner: &'a Self::PinnedInner) -> Self::Global<'a>
981+
where
982+
Self: Sized;
983+
}
984+
985+
pub use global_weak_handle::*;
986+
987+
mod global_weak_handle {
988+
use super::*;
989+
990+
/// Struct that's used to hold weak references of a [Slint global component](index.html#generated-components)
991+
///
992+
/// In order to create a GlobalWeak, you should call .as_weak() on the global component instance.
993+
pub struct GlobalWeak<T: GlobalComponentHandle> {
994+
inner: T::WeakInner,
995+
#[cfg(feature = "std")]
996+
thread: std::thread::ThreadId,
997+
}
998+
999+
/// Struct that's used to hold a strong reference of a Slint global component
1000+
pub struct GlobalStrong<T: GlobalComponentHandle>(T::PinnedInner);
1001+
1002+
impl<T: GlobalComponentHandle> GlobalStrong<T> {
1003+
/// Get the actual global component
1004+
pub fn to_global<'a>(&'a self) -> T::Global<'a> {
1005+
T::to_self(&self.0)
1006+
}
1007+
}
1008+
1009+
impl<T: GlobalComponentHandle> Default for GlobalWeak<T> {
1010+
fn default() -> Self {
1011+
Self {
1012+
inner: T::WeakInner::default(),
1013+
#[cfg(feature = "std")]
1014+
thread: std::thread::current().id(),
1015+
}
1016+
}
1017+
}
1018+
1019+
impl<T: GlobalComponentHandle> Clone for GlobalWeak<T> {
1020+
fn clone(&self) -> Self {
1021+
Self {
1022+
inner: self.inner.clone(),
1023+
#[cfg(feature = "std")]
1024+
thread: self.thread,
1025+
}
1026+
}
1027+
}
1028+
1029+
impl<T: GlobalComponentHandle> GlobalWeak<T> {
1030+
#[doc(hidden)]
1031+
pub fn new(inner: T::WeakInner) -> Self {
1032+
Self {
1033+
inner,
1034+
#[cfg(feature = "std")]
1035+
thread: std::thread::current().id(),
1036+
}
1037+
}
1038+
1039+
/// Returns a new GlobalStrong struct, where it's possible to get the global component
1040+
/// struct interface. Ff some other instance still
1041+
/// holds a strong reference. Otherwise, returns None.
1042+
///
1043+
/// This also returns None if the current thread is not the thread that created
1044+
/// the component
1045+
pub fn upgrade(&self) -> Option<GlobalStrong<T>> {
1046+
#[cfg(feature = "std")]
1047+
if std::thread::current().id() != self.thread {
1048+
return None;
1049+
}
1050+
let coso = T::upgrade_from_weak_inner(&self.inner)?;
1051+
Some(GlobalStrong(coso.clone()))
1052+
}
1053+
1054+
/// Convenience function where a given functor is called with the global component
1055+
///
1056+
/// If the current thread is not the thread that created the component the functor
1057+
/// will not be called and this function will do nothing.
1058+
pub fn upgrade_in(&self, func: impl FnOnce(T::Global<'_>)) {
1059+
#[cfg(feature = "std")]
1060+
if std::thread::current().id() != self.thread {
1061+
return;
1062+
}
1063+
1064+
if let Some(inner) = T::upgrade_from_weak_inner(&self.inner) {
1065+
func(T::to_self(&inner));
1066+
}
1067+
}
1068+
1069+
/// Convenience function that combines [`invoke_from_event_loop()`] with [`Self::upgrade()`]
1070+
///
1071+
/// The given functor will be added to an internal queue and will wake the event loop.
1072+
/// On the next iteration of the event loop, the functor will be executed with a `T` as an argument.
1073+
///
1074+
/// If the component was dropped because there are no more strong reference to the component,
1075+
/// the functor will not be called.
1076+
/// # Example
1077+
/// ```rust
1078+
/// # i_slint_backend_testing::init_no_event_loop();
1079+
/// slint::slint! {
1080+
/// export global MyAppData { in property<int> foo; }
1081+
/// export component MyApp inherits Window { /* ... */ } }
1082+
/// }
1083+
/// let ui = MyApp::new().unwrap();
1084+
/// let my_app_data = ui.global::<MyAppData>();
1085+
/// let my_app_data_weak = my_app_data.as_weak();
1086+
///
1087+
/// let thread = std::thread::spawn(move || {
1088+
/// // ... Do some computation in the thread
1089+
/// let foo = 42;
1090+
/// # assert!(my_app_data_weak.upgrade().is_none()); // note that upgrade fails in a thread
1091+
/// # return; // don't upgrade_in_event_loop in our examples
1092+
/// // now forward the data to the main thread using upgrade_in_event_loop
1093+
/// my_app_data_weak.upgrade_in_event_loop(move |my_app_data| my_app_data.set_foo(foo));
1094+
/// });
1095+
/// # thread.join().unwrap(); return; // don't run the event loop in examples
1096+
/// ui.run().unwrap();
1097+
/// ```
1098+
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
1099+
pub fn upgrade_in_event_loop(
1100+
&self,
1101+
func: impl FnOnce(T::Global<'_>) + Send + 'static,
1102+
) -> Result<(), EventLoopError>
1103+
where
1104+
T: 'static,
1105+
{
1106+
let weak_handle = self.clone();
1107+
super::invoke_from_event_loop(move || {
1108+
if let Some(h) = weak_handle.upgrade() {
1109+
func(h.to_global());
1110+
}
1111+
})
1112+
}
1113+
}
1114+
1115+
// Safety: we make sure in upgrade that the thread is the proper one,
1116+
// and the Weak only use atomic pointer so it is safe to clone and drop in another thread
1117+
#[allow(unsafe_code)]
1118+
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
1119+
unsafe impl<T: GlobalComponentHandle> Send for GlobalWeak<T> {}
1120+
#[allow(unsafe_code)]
1121+
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
1122+
unsafe impl<T: GlobalComponentHandle> Sync for GlobalWeak<T> {}
1123+
}
1124+
9581125
/// Adds the specified function to an internal queue, notifies the event loop to wake up.
9591126
/// Once woken up, any queued up functors will be invoked.
9601127
///

tests/manual/module-builds/app/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn Error>> {
2525
let mut bdata = blogica::backend::BData::default();
2626

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

3939
bdata.codes = slint::ModelRc::new(slint::VecModel::from(
40-
(1..6)
40+
(1..5)
4141
.into_iter()
4242
.map(|_| slint::SharedString::from(random_word::get(random_word::Lang::En)))
4343
.collect::<Vec<_>>(),

tests/manual/module-builds/blogica/src/lib.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: MIT
33

44
pub mod backend {
5-
use slint::SharedString;
5+
use slint::{Model, SharedString};
66

77
slint::include_modules!();
88

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

15+
blogica_api.on_update({
16+
let blogica_api = blogica_api.as_weak();
17+
move |bdata| {
18+
{
19+
let blogica_api = blogica_api.upgrade().unwrap();
20+
let blogica_api = blogica_api.to_global();
21+
22+
if bdata.colors.row_count() >= 4 {
23+
blogica_api.set_color1(bdata.colors.row_data(0).unwrap());
24+
blogica_api.set_color2(bdata.colors.row_data(1).unwrap());
25+
blogica_api.set_color3(bdata.colors.row_data(2).unwrap());
26+
blogica_api.set_color4(bdata.colors.row_data(3).unwrap());
27+
}
28+
}
29+
30+
blogica_api.upgrade_in(move |blogica_api| {
31+
if bdata.codes.row_count() >= 4 {
32+
blogica_api.set_code1(bdata.codes.row_data(0).unwrap());
33+
blogica_api.set_code2(bdata.codes.row_data(1).unwrap());
34+
blogica_api.set_code3(bdata.codes.row_data(2).unwrap());
35+
blogica_api.set_code4(bdata.codes.row_data(3).unwrap());
36+
}
37+
});
38+
}
39+
});
40+
1541
blogica_api.set_initialized(true);
1642
}
1743
}

tests/manual/module-builds/blogica/ui/blogica.slint

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,34 @@ export struct BData {
99
export global BLogicAAPI {
1010
in property <bool> initialized: false;
1111

12-
out property <color> color1: #0e3151;
13-
out property <color> color2: #107013;
14-
out property <color> color3: #8a1624;
15-
out property <color> color4: #e4d213;
12+
in property <color> color1: #0e3151;
13+
in property <color> color2: #107013;
14+
in property <color> color3: #8a1624;
15+
in property <color> color4: #e4d213;
1616

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

22-
public function update(bdata:BData) {
23-
if (bdata.colors.length >= 4) {
24-
self.color1 = bdata.colors[0];
25-
self.color2 = bdata.colors[1];
26-
self.color3 = bdata.colors[2];
27-
self.color4 = bdata.colors[3];
28-
}
29-
if (bdata.codes.length >= 4) {
30-
self.code1 = bdata.codes[0];
31-
self.code2 = bdata.codes[1];
32-
self.code3 = bdata.codes[2];
33-
self.code4 = bdata.codes[3];
34-
}
35-
}
22+
callback update(BData);
3623
}
3724

3825
export component BLogicA {
3926
private property <bool> api-initialized <=> BLogicAAPI.initialized;
27+
28+
// Workaround - binding BLogicAAPI.colorN directly to Rectangle background
29+
// property does not work
30+
private property <color> color1 <=> BLogicAAPI.color1;
31+
private property <color> color2 <=> BLogicAAPI.color2;
32+
private property <color> color3 <=> BLogicAAPI.color3;
33+
private property <color> color4 <=> BLogicAAPI.color4;
34+
4035
width: 600px; height: 200px;
4136
Rectangle {
4237
x: 0px; y:0px;
4338
width: 50%; height: 50%;
44-
background: BLogicAAPI.color1;
39+
background: color1;
4540
Text {
4641
text <=> BLogicAAPI.code1;
4742
color: white;
@@ -53,7 +48,7 @@ export component BLogicA {
5348
Rectangle {
5449
x: root.width / 2; y:0px;
5550
width: 50%; height: 50%;
56-
background: BLogicAAPI.color2;
51+
background: color2;
5752
Text {
5853
text <=> BLogicAAPI.code2;
5954
color: white;
@@ -65,7 +60,7 @@ export component BLogicA {
6560
Rectangle {
6661
x: 0px; y:root.height / 2;
6762
width: 50%; height: 50%;
68-
background: BLogicAAPI.color3;
63+
background: color3;
6964
Text {
7065
text <=> BLogicAAPI.code3;
7166
color: white;
@@ -77,7 +72,7 @@ export component BLogicA {
7772
Rectangle {
7873
x: root.width / 2; y: root.height / 2;
7974
width: 50%; height: 50%;
80-
background: BLogicAAPI.color4;
75+
background: color4;
8176
Text {
8277
text <=> BLogicAAPI.code4;
8378
color: white;

0 commit comments

Comments
 (0)