1use crate::Property;
5use crate::api::PlatformError;
6use crate::graphics::Color;
7use crate::input::InternalKeyboardModifierState;
8use crate::item_tree::{ItemRc, ItemTreeRc};
9use crate::items::ColorScheme;
10use crate::lengths::LogicalLength;
11use crate::platform::{EventLoopProxy, Platform, WindowAdapter, WindowEvent};
12use alloc::boxed::Box;
13use alloc::rc::Rc;
14use core::cell::Cell;
15use core::cell::RefCell;
16use pin_weak::rc::PinWeak;
17
18pub type WindowEventHook =
21 Box<dyn Fn(&Rc<dyn WindowAdapter>, &WindowEvent, crate::platform::WindowEventDispatchResult)>;
22
23crate::thread_local! {
24 pub(crate) static GLOBAL_CONTEXT : once_cell::unsync::OnceCell<SlintContext>
25 = const { once_cell::unsync::OnceCell::new() }
26}
27
28#[pin_project::pin_project]
29pub(crate) struct SlintContextInner {
30 platform: Box<dyn Platform>,
31 pub(crate) window_count: core::cell::RefCell<isize>,
32
33 #[pin]
37 pub(crate) translations_dirty: Property<usize>,
38 pub(crate) translations_bundle:
39 core::cell::RefCell<Option<alloc::vec::Vec<i_slint_common::TranslationsBundled>>>,
40 #[cfg(feature = "tr")]
41 external_translator: core::cell::RefCell<Option<Box<dyn tr::Translator>>>,
42 #[pin]
43 pub(crate) locale_decimal_separator: Property<char>,
44
45 #[pin]
49 pub(crate) color_scheme: Property<ColorScheme>,
50 #[pin]
54 pub(crate) accent_color: Property<Color>,
55 #[pin]
60 pub(crate) platform_default_font_size: Property<Option<LogicalLength>>,
61 pub(crate) window_shown_hook:
62 core::cell::RefCell<Option<Box<dyn FnMut(&Rc<dyn crate::platform::WindowAdapter>)>>>,
63 pub(crate) window_event_hook: core::cell::RefCell<Option<WindowEventHook>>,
64 pub(crate) log_message_handler: RefCell<Option<crate::debug_log::LogMessageHandler>>,
65 #[cfg(all(unix, not(target_os = "macos")))]
66 xdg_app_id: core::cell::RefCell<Option<crate::SharedString>>,
67 #[cfg(feature = "shared-parley")]
68 pub(crate) font_context: core::cell::RefCell<crate::textlayout::sharedparley::FontContext>,
69 #[cfg(feature = "shared-swash")]
70 pub(crate) swash_scale_context: core::cell::RefCell<swash::scale::ScaleContext>,
71 pub(crate) modifiers: Cell<InternalKeyboardModifierState>,
72
73 pub(crate) timers: crate::timers::TimerListRc,
76}
77
78#[derive(Clone)]
82pub struct SlintContext(pub(crate) core::pin::Pin<Rc<SlintContextInner>>);
83
84impl SlintContext {
85 pub fn new(platform: Box<dyn Platform + 'static>) -> Self {
93 #[cfg(feature = "shared-parley")]
94 let collection = i_slint_common::sharedfontique::create_collection(true);
95
96 let this = Self(Rc::pin(SlintContextInner {
97 platform,
98 window_count: 0.into(),
99
100 translations_dirty: Property::new_named(0, "SlintContext::translations"),
101 translations_bundle: Default::default(),
102 #[cfg(feature = "tr")]
103 external_translator: Default::default(),
104 locale_decimal_separator: Property::new_named(
105 i_slint_common::DEFAULT_DECIMAL_SEPARATOR,
106 "SlintContext::locale_decimal_separator",
107 ),
108
109 color_scheme: Property::new_named(ColorScheme::Unknown, "SlintContext::color_scheme"),
110 accent_color: Property::new_named(Color::default(), "SlintContext::accent_color"),
111 platform_default_font_size: Property::new_named(
112 None,
113 "SlintContext::platform_default_font_size",
114 ),
115 window_shown_hook: Default::default(),
116 window_event_hook: Default::default(),
117 log_message_handler: Default::default(),
118 #[cfg(all(unix, not(target_os = "macos")))]
119 xdg_app_id: Default::default(),
120 #[cfg(feature = "shared-parley")]
121 font_context: {
122 let font_context = parley::FontContext {
123 collection: collection.inner,
124 source_cache: collection.source_cache,
125 };
126 core::cell::RefCell::new(crate::textlayout::sharedparley::FontContext::new(
127 font_context,
128 ))
129 },
130 #[cfg(feature = "shared-swash")]
131 swash_scale_context: core::cell::RefCell::new(swash::scale::ScaleContext::new()),
132 modifiers: Cell::new(Default::default()),
133 timers: crate::timers::take_pending_timers(),
137 }));
138 crate::timers::set_owning_context(&this.0.timers, &this);
141 GLOBAL_CONTEXT.with(|slot| {
145 let _ = slot.set(this.clone());
146 });
147 this.platform().bind_context(this.downgrade(), crate::InternalToken);
151 this
152 }
153
154 pub fn platform(&self) -> &dyn Platform {
156 &*self.0.platform
157 }
158
159 #[cfg(feature = "shared-parley")]
161 pub fn font_context(
162 &self,
163 ) -> &core::cell::RefCell<crate::textlayout::sharedparley::FontContext> {
164 &self.0.font_context
165 }
166
167 #[cfg(feature = "shared-swash")]
169 pub fn swash_scale_context(&self) -> &core::cell::RefCell<swash::scale::ScaleContext> {
170 &self.0.swash_scale_context
171 }
172
173 pub fn event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
176 self.0.platform.new_event_loop_proxy()
177 }
178
179 #[cfg(target_has_atomic = "ptr")]
180 pub fn spawn_local<F: core::future::Future + 'static>(
182 &self,
183 fut: F,
184 ) -> Result<crate::future::JoinHandle<F::Output>, crate::api::EventLoopError> {
185 crate::future::spawn_local_with_ctx(self, fut)
186 }
187
188 pub fn run_event_loop(&self) -> Result<(), PlatformError> {
189 self.0.platform.run_event_loop()
190 }
191
192 pub fn new_timer(&self) -> crate::timers::Timer {
200 crate::timers::Timer::with_list(&self.0.timers)
201 }
202
203 pub fn single_shot(&self, duration: core::time::Duration, callback: impl FnOnce() + 'static) {
208 crate::timers::single_shot_on(&self.0.timers, duration, callback);
209 }
210
211 pub fn update_timers_and_animations(&self) {
218 let now = crate::animations::Instant::now(self);
219 crate::animations::update_animations(now);
220 self.maybe_activate_timers(now);
221 crate::properties::ChangeTracker::run_change_handlers();
222 }
223
224 pub fn duration_until_next_timer_update(&self) -> Option<core::time::Duration> {
230 let timeout = self.next_timer_timeout()?;
231 let now = crate::animations::Instant::now(self);
232 Some(core::time::Duration::from_millis(timeout.0.saturating_sub(now.0)))
233 }
234
235 pub fn maybe_activate_timers(&self, now: crate::animations::Instant) -> bool {
238 crate::timers::TimerList::activate_expired(&self.0.timers, now)
239 }
240
241 pub fn next_timer_timeout(&self) -> Option<crate::animations::Instant> {
243 self.0.timers.borrow().first_timeout()
244 }
245
246 pub fn color_scheme(&self, root: Option<&ItemTreeRc>) -> ColorScheme {
253 if let Some(root) = root {
254 let root_item = ItemRc::new_root(root.clone());
255 if let Some(tray) = root_item.downcast::<crate::items::SystemTrayIcon>() {
256 let scheme = tray.as_pin_ref().color_scheme();
257 if scheme != ColorScheme::Unknown {
258 return scheme;
259 }
260 }
261 }
262 self.0.as_ref().project_ref().color_scheme.get()
263 }
264
265 pub fn set_color_scheme(&self, scheme: ColorScheme) {
268 self.0.as_ref().project_ref().color_scheme.set(scheme);
269 }
270
271 pub fn accent_color(&self) -> Color {
274 self.0.as_ref().project_ref().accent_color.get()
275 }
276
277 pub fn set_accent_color(&self, color: Color) {
280 self.0.as_ref().project_ref().accent_color.set(color);
281 }
282
283 pub fn platform_default_font_size(&self) -> Option<LogicalLength> {
287 self.0.as_ref().project_ref().platform_default_font_size.get()
288 }
289
290 pub fn set_platform_default_font_size(&self, size: Option<LogicalLength>) {
293 self.0.as_ref().project_ref().platform_default_font_size.set(size);
294 }
295
296 #[doc(hidden)]
297 pub fn dispatch_log_message(&self, message: crate::debug_log::LogMessage<'_>) {
298 if let Some(handler) = self.0.log_message_handler.borrow().as_ref() {
299 handler(message);
300 } else {
301 self.0.platform.debug_log(message.message_arguments());
302 }
303 }
304
305 #[doc(hidden)]
306 pub fn set_log_message_handler(
307 &self,
308 handler: Option<crate::debug_log::LogMessageHandler>,
309 ) -> Option<crate::debug_log::LogMessageHandler> {
310 let mut slot = self.0.log_message_handler.borrow_mut();
311 core::mem::replace(&mut *slot, handler)
312 }
313
314 pub(crate) fn acquire_keepalive(&self) {
318 *self.0.window_count.borrow_mut() += 1;
319 }
320
321 pub(crate) fn release_keepalive(&self) {
325 let mut count = self.0.window_count.borrow_mut();
326 *count -= 1;
327 if *count <= 0 {
328 drop(count);
329 let _ = self.event_loop_proxy().and_then(|p| p.quit_event_loop().ok());
330 }
331 }
332
333 pub fn set_xdg_app_id(&self, _app_id: crate::SharedString) {
334 #[cfg(all(unix, not(target_os = "macos")))]
335 {
336 self.0.xdg_app_id.replace(Some(_app_id));
337 }
338 }
339
340 #[cfg(all(unix, not(target_os = "macos")))]
341 pub fn xdg_app_id(&self) -> Option<crate::SharedString> {
342 self.0.xdg_app_id.borrow().clone()
343 }
344
345 #[cfg(not(all(unix, not(target_os = "macos"))))]
346 pub fn xdg_app_id(&self) -> Option<crate::SharedString> {
347 None
348 }
349
350 pub fn locale_decimal_separator(&self) -> char {
352 self.0.as_ref().project_ref().locale_decimal_separator.get()
353 }
354
355 #[cfg(feature = "std")]
357 pub fn set_locale(&self, locale: &str) {
358 self.0
359 .as_ref()
360 .project_ref()
361 .locale_decimal_separator
362 .set(i_slint_common::decimal_separator_for_locale(locale));
363 }
364
365 #[cfg(feature = "tr")]
366 pub fn set_external_translator(&self, translator: Option<Box<dyn tr::Translator>>) {
367 *self.0.external_translator.borrow_mut() = translator;
368 self.0.as_ref().project_ref().translations_dirty.mark_dirty();
369 }
370
371 #[cfg(feature = "tr")]
372 pub fn external_translator(&self) -> Option<core::cell::Ref<'_, Box<dyn tr::Translator>>> {
373 core::cell::Ref::filter_map(self.0.external_translator.borrow(), |maybe_translator| {
374 maybe_translator.as_ref()
375 })
376 .ok()
377 }
378
379 pub fn downgrade(&self) -> SlintContextWeak {
382 SlintContextWeak(PinWeak::downgrade(self.0.clone()))
383 }
384}
385
386#[derive(Clone)]
391pub struct SlintContextWeak(PinWeak<SlintContextInner>);
392
393impl SlintContextWeak {
394 pub fn upgrade(&self) -> Option<SlintContext> {
396 self.0.upgrade().map(SlintContext)
397 }
398}
399
400pub fn with_global_context<R>(
404 factory: impl FnOnce() -> Result<Box<dyn Platform + 'static>, PlatformError>,
405 f: impl FnOnce(&SlintContext) -> R,
406) -> Result<R, PlatformError> {
407 GLOBAL_CONTEXT.with(|p| match p.get() {
408 Some(ctx) => Ok(f(ctx)),
409 None => {
410 if crate::platform::with_event_loop_proxy(|proxy| proxy.is_some()) {
411 return Err(PlatformError::SetPlatformError(
412 crate::platform::SetPlatformError::AlreadySet,
413 ));
414 }
415 crate::platform::set_platform(factory()?).map_err(PlatformError::SetPlatformError)?;
416 Ok(f(p.get().unwrap()))
417 }
418 })
419}
420
421pub fn set_window_shown_hook(
424 hook: Option<Box<dyn FnMut(&Rc<dyn crate::platform::WindowAdapter>)>>,
425) -> Result<Option<Box<dyn FnMut(&Rc<dyn crate::platform::WindowAdapter>)>>, PlatformError> {
426 GLOBAL_CONTEXT.with(|p| match p.get() {
427 Some(ctx) => Ok(ctx.0.window_shown_hook.replace(hook)),
428 None => Err(PlatformError::NoPlatform),
429 })
430}
431
432pub fn set_window_event_hook(
435 hook: Option<WindowEventHook>,
436) -> Result<Option<WindowEventHook>, PlatformError> {
437 GLOBAL_CONTEXT.with(|p| match p.get() {
438 Some(ctx) => {
439 let mut slot = ctx.0.window_event_hook.try_borrow_mut().map_err(|_| {
440 PlatformError::Other(alloc::string::String::from("event hook is currently in use"))
441 })?;
442 Ok(core::mem::replace(&mut *slot, hook))
443 }
444 None => Err(PlatformError::NoPlatform),
445 })
446}