1mod access;
4#[doc(hidden)]
5pub mod backend;
6mod draw;
7mod event;
8mod proxy;
9mod resource;
10#[cfg(feature = "tokio")]
11mod task;
12
13use log::debug;
14use skia_safe::{
15 FontMgr, svg,
16 textlayout::{FontCollection, TypefaceFontProvider},
17};
18use std::cell::RefCell;
19use std::collections::{BinaryHeap, VecDeque};
20use std::rc::Rc;
21use std::sync::Mutex;
22use std::sync::atomic::{AtomicU64, Ordering};
23use std::{
24 any::{Any, TypeId},
25 sync::Arc,
26};
27use vizia_id::IdManager;
28use vizia_window::WindowDescription;
29
30#[cfg(all(
31 feature = "clipboard",
32 not(all(
33 any(
34 target_os = "linux",
35 target_os = "dragonfly",
36 target_os = "freebsd",
37 target_os = "netbsd",
38 target_os = "openbsd"
39 ),
40 feature = "wayland",
41 ))
42))]
43use copypasta::ClipboardContext;
44#[cfg(feature = "clipboard")]
45use copypasta::{ClipboardProvider, nop_clipboard::NopClipboardContext};
46use hashbrown::{HashMap, HashSet, hash_map::Entry};
47
48pub use access::*;
49pub use draw::*;
50pub use event::*;
51pub use proxy::*;
52pub use resource::*;
53#[cfg(feature = "tokio")]
54pub use task::*;
55
56use crate::{
57 events::{TimedEvent, TimedEventHandle, TimerState, ViewHandler},
58 model::ModelData,
59};
60
61use crate::{
62 binding::BindingHandler,
63 resource::{LoadingStatus, StoredImage},
64};
65use crate::{cache::CachedData, resource::ImageOrSvg};
66
67use crate::prelude::*;
68use crate::resource::ResourceManager;
69use crate::text::TextContext;
70use vizia_input::{ImeState, MouseState};
71use vizia_storage::{ChildIterator, LayoutTreeIterator};
72
73#[cfg(feature = "tokio")]
74pub(crate) type TaskRuntime = Arc<tokio::runtime::Runtime>;
75
76static DEFAULT_LAYOUT: &str = include_str!("../../resources/themes/default_layout.css");
77static DEFAULT_THEME: &str = include_str!("../../resources/themes/default_theme.css");
78static MARKDOWN: &str = include_str!("../../resources/themes/markdown.css");
79static DEFAULT_TRANSLATION_EN_US: &str =
80 include_str!("../../resources/translations/en-US/core.ftl");
81
82type Views = HashMap<Entity, Box<dyn ViewHandler>>;
83type Models = HashMap<Entity, HashMap<TypeId, Box<dyn ModelData>>>;
84type Bindings = HashMap<Entity, Box<dyn BindingHandler>>;
85
86static NEXT_CONTEXT_ID: AtomicU64 = AtomicU64::new(1);
87
88#[derive(Clone, Copy, PartialEq, Eq, Hash)]
89pub(crate) struct SignalRebuild {
90 pub(crate) context_id: u64,
91 pub(crate) entity: Entity,
92}
93
94#[cfg(feature = "clipboard")]
95fn default_clipboard_provider() -> Box<dyn ClipboardProvider> {
96 #[cfg(all(
97 any(
98 target_os = "linux",
99 target_os = "dragonfly",
100 target_os = "freebsd",
101 target_os = "netbsd",
102 target_os = "openbsd"
103 ),
104 feature = "wayland"
105 ))]
106 {
107 Box::new(NopClipboardContext::new().unwrap())
108 }
109
110 #[cfg(not(all(
111 any(
112 target_os = "linux",
113 target_os = "dragonfly",
114 target_os = "freebsd",
115 target_os = "netbsd",
116 target_os = "openbsd"
117 ),
118 feature = "wayland",
119 )))]
120 {
121 if let Ok(context) = ClipboardContext::new() {
122 Box::new(context)
123 } else {
124 Box::new(NopClipboardContext::new().unwrap())
125 }
126 }
127}
128
129thread_local! {
130 pub(crate) static SIGNAL_REBUILDS: RefCell<HashSet<SignalRebuild>> = RefCell::new(HashSet::new());
133}
134
135#[derive(Default, Clone)]
136pub struct WindowState {
137 pub window_description: WindowDescription,
138 pub scale_factor: f32,
139 pub needs_relayout: bool,
140 pub needs_redraw: bool,
141 pub redraw_list: HashSet<Entity>,
142 pub dirty_rect: Option<BoundingBox>,
143 pub owner: Option<Entity>,
144 pub is_modal: bool,
145 pub should_close: bool,
146 pub content: Option<Arc<dyn Fn(&mut Context)>>,
147}
148
149pub struct Context {
151 pub(crate) context_id: u64,
152 pub(crate) entity_manager: IdManager<Entity>,
153 pub(crate) entity_identifiers: HashMap<String, Entity>,
154 pub tree: Tree<Entity>,
155 pub(crate) current: Entity,
156 pub(crate) views: Views,
157 pub(crate) models: Models,
158 pub(crate) bindings: Bindings,
159 pub(crate) event_queue: VecDeque<Event>,
160 pub(crate) event_schedule: BinaryHeap<TimedEvent>,
161 pub(crate) next_event_id: usize,
162 pub(crate) timers: Vec<TimerState>,
163 pub(crate) running_timers: BinaryHeap<TimerState>,
164 pub tree_updates: Vec<Option<accesskit::TreeUpdate>>,
165 pub(crate) listeners:
166 HashMap<Entity, Box<dyn Fn(&mut dyn ViewHandler, &mut EventContext, &mut Event)>>,
167 pub(crate) global_listeners: Vec<Box<dyn Fn(&mut EventContext, &mut Event)>>,
168 pub(crate) style: Style,
169 pub(crate) cache: CachedData,
170 pub windows: HashMap<Entity, WindowState>,
171
172 pub mouse: MouseState<Entity>,
173 pub(crate) modifiers: Modifiers,
174
175 pub(crate) captured: Entity,
176 pub(crate) triggered: Entity,
177 pub(crate) hovered: Entity,
178 pub(crate) drag_hovered: Entity,
179 pub(crate) focused: Entity,
180 pub(crate) focus_stack: Vec<Entity>,
181 pub(crate) cursor_icon_locked: bool,
182
183 pub(crate) resource_manager: ResourceManager,
184
185 pub text_context: TextContext,
186
187 #[cfg(feature = "tokio")]
188 pub(crate) task_runtime: TaskRuntime,
189 #[cfg(feature = "tokio")]
190 pub(crate) named_tasks: NamedTaskMap,
191
192 pub(crate) event_proxy: Option<Box<dyn EventProxy>>,
193
194 #[cfg(feature = "clipboard")]
195 pub(crate) clipboards: HashMap<Entity, Box<dyn ClipboardProvider>>,
196
197 pub(crate) click_time: Instant,
198 pub(crate) clicks: usize,
199 pub(crate) click_pos: (f32, f32),
200 pub(crate) click_button: MouseButton,
201
202 pub ignore_default_theme: bool,
203 built_in_translations_added: bool,
204 built_in_styles_added: bool,
205 pub window_has_focus: bool,
206 pub ime_state: ImeState,
207
208 pub(crate) drop_data: Option<DropData>,
209 pub(crate) active_drag_view: Option<Entity>,
210}
211
212impl Default for Context {
213 fn default() -> Self {
214 Context::new()
215 }
216}
217
218impl Context {
219 pub fn new() -> Self {
221 let mut cache = CachedData::default();
222 cache.add(Entity::root());
223
224 let mut result = Self {
225 context_id: NEXT_CONTEXT_ID.fetch_add(1, Ordering::Relaxed),
226 entity_manager: IdManager::new(),
227 entity_identifiers: HashMap::new(),
228 tree: Tree::new(),
229 current: Entity::root(),
230 views: HashMap::default(),
231 models: HashMap::default(),
232 bindings: HashMap::default(),
233 style: Style::default(),
234 cache,
235 windows: HashMap::new(),
236 event_queue: VecDeque::new(),
237 event_schedule: BinaryHeap::new(),
238 next_event_id: 0,
239 timers: Vec::new(),
240 running_timers: BinaryHeap::new(),
241 tree_updates: Vec::new(),
242 listeners: HashMap::default(),
243 global_listeners: Vec::new(),
244 mouse: MouseState::default(),
245 modifiers: Modifiers::empty(),
246 captured: Entity::null(),
247 triggered: Entity::null(),
248 hovered: Entity::root(),
249 drag_hovered: Entity::null(),
250 focused: Entity::root(),
251 focus_stack: Vec::new(),
252 cursor_icon_locked: false,
253 resource_manager: ResourceManager::new(),
254 text_context: {
255 let mut font_collection = FontCollection::new();
256
257 let default_font_manager = FontMgr::default();
258
259 let asset_provider = TypefaceFontProvider::new();
260
261 font_collection.set_default_font_manager(default_font_manager.clone(), None);
262 let asset_font_manager: FontMgr = asset_provider.clone().into();
263 font_collection.set_asset_font_manager(asset_font_manager);
264
265 TextContext {
266 font_collection,
267 default_font_manager,
268 asset_provider,
269 text_bounds: Default::default(),
270 text_paragraphs: Default::default(),
271 }
272 },
273 #[cfg(feature = "tokio")]
274 task_runtime: Self::new_task_runtime(),
275 #[cfg(feature = "tokio")]
276 named_tasks: new_named_task_map(),
277
278 event_proxy: None,
279
280 #[cfg(feature = "clipboard")]
281 clipboards: HashMap::new(),
282 click_time: Instant::now(),
283 clicks: 0,
284 click_pos: (0.0, 0.0),
285 click_button: MouseButton::Left,
286
287 ignore_default_theme: false,
288 built_in_translations_added: false,
289 built_in_styles_added: false,
290 window_has_focus: true,
291
292 ime_state: Default::default(),
293
294 drop_data: None,
295 active_drag_view: None,
296 };
297
298 result.tree.set_window(Entity::root(), true);
299
300 result.style.needs_restyle(Entity::root());
301 result.style.needs_relayout(Entity::root());
302 result.style.needs_retransform(Entity::root());
303 result.style.needs_reclip(Entity::root());
304 result.needs_redraw(Entity::root());
305
306 result.style.dpi_factor = 1.0;
308
309 Environment::new(&mut result).build(&mut result);
311
312 result.entity_manager.create();
313
314 result.style.role.insert(Entity::root(), Role::Window);
315
316 result
317 }
318
319 #[cfg(feature = "tokio")]
320 fn new_task_runtime() -> TaskRuntime {
321 Arc::new(
322 tokio::runtime::Builder::new_multi_thread()
323 .enable_all()
324 .build()
325 .expect("failed to build context task runtime"),
326 )
327 }
328
329 pub fn current(&self) -> Entity {
332 self.current
333 }
334
335 pub fn with_current<T>(&mut self, current: Entity, f: impl FnOnce(&mut Context) -> T) -> T {
337 let previous = self.current;
338 self.current = current;
339 let ret = f(self);
340 self.current = previous;
341 ret
342 }
343
344 pub fn environment(&self) -> &Environment {
346 self.data::<Environment>()
347 }
348
349 pub fn parent_window(&self) -> Entity {
351 self.tree.get_parent_window(self.current).unwrap_or(Entity::root())
352 }
353
354 pub fn scale_factor(&self) -> f32 {
356 self.style.dpi_factor as f32
357 }
358
359 pub fn needs_redraw(&mut self, entity: Entity) {
361 if self.entity_manager.is_alive(entity) {
362 let window = if self.tree.is_window(entity) {
365 entity
366 } else {
367 self.tree.get_parent_window(entity).unwrap_or(Entity::root())
368 };
369 if let Some(window_state) = self.windows.get_mut(&window) {
370 window_state.redraw_list.insert(entity);
371 }
372 }
373 }
374
375 pub fn needs_restyle(&mut self, entity: Entity) {
377 if entity == Entity::null() || self.style.restyle.contains(&entity) {
378 return;
379 }
380 self.style.restyle.insert(entity);
381 let iter = if let Some(parent) = self.tree.get_layout_parent(entity) {
382 LayoutTreeIterator::subtree(&self.tree, parent)
383 } else {
384 LayoutTreeIterator::subtree(&self.tree, entity)
385 };
386
387 for descendant in iter {
388 self.style.restyle.insert(descendant);
389 }
390 }
392
393 pub fn needs_retransform(&mut self, entity: Entity) {
394 self.style.needs_retransform(entity);
395 let iter = LayoutTreeIterator::subtree(&self.tree, entity);
396 for descendant in iter {
397 self.style.needs_retransform(descendant);
398 }
399 }
400
401 pub fn needs_reclip(&mut self, entity: Entity) {
402 self.style.needs_reclip(entity);
403 let iter = LayoutTreeIterator::subtree(&self.tree, entity);
404 for descendant in iter {
405 self.style.needs_reclip(descendant);
406 }
407 }
408
409 pub fn needs_relayout(&mut self) {
411 self.style.needs_relayout(Entity::root());
412 }
413
414 pub(crate) fn set_system_flags(&mut self, entity: Entity, system_flags: SystemFlags) {
415 if system_flags.contains(SystemFlags::RELAYOUT) {
416 self.style.needs_relayout(entity);
417 }
418
419 if system_flags.contains(SystemFlags::RESTYLE) {
420 self.needs_restyle(entity);
421 }
422
423 if system_flags.contains(SystemFlags::REDRAW) {
424 self.needs_redraw(entity);
425 }
426
427 if system_flags.contains(SystemFlags::REFLOW) {
428 self.style.needs_text_update(entity);
429 }
430
431 if system_flags.contains(SystemFlags::RETRANSFORM) {
432 self.needs_retransform(entity);
433 }
434
435 if system_flags.contains(SystemFlags::RECLIP) {
436 self.needs_reclip(entity);
437 }
438
439 if system_flags.contains(SystemFlags::REACCESS) {
440 self.style.needs_access_update(entity);
441 }
442 }
443
444 pub(crate) fn set_focus_pseudo_classes(
446 &mut self,
447 focused: Entity,
448 enabled: bool,
449 focus_visible: bool,
450 ) {
451 if enabled {
452 debug!(
453 "Focus changed to {:?} parent: {:?}, view: {}, posx: {}, posy: {} width: {} height: {}",
454 focused,
455 self.tree.get_parent(focused),
456 self.views
457 .get(&focused)
458 .map_or("<None>", |view| view.element().unwrap_or("<Unnamed>")),
459 self.cache.get_posx(focused),
460 self.cache.get_posy(focused),
461 self.cache.get_width(focused),
462 self.cache.get_height(focused),
463 );
464 }
465
466 if let Some(pseudo_classes) = self.style.pseudo_classes.get_mut(focused) {
467 pseudo_classes.set(PseudoClassFlags::FOCUS, enabled);
468 if !enabled || focus_visible {
469 pseudo_classes.set(PseudoClassFlags::FOCUS_VISIBLE, enabled);
470 self.style.needs_access_update(focused);
471 self.needs_restyle(focused);
472 }
473 }
474
475 let ancestors = focused.parent_iter(&self.tree).collect::<Vec<_>>();
476 for entity in ancestors {
477 if let Some(pseudo_classes) = self.style.pseudo_classes.get_mut(entity) {
478 pseudo_classes.set(PseudoClassFlags::FOCUS_WITHIN, enabled);
479 }
480 self.needs_restyle(entity);
481 }
482 }
483
484 pub fn focus_with_visibility(&mut self, focus_visible: bool) {
486 let focusable = self.current == Entity::root()
487 || self
488 .style
489 .abilities
490 .get(self.current)
491 .is_some_and(|abilities| abilities.contains(Abilities::FOCUSABLE));
492 if !focusable {
493 return;
494 }
495
496 let old_focus = self.focused;
497 let new_focus = self.current;
498 self.set_focus_pseudo_classes(old_focus, false, focus_visible);
499 if self.current != self.focused {
500 self.emit_to(old_focus, WindowEvent::FocusOut);
501 self.emit_to(new_focus, WindowEvent::FocusIn);
502 self.focused = self.current;
503 }
504 self.set_focus_pseudo_classes(new_focus, true, focus_visible);
505
506 self.emit_custom(Event::new(WindowEvent::FocusVisibility(focus_visible)).target(old_focus));
507 self.emit_custom(Event::new(WindowEvent::FocusVisibility(focus_visible)).target(new_focus));
508
509 self.needs_restyle(self.focused);
510 self.needs_restyle(self.current);
511 self.style.needs_access_update(self.focused);
512 self.style.needs_access_update(self.current);
513 }
514
515 pub fn focus(&mut self) {
517 let focused = self.focused;
518 let old_focus_visible = self
519 .style
520 .pseudo_classes
521 .get_mut(focused)
522 .filter(|class| class.contains(PseudoClassFlags::FOCUS_VISIBLE))
523 .is_some();
524 self.focus_with_visibility(old_focus_visible)
525 }
526
527 pub(crate) fn remove_children(&mut self, entity: Entity) {
529 let child_iter = ChildIterator::new(&self.tree, entity);
530 let children = child_iter.collect::<Vec<_>>();
531 for child in children.into_iter() {
532 self.remove(child);
533 }
534 }
535
536 pub fn remove(&mut self, entity: Entity) {
538 let delete_list = entity.branch_iter(&self.tree).collect::<Vec<_>>();
539
540 if !delete_list.is_empty() {
541 self.style.needs_restyle(self.current);
542 let is_absolute = self.style.position_type.get(entity).copied().unwrap_or_default()
547 == PositionType::Absolute;
548 if !is_absolute {
549 if let Some(parent) = self.tree.get_layout_parent(entity) {
550 self.style.needs_relayout(parent);
551 } else {
552 self.style.needs_relayout(entity);
553 }
554 }
555 self.needs_redraw(self.current);
556 }
557
558 for entity in delete_list.iter().rev() {
559 if let Some(mut view) = self.views.remove(entity) {
560 view.event(
561 &mut EventContext::new_with_current(self, *entity),
562 &mut Event::new(WindowEvent::Destroyed).direct(*entity),
563 );
564
565 self.views.insert(*entity, view);
566 }
567
568 if let Some(binding) = self.bindings.remove(entity) {
569 binding.remove(self);
570
571 self.bindings.insert(*entity, binding);
572 }
573
574 for image in self.resource_manager.images.values_mut() {
575 image.observers.remove(entity);
577 }
578
579 if let Some(identifier) = self.style.ids.get(*entity) {
580 self.entity_identifiers.remove(identifier);
581 }
582
583 if let Some(index) = self.focus_stack.iter().position(|r| r == entity) {
584 self.focus_stack.remove(index);
585 }
586
587 if self.focused == *entity {
588 if let Some(new_focus) = self.focus_stack.pop() {
589 self.with_current(new_focus, |cx| cx.focus());
590 } else {
591 self.with_current(Entity::root(), |cx| cx.focus());
592 }
593 }
594
595 if self.captured == *entity {
596 self.captured = Entity::null();
597 }
598
599 if let Some(parent) = self.tree.get_layout_parent(*entity) {
600 self.style.needs_access_update(parent);
601 }
602
603 let mut stopped_timers = Vec::new();
604
605 for timer in self.running_timers.iter() {
606 if timer.entity == *entity {
607 stopped_timers.push(timer.id);
608 }
609 }
610
611 for timer in stopped_timers {
612 self.stop_timer(timer);
613 }
614
615 let window_entity = self.tree.get_parent_window(*entity).unwrap_or(Entity::root());
616
617 if !self.tree.is_window(*entity) {
618 if let Some(draw_bounds) = self.cache.draw_bounds.get(*entity) {
619 if let Some(dirty_rect) =
620 &mut self.windows.get_mut(&window_entity).unwrap().dirty_rect
621 {
622 *dirty_rect = dirty_rect.union(draw_bounds);
623 } else {
624 self.windows.get_mut(&window_entity).unwrap().dirty_rect =
625 Some(*draw_bounds);
626 }
627 }
628 }
629
630 self.windows.get_mut(&window_entity).unwrap().redraw_list.remove(entity);
631
632 if self.windows.contains_key(entity) {
633 self.windows.remove(entity);
634 #[cfg(feature = "clipboard")]
635 self.clipboards.remove(entity);
636 }
637
638 self.tree.remove(*entity).expect("");
639 self.cache.remove(*entity);
640 self.style.remove(*entity);
641 self.models.remove(entity);
642 self.views.remove(entity);
643 self.text_context.text_bounds.remove(*entity);
644 self.text_context.text_paragraphs.remove(*entity);
645 self.entity_manager.destroy(*entity);
646 }
647 }
648
649 pub fn toggle_class(&mut self, name: &str, applied: impl Res<bool>) {
651 let name = name.to_owned();
652 let entity = self.current();
653 let current = self.current();
654 self.with_current(current, |cx| {
655 applied.set_or_bind(cx, move |cx, applied| {
656 let applied = applied.get_value(cx);
657 if let Some(class_list) = cx.style.classes.get_mut(entity) {
658 if applied {
659 class_list.insert(name.clone());
660 } else {
661 class_list.remove(&name);
662 }
663 }
664
665 cx.needs_restyle(entity);
666 });
667 });
668 }
669
670 pub fn add_listener<F, W>(&mut self, listener: F)
676 where
677 W: View,
678 F: 'static + Fn(&mut W, &mut EventContext, &mut Event),
679 {
680 self.listeners.insert(
681 self.current,
682 Box::new(move |event_handler, context, event| {
683 if let Some(widget) = event_handler.downcast_mut::<W>() {
684 (listener)(widget, context, event);
685 }
686 }),
687 );
688 }
689
690 pub fn add_global_listener<F>(&mut self, listener: F)
696 where
697 F: 'static + Fn(&mut EventContext, &mut Event),
698 {
699 self.global_listeners.push(Box::new(listener));
700 }
701
702 pub fn load_font_mem(&mut self, data: impl AsRef<[u8]>) {
704 self.text_context.asset_provider.register_typeface(
705 self.text_context.default_font_manager.new_from_data(data.as_ref(), None).unwrap(),
706 None,
707 );
708 }
709
710 fn request_resource_if_not_loaded(
711 &mut self,
712 path: String,
713 request: crate::resource::ResourceRequest,
714 options: crate::resource::ResourceLoadOptions,
715 ) {
716 if self.resource_manager.resource_status(&path) != LoadingStatus::NotLoaded {
718 return;
719 }
720
721 self.resource_manager.queue_resource_request(request, options);
722 }
723
724 pub fn resource_status_signal(&mut self, path: impl Into<String>) -> Signal<LoadingStatus> {
728 let path = path.into();
729
730 if let Some(signal) = self.resource_manager.loading_status.get(&path).copied() {
731 return signal;
732 }
733
734 let status = self.resource_manager.resource_status(&path);
735 let signal = Signal::new(status);
736 self.resource_manager.loading_status.insert(path, signal);
737 signal
738 }
739
740 pub fn resource_status(&mut self, path: impl Into<String>) -> LoadingStatus {
744 self.resource_status_signal(path).get()
745 }
746
747 pub fn add_font(
751 &mut self,
752 path: impl Into<String>,
753 options: Option<crate::resource::ResourceLoadOptions>,
754 ) {
755 let path = path.into();
756 let options = options.unwrap_or_default();
757
758 self.request_resource_if_not_loaded(
759 path.clone(),
760 crate::resource::ResourceRequest::Font(crate::resource::FontRequest { path }),
761 options,
762 );
763 }
764
765 pub fn add_image(
770 &mut self,
771 name: impl Into<String>,
772 path: impl Into<String>,
773 policy: ImageRetentionPolicy,
774 options: Option<crate::resource::ResourceLoadOptions>,
775 ) -> Signal<LoadingStatus> {
776 let name = name.into();
777 let path = path.into();
778 let options = options.unwrap_or_default();
779
780 self.resource_manager.register_image_source(name.clone(), path.clone());
781
782 if self.resource_manager.resource_status(&path) != crate::resource::LoadingStatus::NotLoaded
786 {
787 let existing_id = self
788 .resource_manager
789 .image_sources
790 .iter()
791 .find(|(alias, alias_path)| {
792 *alias_path == &path && self.resource_manager.image_ids.contains_key(*alias)
793 })
794 .and_then(|(alias, _)| self.resource_manager.image_ids.get(alias).copied());
795 if let Some(id) = existing_id {
796 self.resource_manager.image_ids.entry(name.clone()).or_insert(id);
797 }
798 }
801
802 self.request_resource_if_not_loaded(
803 path.clone(),
804 crate::resource::ResourceRequest::Image(crate::resource::ImageRequest {
805 name,
806 path: path.clone(),
807 policy,
808 }),
809 options,
810 );
811
812 self.resource_status_signal(path)
813 }
814
815 pub fn add_translation_file(
821 &mut self,
822 lang: LanguageIdentifier,
823 path: impl Into<String>,
824 options: Option<crate::resource::ResourceLoadOptions>,
825 ) {
826 let path = path.into();
827 let options = options.unwrap_or_default();
828
829 self.request_resource_if_not_loaded(
830 path.clone(),
831 crate::resource::ResourceRequest::Translation(crate::resource::TranslationRequest {
832 lang,
833 path,
834 }),
835 options,
836 );
837 }
838
839 pub fn focused_element(&self) -> Option<&'static str> {
847 self.views.get(&self.focused).and_then(|view| view.element())
848 }
849
850 pub fn add_stylesheet(&mut self, style: impl IntoCssStr) -> Result<(), std::io::Error> {
851 self.resource_manager.styles.push(Box::new(style));
852
853 EventContext::new(self).reload_styles().expect("Failed to reload styles");
854
855 Ok(())
856 }
857
858 pub fn add_built_in_styles(&mut self) {
860 self.add_built_in_translations();
861
862 let user_styles = if self.built_in_styles_added {
863 if self.resource_manager.styles.len() >= 3 {
864 self.resource_manager.styles.drain(3..).collect::<Vec<_>>()
865 } else {
866 Vec::new()
867 }
868 } else {
869 self.resource_manager.styles.drain(..).collect::<Vec<_>>()
870 };
871
872 self.resource_manager.styles.clear();
873
874 self.resource_manager.styles.push(Box::new(DEFAULT_LAYOUT));
875 self.resource_manager.styles.push(Box::new(MARKDOWN));
876
877 if !self.ignore_default_theme {
878 self.resource_manager.styles.push(Box::new(DEFAULT_THEME));
879 let environment = self.data::<Environment>();
880 let theme_mode = environment.effective_theme();
881 let direction = environment.direction.get();
882 self.with_current(Entity::root(), |cx| {
883 let cx = &mut EventContext::new(cx);
884 cx.toggle_class("dark", theme_mode == ThemeMode::DarkMode);
885 cx.toggle_class("rtl", direction == Direction::RightToLeft);
886 })
887 } else {
888 self.resource_manager.styles.push(Box::new(""));
890 }
891
892 self.resource_manager.styles.extend(user_styles);
893 self.built_in_styles_added = true;
894
895 EventContext::new(self).reload_styles().unwrap();
896 }
897
898 pub fn add_built_in_translations(&mut self) {
900 if self.built_in_translations_added {
901 return;
902 }
903
904 self.load_translation("en-US".parse().unwrap(), DEFAULT_TRANSLATION_EN_US)
905 .expect("Failed to load built-in en-US translation resources");
906 self.built_in_translations_added = true;
907 }
908
909 pub fn add_animation(&mut self, animation: AnimationBuilder) -> Animation {
910 self.style.add_animation(animation)
911 }
912
913 pub fn add_resource_loader<L: crate::resource::ResourceLoader>(&mut self, loader: L) {
919 self.resource_manager.resource_loaders.insert(0, Box::new(loader));
920 }
921
922 pub fn load_translation(
926 &mut self,
927 lang: LanguageIdentifier,
928 ftl: impl ToString,
929 ) -> Result<(), crate::resource::TranslationError> {
930 self.resource_manager.add_translation(lang, ftl.to_string())
931 }
932
933 pub fn add_timer(
964 &mut self,
965 interval: Duration,
966 duration: Option<Duration>,
967 callback: impl Fn(&mut EventContext, TimerAction) + 'static,
968 ) -> Timer {
969 let id = Timer(self.timers.len());
970 self.timers.push(TimerState {
971 entity: Entity::root(),
972 id,
973 time: Instant::now(),
974 interval,
975 duration,
976 start_time: Instant::now(),
977 callback: Rc::new(callback),
978 ticking: false,
979 stopping: false,
980 });
981
982 id
983 }
984
985 pub fn start_timer(&mut self, timer: Timer) {
989 let current = self.current;
990 if !self.timer_is_running(timer) {
991 let timer_state = self.timers[timer.0].clone();
992 self.running_timers.push(timer_state);
994 }
995
996 self.modify_timer(timer, |timer_state| {
997 let now = Instant::now();
998 timer_state.start_time = now;
999 timer_state.time = now;
1000 timer_state.entity = current;
1001 timer_state.ticking = false;
1002 timer_state.stopping = false;
1003 });
1004 }
1005
1006 pub fn modify_timer(&mut self, timer: Timer, timer_function: impl Fn(&mut TimerState)) {
1008 let mut running_timers = self.running_timers.clone().into_vec();
1009
1010 if let Some(timer_state) =
1011 running_timers.iter_mut().find(|timer_state| timer_state.id == timer)
1012 {
1013 (timer_function)(timer_state);
1014 self.running_timers = running_timers.into();
1015 return;
1016 }
1017
1018 for pending_timer in self.timers.iter_mut() {
1019 if pending_timer.id == timer {
1020 (timer_function)(pending_timer);
1021 }
1022 }
1023 }
1024
1025 pub fn timer_is_running(&mut self, timer: Timer) -> bool {
1027 for timer_state in self.running_timers.iter() {
1028 if timer_state.id == timer {
1029 return true;
1030 }
1031 }
1032
1033 false
1034 }
1035
1036 pub fn stop_timer(&mut self, timer: Timer) {
1040 let mut running_timers = self.running_timers.clone();
1041
1042 for timer_state in running_timers.iter() {
1043 if timer_state.id == timer {
1044 (timer_state.callback)(
1045 &mut EventContext::new_with_current(self, timer_state.entity),
1046 TimerAction::Stop,
1047 );
1048 }
1049 }
1050
1051 self.running_timers =
1052 running_timers.drain().filter(|timer_state| timer_state.id != timer).collect();
1053 }
1054
1055 pub(crate) fn tick_timers(&mut self) {
1057 let now = Instant::now();
1058 while let Some(next_timer_state) = self.running_timers.peek() {
1059 if next_timer_state.time <= now {
1060 let mut timer_state = self.running_timers.pop().unwrap();
1061
1062 if timer_state.end_time().unwrap_or_else(|| now + Duration::from_secs(1)) >= now {
1063 if !timer_state.ticking {
1064 (timer_state.callback)(
1065 &mut EventContext::new_with_current(self, timer_state.entity),
1066 TimerAction::Start,
1067 );
1068 timer_state.ticking = true;
1069 } else {
1070 (timer_state.callback)(
1071 &mut EventContext::new_with_current(self, timer_state.entity),
1072 TimerAction::Tick(now - timer_state.time),
1073 );
1074 }
1075 timer_state.time = now + timer_state.interval - (now - timer_state.time);
1076 self.running_timers.push(timer_state);
1077 } else {
1078 (timer_state.callback)(
1079 &mut EventContext::new_with_current(self, timer_state.entity),
1080 TimerAction::Stop,
1081 );
1082 }
1083 } else {
1084 break;
1085 }
1086 }
1087 }
1088
1089 pub fn load_image(&mut self, path: &str, data: &'static [u8], policy: ImageRetentionPolicy) {
1091 let id = if let Some(image_id) = self.resource_manager.image_ids.get(path) {
1092 *image_id
1093 } else {
1094 let id = self.resource_manager.image_id_manager.create();
1095 self.resource_manager.image_ids.insert(path.to_owned(), id);
1096 id
1097 };
1098
1099 if let Some(image) =
1100 skia_safe::Image::from_encoded(unsafe { skia_safe::Data::new_bytes(data) })
1101 {
1102 match self.resource_manager.images.entry(id) {
1103 Entry::Occupied(mut occ) => {
1104 occ.get_mut().image = ImageOrSvg::Image(image);
1105 occ.get_mut().dirty = true;
1106 occ.get_mut().retention_policy = policy;
1107 }
1108 Entry::Vacant(vac) => {
1109 vac.insert(StoredImage {
1110 image: ImageOrSvg::Image(image),
1111 retention_policy: policy,
1112 used: true,
1113 dirty: false,
1114 observers: HashSet::new(),
1115 });
1116 }
1117 }
1118 let observers: Vec<Entity> = self
1119 .resource_manager
1120 .images
1121 .get(&id)
1122 .map(|img| img.observers.iter().copied().collect())
1123 .unwrap_or_default();
1124 for observer in observers {
1125 self.style.needs_relayout(observer);
1126 self.needs_redraw(observer);
1127 }
1128 self.style.needs_relayout(self.current);
1129 self.needs_redraw(self.current);
1130 }
1131 }
1132
1133 pub fn load_svg(&mut self, path: &str, data: &[u8], policy: ImageRetentionPolicy) -> ImageId {
1134 let id = if let Some(image_id) = self.resource_manager.image_ids.get(path) {
1135 return *image_id;
1136 } else {
1137 let id = self.resource_manager.image_id_manager.create();
1138 self.resource_manager.image_ids.insert(path.to_owned(), id);
1139 id
1140 };
1141
1142 if let Ok(svg) = svg::Dom::from_bytes(data, self.text_context.default_font_manager.clone())
1143 {
1144 match self.resource_manager.images.entry(id) {
1145 Entry::Occupied(mut occ) => {
1146 occ.get_mut().image = ImageOrSvg::Svg(svg);
1147 occ.get_mut().dirty = true;
1148 occ.get_mut().retention_policy = policy;
1149 }
1150 Entry::Vacant(vac) => {
1151 vac.insert(StoredImage {
1152 image: ImageOrSvg::Svg(svg),
1153 retention_policy: policy,
1154 used: true,
1155 dirty: false,
1156 observers: HashSet::new(),
1157 });
1158 }
1159 }
1160 let observers: Vec<Entity> = self
1164 .resource_manager
1165 .images
1166 .get(&id)
1167 .map(|img| img.observers.iter().copied().collect())
1168 .unwrap_or_default();
1169 for observer in observers {
1170 self.style.needs_relayout(observer);
1171 self.needs_redraw(observer);
1172 }
1173 self.style.needs_relayout(self.current);
1174 self.needs_redraw(self.current);
1175 }
1176
1177 id
1178 }
1179
1180 pub fn spawn<F>(&self, target: F)
1181 where
1182 F: 'static + Send + FnOnce(&mut ContextProxy),
1183 {
1184 let mut cxp = ContextProxy {
1185 current: self.current,
1186 event_proxy: self.event_proxy.as_ref().map(|p| p.make_clone()),
1187 };
1188
1189 std::thread::spawn(move || target(&mut cxp));
1190 }
1191
1192 pub fn get_proxy(&self) -> ContextProxy {
1193 ContextProxy {
1194 current: self.current,
1195 event_proxy: self.event_proxy.as_ref().map(|p| p.make_clone()),
1196 }
1197 }
1198
1199 #[cfg(feature = "tokio")]
1200 pub fn add_task<T, E>(&self, task: TaskBuilder<T, E>) -> TaskHandle
1228 where
1229 T: Send + 'static,
1230 E: Send + 'static,
1231 {
1232 task.add_to_context(self)
1233 }
1234
1235 pub(crate) fn resolve_entity_identifier(&self, identity: &str) -> Option<Entity> {
1237 self.entity_identifiers.get(identity).cloned()
1238 }
1239
1240 pub fn set_ime_state(&mut self, new_state: ImeState) {
1241 self.ime_state = new_state;
1242 }
1243}
1244
1245pub(crate) enum InternalEvent {
1246 Redraw,
1247 LoadImage { path: String, image: Mutex<Option<skia_safe::Image>>, policy: ImageRetentionPolicy },
1248 LoadSvg { path: String, data: Vec<u8>, policy: ImageRetentionPolicy },
1249 LoadFont { path: String, data: Vec<u8> },
1250 LoadTranslation { lang: LanguageIdentifier, path: String, ftl: String },
1251 UpdateResourceStatus { path: String, status: LoadingStatus },
1252}
1253
1254pub struct LocalizationContext<'a> {
1255 pub(crate) current: Entity,
1256 pub(crate) resource_manager: &'a ResourceManager,
1257 pub(crate) models: &'a Models,
1258 pub(crate) views: &'a Views,
1259 pub(crate) tree: &'a Tree<Entity>,
1260}
1261
1262impl<'a> LocalizationContext<'a> {
1263 pub(crate) fn from_context(cx: &'a Context) -> Self {
1264 Self {
1265 current: cx.current,
1266 resource_manager: &cx.resource_manager,
1267 models: &cx.models,
1268 views: &cx.views,
1269 tree: &cx.tree,
1270 }
1271 }
1272
1273 pub(crate) fn from_event_context(cx: &'a EventContext) -> Self {
1274 Self {
1275 current: cx.current,
1276 resource_manager: cx.resource_manager,
1277 models: cx.models,
1278 views: cx.views,
1279 tree: cx.tree,
1280 }
1281 }
1282
1283 pub(crate) fn environment(&self) -> &Environment {
1284 self.data::<Environment>()
1285 }
1286}
1287
1288pub trait DataContext {
1292 fn try_data<T: 'static>(&self) -> Option<&T>;
1294
1295 fn data<T: 'static>(&self) -> &T {
1297 self.try_data::<T>().expect("data not found in context")
1298 }
1299
1300 fn localization_context(&self) -> Option<LocalizationContext<'_>> {
1302 None
1303 }
1304}
1305
1306pub trait EmitContext {
1308 fn emit<M: Any>(&mut self, message: M);
1319
1320 fn emit_to<M: Any>(&mut self, target: Entity, message: M);
1331
1332 fn emit_custom(&mut self, event: Event);
1348
1349 fn schedule_emit<M: Any>(&mut self, message: M, at: Instant) -> TimedEventHandle;
1363
1364 fn schedule_emit_to<M: Any>(
1378 &mut self,
1379 target: Entity,
1380 message: M,
1381 at: Instant,
1382 ) -> TimedEventHandle;
1383
1384 fn schedule_emit_custom(&mut self, event: Event, at: Instant) -> TimedEventHandle;
1404
1405 fn cancel_scheduled(&mut self, handle: TimedEventHandle);
1417}
1418
1419impl DataContext for Context {
1420 fn try_data<T: 'static>(&self) -> Option<&T> {
1421 if let Some(t) = <dyn Any>::downcast_ref::<T>(&()) {
1423 return Some(t);
1424 }
1425
1426 for entity in self.current.parent_iter(&self.tree) {
1427 if let Some(models) = self.models.get(&entity) {
1429 if let Some(model) = models.get(&TypeId::of::<T>()) {
1430 return model.downcast_ref::<T>();
1431 }
1432 }
1433
1434 if let Some(view_handler) = self.views.get(&entity) {
1436 if let Some(data) = view_handler.downcast_ref::<T>() {
1437 return Some(data);
1438 }
1439 }
1440 }
1441
1442 None
1443 }
1444
1445 fn localization_context(&self) -> Option<LocalizationContext<'_>> {
1446 Some(LocalizationContext::from_context(self))
1447 }
1448}
1449
1450impl DataContext for LocalizationContext<'_> {
1451 fn try_data<T: 'static>(&self) -> Option<&T> {
1452 if let Some(t) = <dyn Any>::downcast_ref::<T>(&()) {
1454 return Some(t);
1455 }
1456
1457 for entity in self.current.parent_iter(self.tree) {
1458 if let Some(models) = self.models.get(&entity) {
1460 if let Some(model) = models.get(&TypeId::of::<T>()) {
1461 return model.downcast_ref::<T>();
1462 }
1463 }
1464
1465 if let Some(view_handler) = self.views.get(&entity) {
1467 if let Some(data) = view_handler.downcast_ref::<T>() {
1468 return Some(data);
1469 }
1470 }
1471 }
1472
1473 None
1474 }
1475}
1476
1477impl EmitContext for Context {
1478 fn emit<M: Any>(&mut self, message: M) {
1479 self.event_queue.push_back(
1480 Event::new(message)
1481 .target(self.current)
1482 .origin(self.current)
1483 .propagate(Propagation::Up),
1484 );
1485 }
1486
1487 fn emit_to<M: Any>(&mut self, target: Entity, message: M) {
1488 self.event_queue.push_back(
1489 Event::new(message).target(target).origin(self.current).propagate(Propagation::Direct),
1490 );
1491 }
1492
1493 fn emit_custom(&mut self, event: Event) {
1494 self.event_queue.push_back(event);
1495 }
1496
1497 fn schedule_emit<M: Any>(&mut self, message: M, at: Instant) -> TimedEventHandle {
1498 self.schedule_emit_custom(
1499 Event::new(message)
1500 .target(self.current)
1501 .origin(self.current)
1502 .propagate(Propagation::Up),
1503 at,
1504 )
1505 }
1506
1507 fn schedule_emit_to<M: Any>(
1508 &mut self,
1509 target: Entity,
1510 message: M,
1511 at: Instant,
1512 ) -> TimedEventHandle {
1513 self.schedule_emit_custom(
1514 Event::new(message).target(target).origin(self.current).propagate(Propagation::Direct),
1515 at,
1516 )
1517 }
1518
1519 fn schedule_emit_custom(&mut self, event: Event, at: Instant) -> TimedEventHandle {
1520 let handle = TimedEventHandle(self.next_event_id);
1521 self.event_schedule.push(TimedEvent { event, time: at, ident: handle });
1522 self.next_event_id += 1;
1523 handle
1524 }
1525
1526 fn cancel_scheduled(&mut self, handle: TimedEventHandle) {
1527 self.event_schedule =
1528 self.event_schedule.drain().filter(|item| item.ident != handle).collect();
1529 }
1530}