Skip to content

Commit 46a0a06

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 8261c84 commit 46a0a06

File tree

7 files changed

+280
-51
lines changed

7 files changed

+280
-51
lines changed

internal/compiler/generator/rust.rs

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

@@ -1570,9 +1570,30 @@ fn generate_global(
15701570

15711571
impl<'a> #public_component_id<'a> {
15721572
#property_and_callback_accessors
1573+
1574+
#[allow(unused)]
1575+
pub fn as_weak(&self) -> slint::GlobalWeak<#inner_component_id> {
1576+
let inner = ::core::pin::Pin::into_inner(self.0.clone());
1577+
slint::GlobalWeak::new(sp::Rc::downgrade(&inner))
1578+
}
15731579
}
15741580
#(pub type #aliases<'a> = #public_component_id<'a>;)*
15751581
#getters
1582+
1583+
impl slint::GlobalComponentHandle for #inner_component_id {
1584+
type Global<'a> = #public_component_id<'a>;
1585+
type WeakInner = sp::Weak<#inner_component_id>;
1586+
type PinnedInner = ::core::pin::Pin<sp::Rc<#inner_component_id>>;
1587+
1588+
fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> sp::Option<Self::PinnedInner> {
1589+
let inner = ::core::pin::Pin::new(inner.upgrade()?);
1590+
Some(inner)
1591+
}
1592+
1593+
fn to_self<'a>(inner: &'a Self::PinnedInner) -> Self::Global<'a> {
1594+
#public_component_id(inner)
1595+
}
1596+
}
15761597
)
15771598
});
15781599

@@ -1581,7 +1602,7 @@ fn generate_global(
15811602
#[const_field_offset(sp::const_field_offset)]
15821603
#[repr(C)]
15831604
#[pin]
1584-
#pub_token struct #inner_component_id {
1605+
pub struct #inner_component_id {
15851606
#(#pub_token #declared_property_vars: sp::Property<#declared_property_types>,)*
15861607
#(#pub_token #declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
15871608
#(#pub_token #change_tracker_names : sp::ChangeTracker,)*

internal/core/api.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,174 @@ mod weak_handle {
974974

975975
pub use weak_handle::*;
976976

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

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.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)