1use std::{
12 collections::{BTreeMap, BTreeSet},
13 sync::{
14 atomic::{AtomicUsize, Ordering},
15 Arc,
16 },
17};
18
19use azul_core::{
20 animation::UpdateImageType,
21 callbacks::{FocusTarget, HidpiAdjustedBounds, IFrameCallbackReason, Update},
22 dom::{
23 AccessibilityAction, AttributeType, Dom, DomId, DomIdVec, DomNodeId, NodeId, NodeType, On,
24 },
25 events::{EasingFunction, EventFilter, FocusEventFilter, HoverEventFilter},
26 geom::{LogicalPosition, LogicalRect, LogicalSize, OptionLogicalPosition},
27 gl::OptionGlContextPtr,
28 gpu::{GpuScrollbarOpacityEvent, GpuValueCache},
29 hit_test::{DocumentId, ScrollPosition, ScrollbarHitId},
30 refany::{OptionRefAny, RefAny},
31 resources::{
32 Epoch, FontKey, GlTextureCache, IdNamespace, ImageCache, ImageMask, ImageRef, ImageRefHash,
33 OpacityKey, RendererResources,
34 },
35 selection::{
36 CursorAffinity, GraphemeClusterId, Selection, SelectionAnchor, SelectionFocus,
37 SelectionRange, SelectionState, TextCursor, TextSelection,
38 },
39 styled_dom::{
40 collect_nodes_in_document_order, is_before_in_document_order, NodeHierarchyItemId,
41 StyledDom,
42 },
43 task::{
44 Duration, Instant, SystemTickDiff, SystemTimeDiff, TerminateTimer, ThreadId, ThreadIdVec,
45 ThreadSendMsg, TimerId, TimerIdVec,
46 },
47 window::{CursorPosition, MonitorVec, RawWindowHandle, RendererType},
48 FastBTreeSet, FastHashMap,
49};
50use azul_css::{
51 css::Css,
52 props::{
53 basic::FontRef,
54 property::{CssProperty, CssPropertyVec},
55 },
56 AzString, LayoutDebugMessage, OptionString,
57};
58use rust_fontconfig::FcFontCache;
59
60#[cfg(feature = "icu")]
61use crate::icu::IcuLocalizerHandle;
62use crate::{
63 callbacks::{
64 CallCallbacksResult, Callback, ExternalSystemCallbacks, FocusUpdateRequest, MenuCallback,
65 },
66 managers::{
67 gpu_state::GpuStateManager,
68 iframe::IFrameManager,
69 scroll_state::{ScrollManager, ScrollStates},
70 },
71 solver3::{
72 self, cache::LayoutCache as Solver3LayoutCache, display_list::DisplayList,
73 layout_tree::LayoutTree,
74 },
75 text3::{
76 cache::{
77 FontManager, FontSelector, FontStyle, InlineContent, LayoutCache as TextLayoutCache,
78 LayoutError, ShapedItem, StyleProperties, StyledRun, TextBoundary, UnifiedConstraints,
79 UnifiedLayout,
80 },
81 default::PathLoader,
82 },
83 thread::{OptionThreadReceiveMsg, Thread, ThreadReceiveMsg, ThreadWriteBackMsg},
84 timer::Timer,
85 window_state::{FullWindowState, WindowCreateOptions},
86};
87
88static DOCUMENT_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
90static ID_NAMESPACE_COUNTER: AtomicUsize = AtomicUsize::new(0);
91
92fn new_document_id() -> DocumentId {
94 let namespace_id = new_id_namespace();
95 let id = DOCUMENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed) as u32;
96 DocumentId { namespace_id, id }
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, Eq)]
101pub enum CursorNavigationDirection {
102 Up,
104 Down,
106 Left,
108 Right,
110 LineStart,
112 LineEnd,
114 DocumentStart,
116 DocumentEnd,
118}
119
120#[derive(Debug, Clone)]
122pub enum CursorMovementResult {
123 MovedWithinNode(TextCursor),
125 MovedToNode {
127 dom_id: DomId,
128 node_id: NodeId,
129 cursor: TextCursor,
130 },
131 AtBoundary {
133 boundary: TextBoundary,
134 cursor: TextCursor,
135 },
136}
137
138#[derive(Debug, Clone)]
140pub struct NoCursorDestination {
141 pub reason: String,
142}
143
144#[derive(Debug, Clone)]
149pub enum CursorBlinkTimerAction {
150 Start(crate::timer::Timer),
152 Stop,
154 NoChange,
156}
157
158fn new_id_namespace() -> IdNamespace {
160 let id = ID_NAMESPACE_COUNTER.fetch_add(1, Ordering::Relaxed) as u32;
161 IdNamespace(id)
162}
163
164extern "C" fn cursor_blink_timer_destructor(_: RefAny) {
170 }
172
173pub extern "C" fn cursor_blink_timer_callback(
183 _data: RefAny,
184 mut info: crate::timer::TimerCallbackInfo,
185) -> azul_core::callbacks::TimerCallbackReturn {
186 use azul_core::callbacks::{TimerCallbackReturn, Update};
187 use azul_core::task::TerminateTimer;
188
189 let now = info.get_current_time();
191
192 info.set_cursor_visibility_toggle();
210
211 TimerCallbackReturn {
213 should_update: Update::RefreshDom,
214 should_terminate: TerminateTimer::Continue,
215 }
216}
217
218#[derive(Debug)]
220pub struct DomLayoutResult {
221 pub styled_dom: StyledDom,
223 pub layout_tree: LayoutTree,
225 pub calculated_positions: crate::solver3::PositionVec,
227 pub viewport: LogicalRect,
229 pub display_list: DisplayList,
231 pub scroll_ids: BTreeMap<usize, u64>,
234 pub scroll_id_to_node_id: BTreeMap<u64, NodeId>,
237}
238
239#[derive(Debug, Clone)]
241pub struct ScrollbarDragState {
242 pub hit_id: ScrollbarHitId,
243 pub initial_mouse_pos: LogicalPosition,
244 pub initial_scroll_offset: LogicalPosition,
245}
246
247pub use crate::managers::text_input::PendingTextEdit;
251
252#[derive(Debug, Clone)]
255pub struct TextConstraintsCache {
256 pub constraints: BTreeMap<(DomId, NodeId), UnifiedConstraints>,
258}
259
260impl Default for TextConstraintsCache {
261 fn default() -> Self {
262 Self {
263 constraints: BTreeMap::new(),
264 }
265 }
266}
267
268#[derive(Debug, Clone)]
271pub struct DirtyTextNode {
272 pub content: Vec<InlineContent>,
274 pub cursor: Option<TextCursor>,
276 pub needs_ancestor_relayout: bool,
278}
279
280#[derive(Debug, Default)]
285pub struct CallbackChangeResult {
286 pub timers: FastHashMap<TimerId, crate::timer::Timer>,
288 pub threads: FastHashMap<ThreadId, crate::thread::Thread>,
290 pub timers_removed: FastBTreeSet<TimerId>,
292 pub threads_removed: FastBTreeSet<ThreadId>,
294 pub windows_created: Vec<crate::window_state::WindowCreateOptions>,
296 pub menus_to_open: Vec<(azul_core::menu::Menu, Option<LogicalPosition>)>,
298 pub tooltips_to_show: Vec<(AzString, LogicalPosition)>,
300 pub hide_tooltip: bool,
302 pub stop_propagation: bool,
304 pub stop_immediate_propagation: bool,
306 pub prevent_default: bool,
308 pub focus_target: Option<FocusTarget>,
310 pub words_changed: BTreeMap<DomId, BTreeMap<NodeId, AzString>>,
312 pub images_changed: BTreeMap<DomId, BTreeMap<NodeId, (ImageRef, UpdateImageType)>>,
314 pub image_callbacks_changed: BTreeMap<DomId, FastBTreeSet<NodeId>>,
317 pub iframes_to_update: BTreeMap<DomId, FastBTreeSet<NodeId>>,
320 pub image_masks_changed: BTreeMap<DomId, BTreeMap<NodeId, ImageMask>>,
322 pub css_properties_changed: BTreeMap<DomId, BTreeMap<NodeId, CssPropertyVec>>,
324 pub nodes_scrolled: BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, LogicalPosition>>,
326 pub modified_window_state: FullWindowState,
328 pub queued_window_states: Vec<FullWindowState>,
331 pub hit_test_update_requested: Option<LogicalPosition>,
334 pub text_input_triggered: Vec<(azul_core::dom::DomNodeId, Vec<azul_core::events::EventFilter>)>,
337 pub begin_interactive_move: bool,
339}
340
341pub struct LayoutWindow {
350 #[cfg(feature = "pdf")]
352 pub fragmentation_context: crate::paged::FragmentationContext,
353 pub layout_cache: Solver3LayoutCache,
355 pub text_cache: TextLayoutCache,
357 pub font_manager: FontManager<FontRef>,
359 pub image_cache: ImageCache,
361 pub layout_results: BTreeMap<DomId, DomLayoutResult>,
363 pub scroll_manager: ScrollManager,
365 pub gesture_drag_manager: crate::managers::gesture::GestureAndDragManager,
367 pub focus_manager: crate::managers::focus_cursor::FocusManager,
369 pub cursor_manager: crate::managers::cursor::CursorManager,
371 pub file_drop_manager: crate::managers::file_drop::FileDropManager,
373 pub selection_manager: crate::managers::selection::SelectionManager,
375 pub clipboard_manager: crate::managers::clipboard::ClipboardManager,
377 pub drag_drop_manager: crate::managers::drag_drop::DragDropManager,
379 pub hover_manager: crate::managers::hover::HoverManager,
381 pub iframe_manager: IFrameManager,
383 pub gpu_state_manager: GpuStateManager,
385 pub a11y_manager: crate::managers::a11y::A11yManager,
387 pub timers: BTreeMap<TimerId, Timer>,
389 pub threads: BTreeMap<ThreadId, Thread>,
391 pub renderer_resources: RendererResources,
393 pub renderer_type: Option<RendererType>,
395 pub previous_window_state: Option<FullWindowState>,
397 pub current_window_state: FullWindowState,
400 pub document_id: DocumentId,
403 pub id_namespace: IdNamespace,
405 pub epoch: Epoch,
408 pub gl_texture_cache: GlTextureCache,
410 currently_dragging_thumb: Option<ScrollbarDragState>,
412 pub text_input_manager: crate::managers::text_input::TextInputManager,
414 pub undo_redo_manager: crate::managers::undo_redo::UndoRedoManager,
416 text_constraints_cache: TextConstraintsCache,
419 dirty_text_nodes: BTreeMap<(DomId, NodeId), DirtyTextNode>,
423 pub pending_iframe_updates: BTreeMap<DomId, FastBTreeSet<NodeId>>,
426 pub system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
429 pub monitors: std::sync::Arc<std::sync::Mutex<MonitorVec>>,
433 #[cfg(feature = "icu")]
436 pub icu_localizer: IcuLocalizerHandle,
437}
438
439fn default_duration_500ms() -> Duration {
440 Duration::System(SystemTimeDiff::from_millis(500))
441}
442
443fn default_duration_200ms() -> Duration {
444 Duration::System(SystemTimeDiff::from_millis(200))
445}
446
447fn duration_to_millis(duration: Duration) -> u64 {
452 match duration {
453 #[cfg(feature = "std")]
454 Duration::System(system_diff) => {
455 let std_duration: std::time::Duration = system_diff.into();
456 std_duration.as_millis() as u64
457 }
458 #[cfg(not(feature = "std"))]
459 Duration::System(system_diff) => {
460 system_diff.secs * 1000 + (system_diff.nanos / 1_000_000) as u64
462 }
463 Duration::Tick(tick_diff) => {
464 tick_diff.tick_diff
466 }
467 }
468}
469
470impl LayoutWindow {
471 pub fn new(fc_cache: FcFontCache) -> Result<Self, crate::solver3::LayoutError> {
475 Ok(Self {
476 #[cfg(feature = "pdf")]
478 fragmentation_context: crate::paged::FragmentationContext::new_continuous(800.0),
479 layout_cache: Solver3LayoutCache {
480 tree: None,
481 calculated_positions: Vec::new(),
482 viewport: None,
483 scroll_ids: BTreeMap::new(),
484 scroll_id_to_node_id: BTreeMap::new(),
485 counters: BTreeMap::new(),
486 float_cache: BTreeMap::new(),
487 cache_map: Default::default(),
488 },
489 text_cache: TextLayoutCache::new(),
490 font_manager: FontManager::new(fc_cache)?,
491 image_cache: ImageCache::default(),
492 layout_results: BTreeMap::new(),
493 scroll_manager: ScrollManager::new(),
494 gesture_drag_manager: crate::managers::gesture::GestureAndDragManager::new(),
495 focus_manager: crate::managers::focus_cursor::FocusManager::new(),
496 cursor_manager: crate::managers::cursor::CursorManager::new(),
497 file_drop_manager: crate::managers::file_drop::FileDropManager::new(),
498 selection_manager: crate::managers::selection::SelectionManager::new(),
499 clipboard_manager: crate::managers::clipboard::ClipboardManager::new(),
500 drag_drop_manager: crate::managers::drag_drop::DragDropManager::new(),
501 hover_manager: crate::managers::hover::HoverManager::new(),
502 iframe_manager: IFrameManager::new(),
503 gpu_state_manager: GpuStateManager::new(
504 default_duration_500ms(),
505 default_duration_200ms(),
506 ),
507 a11y_manager: crate::managers::a11y::A11yManager::new(),
508 timers: BTreeMap::new(),
509 threads: BTreeMap::new(),
510 renderer_resources: RendererResources::default(),
511 renderer_type: None,
512 previous_window_state: None,
513 current_window_state: FullWindowState::default(),
514 document_id: new_document_id(),
515 id_namespace: new_id_namespace(),
516 epoch: Epoch::new(),
517 gl_texture_cache: GlTextureCache::default(),
518 currently_dragging_thumb: None,
519 text_input_manager: crate::managers::text_input::TextInputManager::new(),
520 undo_redo_manager: crate::managers::undo_redo::UndoRedoManager::new(),
521 text_constraints_cache: TextConstraintsCache {
522 constraints: BTreeMap::new(),
523 },
524 dirty_text_nodes: BTreeMap::new(),
525 pending_iframe_updates: BTreeMap::new(),
526 system_style: None,
527 monitors: std::sync::Arc::new(std::sync::Mutex::new(MonitorVec::from_const_slice(&[]))),
528 #[cfg(feature = "icu")]
529 icu_localizer: IcuLocalizerHandle::default(),
530 })
531 }
532
533 #[cfg(feature = "pdf")]
546 pub fn new_paged(
547 fc_cache: FcFontCache,
548 page_size: LogicalSize,
549 ) -> Result<Self, crate::solver3::LayoutError> {
550 Ok(Self {
551 fragmentation_context: crate::paged::FragmentationContext::new_paged(page_size),
552 layout_cache: Solver3LayoutCache {
553 tree: None,
554 calculated_positions: Vec::new(),
555 viewport: None,
556 scroll_ids: BTreeMap::new(),
557 scroll_id_to_node_id: BTreeMap::new(),
558 counters: BTreeMap::new(),
559 float_cache: BTreeMap::new(),
560 cache_map: Default::default(),
561 },
562 text_cache: TextLayoutCache::new(),
563 font_manager: FontManager::new(fc_cache)?,
564 image_cache: ImageCache::default(),
565 layout_results: BTreeMap::new(),
566 scroll_manager: ScrollManager::new(),
567 gesture_drag_manager: crate::managers::gesture::GestureAndDragManager::new(),
568 focus_manager: crate::managers::focus_cursor::FocusManager::new(),
569 cursor_manager: crate::managers::cursor::CursorManager::new(),
570 file_drop_manager: crate::managers::file_drop::FileDropManager::new(),
571 selection_manager: crate::managers::selection::SelectionManager::new(),
572 clipboard_manager: crate::managers::clipboard::ClipboardManager::new(),
573 drag_drop_manager: crate::managers::drag_drop::DragDropManager::new(),
574 hover_manager: crate::managers::hover::HoverManager::new(),
575 iframe_manager: IFrameManager::new(),
576 gpu_state_manager: GpuStateManager::new(
577 default_duration_500ms(),
578 default_duration_200ms(),
579 ),
580 a11y_manager: crate::managers::a11y::A11yManager::new(),
581 timers: BTreeMap::new(),
582 threads: BTreeMap::new(),
583 renderer_resources: RendererResources::default(),
584 renderer_type: None,
585 previous_window_state: None,
586 current_window_state: FullWindowState::default(),
587 document_id: new_document_id(),
588 id_namespace: new_id_namespace(),
589 epoch: Epoch::new(),
590 gl_texture_cache: GlTextureCache::default(),
591 currently_dragging_thumb: None,
592 text_input_manager: crate::managers::text_input::TextInputManager::new(),
593 undo_redo_manager: crate::managers::undo_redo::UndoRedoManager::new(),
594 text_constraints_cache: TextConstraintsCache {
595 constraints: BTreeMap::new(),
596 },
597 dirty_text_nodes: BTreeMap::new(),
598 pending_iframe_updates: BTreeMap::new(),
599 system_style: None,
600 monitors: std::sync::Arc::new(std::sync::Mutex::new(MonitorVec::from_const_slice(&[]))),
601 #[cfg(feature = "icu")]
602 icu_localizer: IcuLocalizerHandle::default(),
603 })
604 }
605
606 pub fn layout_and_generate_display_list(
624 &mut self,
625 root_dom: StyledDom,
626 window_state: &FullWindowState,
627 renderer_resources: &RendererResources,
628 system_callbacks: &ExternalSystemCallbacks,
629 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
630 ) -> Result<(), solver3::LayoutError> {
631 self.layout_results.clear();
633
634 if let Some(msgs) = debug_messages.as_mut() {
635 msgs.push(LayoutDebugMessage::info(format!(
636 "[layout_and_generate_display_list] Starting layout for DOM with {} nodes",
637 root_dom.node_data.len()
638 )));
639 }
640
641 let result = self.layout_dom_recursive(
643 root_dom,
644 window_state,
645 renderer_resources,
646 system_callbacks,
647 debug_messages,
648 );
649
650 if let Err(ref e) = result {
651 if let Some(msgs) = debug_messages.as_mut() {
652 msgs.push(LayoutDebugMessage::error(format!(
653 "[layout_and_generate_display_list] Layout FAILED: {:?}",
654 e
655 )));
656 }
657 } else {
658 if let Some(msgs) = debug_messages.as_mut() {
659 msgs.push(LayoutDebugMessage::info(format!(
660 "[layout_and_generate_display_list] Layout SUCCESS, layout_results count: {}",
661 self.layout_results.len()
662 )));
663 }
664 }
665
666 #[cfg(feature = "a11y")]
669 if result.is_ok() {
670 let a11y_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
672 crate::managers::a11y::A11yManager::update_tree(
673 self.a11y_manager.root_id,
674 &self.layout_results,
675 &self.current_window_state.title,
676 self.current_window_state.size.dimensions,
677 )
678 }));
679
680 match a11y_result {
681 Ok(tree_update) => {
682 self.a11y_manager.last_tree_update = Some(tree_update);
684 }
685 Err(_) => {
686 }
688 }
689 }
690
691 if result.is_ok() {
693 self.scroll_focused_cursor_into_view();
694 }
695
696 result
697 }
698
699 fn layout_dom_recursive(
700 &mut self,
701 mut styled_dom: StyledDom,
702 window_state: &FullWindowState,
703 renderer_resources: &RendererResources,
704 system_callbacks: &ExternalSystemCallbacks,
705 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
706 ) -> Result<(), solver3::LayoutError> {
707 if styled_dom.dom_id.inner == 0 {
708 styled_dom.dom_id = DomId::ROOT_ID;
709 }
710 let dom_id = styled_dom.dom_id;
711
712 let viewport = LogicalRect {
713 origin: LogicalPosition::zero(),
714 size: window_state.size.dimensions,
715 };
716
717 let platform = self.system_style.as_ref()
719 .map(|s| s.platform.clone())
720 .unwrap_or_else(azul_css::system::Platform::current);
721
722 {
725 use crate::{
726 solver3::getters::{
727 collect_and_resolve_font_chains, collect_font_ids_from_chains,
728 compute_fonts_to_load, load_fonts_from_disk, register_embedded_fonts_from_styled_dom,
729 },
730 text3::default::PathLoader,
731 };
732
733 if let Some(msgs) = debug_messages.as_mut() {
734 msgs.push(LayoutDebugMessage::info(
735 "[FontLoading] Starting font resolution for DOM".to_string(),
736 ));
737 }
738
739 register_embedded_fonts_from_styled_dom(&styled_dom, &self.font_manager, &platform);
742
743 let chains = collect_and_resolve_font_chains(&styled_dom, &self.font_manager.fc_cache, &platform);
745 if let Some(msgs) = debug_messages.as_mut() {
746 msgs.push(LayoutDebugMessage::info(format!(
747 "[FontLoading] Resolved {} font chains",
748 chains.len()
749 )));
750 }
751
752 let required_fonts = collect_font_ids_from_chains(&chains);
754 if let Some(msgs) = debug_messages.as_mut() {
755 msgs.push(LayoutDebugMessage::info(format!(
756 "[FontLoading] Required fonts: {} unique fonts",
757 required_fonts.len()
758 )));
759 }
760
761 let already_loaded = self.font_manager.get_loaded_font_ids();
763 let fonts_to_load = compute_fonts_to_load(&required_fonts, &already_loaded);
764 if let Some(msgs) = debug_messages.as_mut() {
765 msgs.push(LayoutDebugMessage::info(format!(
766 "[FontLoading] Already loaded: {}, need to load: {}",
767 already_loaded.len(),
768 fonts_to_load.len()
769 )));
770 }
771
772 if !fonts_to_load.is_empty() {
774 if let Some(msgs) = debug_messages.as_mut() {
775 msgs.push(LayoutDebugMessage::info(format!(
776 "[FontLoading] Loading {} fonts from disk...",
777 fonts_to_load.len()
778 )));
779 }
780 let loader = PathLoader::new();
781 let load_result = load_fonts_from_disk(
782 &fonts_to_load,
783 &self.font_manager.fc_cache,
784 |bytes, index| loader.load_font(bytes, index),
785 );
786
787 if let Some(msgs) = debug_messages.as_mut() {
788 msgs.push(LayoutDebugMessage::info(format!(
789 "[FontLoading] Loaded {} fonts, {} failed",
790 load_result.loaded.len(),
791 load_result.failed.len()
792 )));
793 }
794
795 self.font_manager.insert_fonts(load_result.loaded);
797
798 for (font_id, error) in &load_result.failed {
800 if let Some(msgs) = debug_messages.as_mut() {
801 msgs.push(LayoutDebugMessage::warning(format!(
802 "[FontLoading] Failed to load font {:?}: {}",
803 font_id, error
804 )));
805 }
806 }
807 }
808
809 self.font_manager.set_font_chain_cache(chains.into_fontconfig_chains());
811 }
812
813 let scroll_offsets = self.scroll_manager.get_scroll_states_for_dom(dom_id);
814 let styled_dom_clone = styled_dom.clone();
815 let gpu_cache = self.gpu_state_manager.get_or_create_cache(dom_id).clone();
816
817 let cursor_is_visible = self.cursor_manager.should_draw_cursor();
819
820 let cursor_location = self.cursor_manager.get_cursor_location().and_then(|loc| {
822 self.cursor_manager.get_cursor().map(|cursor| {
823 (loc.dom_id, loc.node_id, cursor.clone())
824 })
825 });
826
827 let mut display_list = solver3::layout_document(
828 &mut self.layout_cache,
829 &mut self.text_cache,
830 styled_dom,
831 viewport,
832 &self.font_manager,
833 &scroll_offsets,
834 &self.selection_manager.selections,
835 &self.selection_manager.text_selections,
836 debug_messages,
837 Some(&gpu_cache),
838 &self.renderer_resources,
839 self.id_namespace,
840 dom_id,
841 cursor_is_visible,
842 cursor_location,
843 self.system_style.clone(),
844 system_callbacks.get_system_time_fn,
845 )?;
846
847 let tree = self
848 .layout_cache
849 .tree
850 .clone()
851 .ok_or(solver3::LayoutError::InvalidTree)?;
852
853 let scroll_ids = self.layout_cache.scroll_ids.clone();
855 let scroll_id_to_node_id = self.layout_cache.scroll_id_to_node_id.clone();
856
857 self.gpu_state_manager
859 .update_scrollbar_transforms(dom_id, &self.scroll_manager, &tree);
860
861 let iframes = self.scan_for_iframes(&styled_dom_clone, &tree, &self.layout_cache.calculated_positions);
864
865 for (node_id, bounds) in iframes {
866 if let Some(child_dom_id) = self.invoke_iframe_callback_with_dom(
867 dom_id,
868 node_id,
869 bounds,
870 Some(&styled_dom_clone),
871 window_state,
872 renderer_resources,
873 system_callbacks,
874 debug_messages,
875 ) {
876 display_list
878 .items
879 .push(crate::solver3::display_list::DisplayListItem::IFrame {
880 child_dom_id,
881 bounds,
882 clip_rect: bounds,
883 });
884 }
885 }
886
887 self.layout_results.insert(
889 dom_id,
890 DomLayoutResult {
891 styled_dom: styled_dom_clone,
892 layout_tree: tree,
893 calculated_positions: self.layout_cache.calculated_positions.clone(),
894 viewport,
895 display_list,
896 scroll_ids,
897 scroll_id_to_node_id,
898 },
899 );
900
901 Ok(())
902 }
903
904 fn scan_for_iframes(
905 &self,
906 styled_dom: &StyledDom,
907 layout_tree: &LayoutTree,
908 calculated_positions: &crate::solver3::PositionVec,
909 ) -> Vec<(NodeId, LogicalRect)> {
910 let node_data_container = styled_dom.node_data.as_container();
911 layout_tree
912 .nodes
913 .iter()
914 .enumerate()
915 .filter_map(|(idx, node)| {
916 let node_dom_id = node.dom_node_id?;
917 let node_data = node_data_container.get(node_dom_id)?;
918 if matches!(node_data.get_node_type(), NodeType::IFrame(_)) {
919 let pos = calculated_positions.get(idx).copied().unwrap_or_default();
920 let size = node.used_size.unwrap_or_default();
921 Some((node_dom_id, LogicalRect::new(pos, size)))
922 } else {
923 None
924 }
925 })
926 .collect()
927 }
928
929 pub fn resize_window(
936 &mut self,
937 styled_dom: StyledDom,
938 new_size: LogicalSize,
939 renderer_resources: &RendererResources,
940 system_callbacks: &ExternalSystemCallbacks,
941 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
942 ) -> Result<DisplayList, crate::solver3::LayoutError> {
943 let mut window_state = FullWindowState::default();
945 window_state.size.dimensions = new_size;
946
947 let dom_id = styled_dom.dom_id;
948
949 self.layout_and_generate_display_list(
952 styled_dom,
953 &window_state,
954 renderer_resources,
955 system_callbacks,
956 debug_messages,
957 )?;
958
959 self.layout_results
962 .get_mut(&dom_id)
963 .map(|result| std::mem::replace(&mut result.display_list, DisplayList::default()))
964 .ok_or(solver3::LayoutError::InvalidTree)
965 }
966
967 pub fn clear_caches(&mut self) {
969 self.layout_cache = Solver3LayoutCache {
970 tree: None,
971 calculated_positions: Vec::new(),
972 viewport: None,
973 scroll_ids: BTreeMap::new(),
974 scroll_id_to_node_id: BTreeMap::new(),
975 counters: BTreeMap::new(),
976 float_cache: BTreeMap::new(),
977 cache_map: Default::default(),
978 };
979 self.text_cache = TextLayoutCache::new();
980 self.layout_results.clear();
981 self.scroll_manager = ScrollManager::new();
982 self.selection_manager.clear_all();
983 }
984
985 pub fn set_scroll_position(&mut self, dom_id: DomId, node_id: NodeId, scroll: ScrollPosition) {
987 #[cfg(feature = "std")]
989 let now = Instant::System(std::time::Instant::now().into());
990 #[cfg(not(feature = "std"))]
991 let now = Instant::Tick(azul_core::task::SystemTick { tick_counter: 0 });
992
993 self.scroll_manager.update_node_bounds(
994 dom_id,
995 node_id,
996 scroll.parent_rect,
997 scroll.children_rect,
998 now.clone(),
999 );
1000 self.scroll_manager
1001 .set_scroll_position(dom_id, node_id, scroll.children_rect.origin, now);
1002 }
1003
1004 pub fn get_scroll_position(&self, dom_id: DomId, node_id: NodeId) -> Option<ScrollPosition> {
1006 let states = self.scroll_manager.get_scroll_states_for_dom(dom_id);
1007 states.get(&node_id).cloned()
1008 }
1009
1010 pub fn set_selection(&mut self, dom_id: DomId, selection: SelectionState) {
1012 self.selection_manager.set_selection(dom_id, selection);
1013 }
1014
1015 pub fn get_selection(&self, dom_id: DomId) -> Option<&SelectionState> {
1017 self.selection_manager.get_selection(&dom_id)
1018 }
1019
1020 pub fn invoke_iframe_callback(
1030 &mut self,
1031 parent_dom_id: DomId,
1032 node_id: NodeId,
1033 bounds: LogicalRect,
1034 window_state: &FullWindowState,
1035 renderer_resources: &RendererResources,
1036 system_callbacks: &ExternalSystemCallbacks,
1037 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1038 ) -> Option<DomId> {
1039 self.invoke_iframe_callback_with_dom(
1040 parent_dom_id, node_id, bounds, None,
1041 window_state, renderer_resources, system_callbacks, debug_messages,
1042 )
1043 }
1044
1045 fn invoke_iframe_callback_with_dom(
1049 &mut self,
1050 parent_dom_id: DomId,
1051 node_id: NodeId,
1052 bounds: LogicalRect,
1053 styled_dom_override: Option<&StyledDom>,
1054 window_state: &FullWindowState,
1055 renderer_resources: &RendererResources,
1056 system_callbacks: &ExternalSystemCallbacks,
1057 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1058 ) -> Option<DomId> {
1059 if let Some(msgs) = debug_messages {
1060 msgs.push(LayoutDebugMessage::info(format!(
1061 "invoke_iframe_callback called for node {:?}",
1062 node_id
1063 )));
1064 }
1065
1066 let iframe_node = if let Some(styled_dom) = styled_dom_override {
1068 let node_data_container = styled_dom.node_data.as_container();
1069 let node_data = node_data_container.get(node_id)?;
1070 match node_data.get_node_type() {
1071 NodeType::IFrame(iframe) => iframe.clone(),
1072 _ => return None,
1073 }
1074 } else {
1075 let layout_result = self.layout_results.get(&parent_dom_id)?;
1076 if let Some(msgs) = debug_messages {
1077 msgs.push(LayoutDebugMessage::info(format!(
1078 "Got layout result for parent DOM {:?}",
1079 parent_dom_id
1080 )));
1081 }
1082 let node_data_container = layout_result.styled_dom.node_data.as_container();
1083 let node_data = node_data_container.get(node_id)?;
1084 match node_data.get_node_type() {
1085 NodeType::IFrame(iframe) => iframe.clone(),
1086 other => {
1087 if let Some(msgs) = debug_messages {
1088 msgs.push(LayoutDebugMessage::info(format!(
1089 "Node is NOT IFrame, type = {:?}",
1090 other
1091 )));
1092 }
1093 return None;
1094 }
1095 }
1096 };
1097
1098 if let Some(msgs) = debug_messages {
1099 msgs.push(LayoutDebugMessage::info("Node is IFrame type".to_string()));
1100 }
1101
1102 self.invoke_iframe_callback_impl(
1104 parent_dom_id,
1105 node_id,
1106 &iframe_node,
1107 bounds,
1108 window_state,
1109 renderer_resources,
1110 system_callbacks,
1111 debug_messages,
1112 )
1113 }
1114
1115 fn invoke_iframe_callback_impl(
1126 &mut self,
1127 parent_dom_id: DomId,
1128 node_id: NodeId,
1129 iframe_node: &azul_core::dom::IFrameNode,
1130 bounds: LogicalRect,
1131 window_state: &FullWindowState,
1132 renderer_resources: &RendererResources,
1133 system_callbacks: &ExternalSystemCallbacks,
1134 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1135 ) -> Option<DomId> {
1136 let now = (system_callbacks.get_system_time_fn.cb)();
1138
1139 self.scroll_manager.update_node_bounds(
1142 parent_dom_id,
1143 node_id,
1144 bounds,
1145 LogicalRect::new(LogicalPosition::zero(), bounds.size), now.clone(),
1147 );
1148
1149 let reason = match self.iframe_manager.check_reinvoke(
1152 parent_dom_id,
1153 node_id,
1154 &self.scroll_manager,
1155 bounds,
1156 ) {
1157 Some(r) => r,
1158 None => {
1159 return self
1161 .iframe_manager
1162 .get_nested_dom_id(parent_dom_id, node_id);
1163 }
1164 };
1165
1166 if let Some(msgs) = debug_messages {
1167 msgs.push(LayoutDebugMessage::info(format!(
1168 "IFrame ({:?}, {:?}) - Reason: {:?}",
1169 parent_dom_id, node_id, reason
1170 )));
1171 }
1172
1173 let scroll_offset = self
1174 .scroll_manager
1175 .get_current_offset(parent_dom_id, node_id)
1176 .unwrap_or_default();
1177 let hidpi_factor = window_state.size.get_hidpi_factor();
1178
1179 let mut callback_info = azul_core::callbacks::IFrameCallbackInfo::new(
1181 reason,
1182 &*self.font_manager.fc_cache,
1183 &self.image_cache,
1184 window_state.theme,
1185 azul_core::callbacks::HidpiAdjustedBounds {
1186 logical_size: bounds.size,
1187 hidpi_factor,
1188 },
1189 bounds.size,
1190 scroll_offset,
1191 bounds.size,
1192 LogicalPosition::zero(),
1193 );
1194
1195 let callback_data = iframe_node.refany.clone();
1197
1198 let callback_return = (iframe_node.callback.cb)(callback_data, callback_info);
1200
1201 self.iframe_manager
1203 .mark_invoked(parent_dom_id, node_id, reason);
1204
1205 let mut child_styled_dom = match callback_return.dom {
1207 azul_core::styled_dom::OptionStyledDom::Some(dom) => dom,
1208 azul_core::styled_dom::OptionStyledDom::None => {
1209 if reason == IFrameCallbackReason::InitialRender {
1211 let mut empty_dom = Dom::create_div();
1213 let empty_css = Css::empty();
1214 empty_dom.style(empty_css)
1215 } else {
1216 self.iframe_manager.update_iframe_info(
1219 parent_dom_id,
1220 node_id,
1221 callback_return.scroll_size,
1222 callback_return.virtual_scroll_size,
1223 );
1224 self.scroll_manager.update_virtual_scroll_bounds(
1226 parent_dom_id,
1227 node_id,
1228 callback_return.virtual_scroll_size,
1229 Some(callback_return.scroll_offset),
1230 );
1231 return self
1232 .iframe_manager
1233 .get_nested_dom_id(parent_dom_id, node_id);
1234 }
1235 }
1236 };
1237
1238 let child_dom_id = self
1240 .iframe_manager
1241 .get_or_create_nested_dom_id(parent_dom_id, node_id);
1242 child_styled_dom.dom_id = child_dom_id;
1243
1244 self.iframe_manager.update_iframe_info(
1246 parent_dom_id,
1247 node_id,
1248 callback_return.scroll_size,
1249 callback_return.virtual_scroll_size,
1250 );
1251 self.scroll_manager.update_virtual_scroll_bounds(
1253 parent_dom_id,
1254 node_id,
1255 callback_return.virtual_scroll_size,
1256 Some(callback_return.scroll_offset),
1257 );
1258
1259 self.layout_dom_recursive(
1263 child_styled_dom,
1264 window_state,
1265 renderer_resources,
1266 system_callbacks,
1267 debug_messages,
1268 )
1269 .ok()?;
1270
1271 Some(child_dom_id)
1272 }
1273
1274 pub fn get_node_size(&self, node_id: DomNodeId) -> Option<LogicalSize> {
1278 let layout_result = self.layout_results.get(&node_id.dom)?;
1279 let nid = node_id.node.into_crate_internal()?;
1280 let layout_indices = layout_result.layout_tree.dom_to_layout.get(&nid)?;
1282 let layout_index = *layout_indices.first()?;
1283 let layout_node = layout_result.layout_tree.get(layout_index)?;
1284 layout_node.used_size
1285 }
1286
1287 pub fn get_node_position(&self, node_id: DomNodeId) -> Option<LogicalPosition> {
1289 let layout_result = self.layout_results.get(&node_id.dom)?;
1290 let nid = node_id.node.into_crate_internal()?;
1291 let layout_indices = layout_result.layout_tree.dom_to_layout.get(&nid)?;
1293 let layout_index = *layout_indices.first()?;
1294 let position = layout_result.calculated_positions.get(layout_index)?;
1295 Some(*position)
1296 }
1297
1298 pub fn get_node_hit_test_bounds(&self, node_id: DomNodeId) -> Option<LogicalRect> {
1304 use crate::solver3::display_list::DisplayListItem;
1305
1306 let layout_result = self.layout_results.get(&node_id.dom)?;
1307 let nid = node_id.node.into_crate_internal()?;
1308
1309 let nid_encoded = NodeHierarchyItemId::from_crate_internal(Some(nid));
1311 let tag_id = layout_result.styled_dom.tag_ids_to_node_ids.iter()
1312 .find(|m| m.node_id == nid_encoded)?
1313 .tag_id
1314 .inner;
1315
1316 for item in &layout_result.display_list.items {
1319 if let DisplayListItem::HitTestArea { bounds, tag } = item {
1320 if tag.0 == tag_id && bounds.size.width > 0.0 && bounds.size.height > 0.0 {
1321 return Some(*bounds);
1322 }
1323 }
1324 }
1325 None
1326 }
1327
1328 pub fn get_parent(&self, node_id: DomNodeId) -> Option<DomNodeId> {
1330 let layout_result = self.layout_results.get(&node_id.dom)?;
1331 let nid = node_id.node.into_crate_internal()?;
1332 let parent_id = layout_result
1333 .styled_dom
1334 .node_hierarchy
1335 .as_container()
1336 .get(nid)?
1337 .parent_id()?;
1338 Some(DomNodeId {
1339 dom: node_id.dom,
1340 node: NodeHierarchyItemId::from_crate_internal(Some(parent_id)),
1341 })
1342 }
1343
1344 pub fn get_first_child(&self, node_id: DomNodeId) -> Option<DomNodeId> {
1346 let layout_result = self.layout_results.get(&node_id.dom)?;
1347 let nid = node_id.node.into_crate_internal()?;
1348 let node_hierarchy = layout_result.styled_dom.node_hierarchy.as_container();
1349 let hierarchy_item = node_hierarchy.get(nid)?;
1350 let first_child_id = hierarchy_item.first_child_id(nid)?;
1351 Some(DomNodeId {
1352 dom: node_id.dom,
1353 node: NodeHierarchyItemId::from_crate_internal(Some(first_child_id)),
1354 })
1355 }
1356
1357 pub fn get_next_sibling(&self, node_id: DomNodeId) -> Option<DomNodeId> {
1359 let layout_result = self.layout_results.get(&node_id.dom)?;
1360 let nid = node_id.node.into_crate_internal()?;
1361 let next_sibling_id = layout_result
1362 .styled_dom
1363 .node_hierarchy
1364 .as_container()
1365 .get(nid)?
1366 .next_sibling_id()?;
1367 Some(DomNodeId {
1368 dom: node_id.dom,
1369 node: NodeHierarchyItemId::from_crate_internal(Some(next_sibling_id)),
1370 })
1371 }
1372
1373 pub fn get_previous_sibling(&self, node_id: DomNodeId) -> Option<DomNodeId> {
1375 let layout_result = self.layout_results.get(&node_id.dom)?;
1376 let nid = node_id.node.into_crate_internal()?;
1377 let prev_sibling_id = layout_result
1378 .styled_dom
1379 .node_hierarchy
1380 .as_container()
1381 .get(nid)?
1382 .previous_sibling_id()?;
1383 Some(DomNodeId {
1384 dom: node_id.dom,
1385 node: NodeHierarchyItemId::from_crate_internal(Some(prev_sibling_id)),
1386 })
1387 }
1388
1389 pub fn get_last_child(&self, node_id: DomNodeId) -> Option<DomNodeId> {
1391 let layout_result = self.layout_results.get(&node_id.dom)?;
1392 let nid = node_id.node.into_crate_internal()?;
1393 let last_child_id = layout_result
1394 .styled_dom
1395 .node_hierarchy
1396 .as_container()
1397 .get(nid)?
1398 .last_child_id()?;
1399 Some(DomNodeId {
1400 dom: node_id.dom,
1401 node: NodeHierarchyItemId::from_crate_internal(Some(last_child_id)),
1402 })
1403 }
1404
1405 pub fn scan_used_fonts(&self) -> BTreeSet<FontKey> {
1407 let mut fonts = BTreeSet::new();
1408 for (_dom_id, layout_result) in &self.layout_results {
1409 }
1412 fonts
1413 }
1414
1415 pub fn scan_used_images(&self, _css_image_cache: &ImageCache) -> BTreeSet<ImageRefHash> {
1417 let mut images = BTreeSet::new();
1418 for (_dom_id, layout_result) in &self.layout_results {
1419 }
1422 images
1423 }
1424
1425 fn get_nested_scroll_states(
1427 &self,
1428 dom_id: DomId,
1429 ) -> BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, ScrollPosition>> {
1430 let mut nested = BTreeMap::new();
1431 let scroll_states = self.scroll_manager.get_scroll_states_for_dom(dom_id);
1432 let mut inner = BTreeMap::new();
1433 for (node_id, scroll_pos) in scroll_states {
1434 inner.insert(
1435 NodeHierarchyItemId::from_crate_internal(Some(node_id)),
1436 scroll_pos,
1437 );
1438 }
1439 nested.insert(dom_id, inner);
1440 nested
1441 }
1442
1443 pub fn scroll_node_into_view(
1462 &mut self,
1463 node_id: DomNodeId,
1464 options: crate::managers::scroll_into_view::ScrollIntoViewOptions,
1465 now: azul_core::task::Instant,
1466 ) -> Vec<crate::managers::scroll_into_view::ScrollAdjustment> {
1467 crate::managers::scroll_into_view::scroll_node_into_view(
1468 node_id,
1469 &self.layout_results,
1470 &mut self.scroll_manager,
1471 options,
1472 now,
1473 )
1474 }
1475
1476 pub fn scroll_cursor_into_view(
1481 &mut self,
1482 cursor_rect: LogicalRect,
1483 node_id: DomNodeId,
1484 options: crate::managers::scroll_into_view::ScrollIntoViewOptions,
1485 now: azul_core::task::Instant,
1486 ) -> Vec<crate::managers::scroll_into_view::ScrollAdjustment> {
1487 crate::managers::scroll_into_view::scroll_cursor_into_view(
1488 cursor_rect,
1489 node_id,
1490 &self.layout_results,
1491 &mut self.scroll_manager,
1492 options,
1493 now,
1494 )
1495 }
1496
1497 pub fn add_timer(&mut self, timer_id: TimerId, timer: Timer) {
1501 self.timers.insert(timer_id, timer);
1502 }
1503
1504 pub fn remove_timer(&mut self, timer_id: &TimerId) -> Option<Timer> {
1506 self.timers.remove(timer_id)
1507 }
1508
1509 pub fn get_timer(&self, timer_id: &TimerId) -> Option<&Timer> {
1511 self.timers.get(timer_id)
1512 }
1513
1514 pub fn get_timer_mut(&mut self, timer_id: &TimerId) -> Option<&mut Timer> {
1516 self.timers.get_mut(timer_id)
1517 }
1518
1519 pub fn get_timer_ids(&self) -> TimerIdVec {
1521 self.timers.keys().copied().collect::<Vec<_>>().into()
1522 }
1523
1524 pub fn tick_timers(&mut self, current_time: azul_core::task::Instant) -> Vec<TimerId> {
1527 let mut ready_timers = Vec::new();
1528
1529 for (timer_id, timer) in &mut self.timers {
1530 ready_timers.push(*timer_id);
1535 }
1536
1537 ready_timers
1538 }
1539
1540 pub fn time_until_next_timer_ms(
1549 &self,
1550 get_system_time_fn: &azul_core::task::GetSystemTimeCallback,
1551 ) -> Option<u64> {
1552 if self.timers.is_empty() {
1553 return None; }
1555
1556 let now = (get_system_time_fn.cb)();
1557 let mut min_ms: Option<u64> = None;
1558
1559 for timer in self.timers.values() {
1560 let next_run = timer.instant_of_next_run();
1561
1562 let ms_until = if next_run < now {
1564 0 } else {
1566 duration_to_millis(next_run.duration_since(&now))
1567 };
1568
1569 min_ms = Some(match min_ms {
1570 Some(current_min) => current_min.min(ms_until),
1571 None => ms_until,
1572 });
1573 }
1574
1575 min_ms
1576 }
1577
1578 pub fn add_thread(&mut self, thread_id: ThreadId, thread: Thread) {
1582 self.threads.insert(thread_id, thread);
1583 }
1584
1585 pub fn remove_thread(&mut self, thread_id: &ThreadId) -> Option<Thread> {
1587 self.threads.remove(thread_id)
1588 }
1589
1590 pub fn get_thread(&self, thread_id: &ThreadId) -> Option<&Thread> {
1592 self.threads.get(thread_id)
1593 }
1594
1595 pub fn get_thread_mut(&mut self, thread_id: &ThreadId) -> Option<&mut Thread> {
1597 self.threads.get_mut(thread_id)
1598 }
1599
1600 pub fn get_thread_ids(&self) -> ThreadIdVec {
1602 self.threads.keys().copied().collect::<Vec<_>>().into()
1603 }
1604
1605 pub fn create_cursor_blink_timer(&self, _window_state: &FullWindowState) -> crate::timer::Timer {
1613 use azul_core::task::{Duration, SystemTimeDiff};
1614 use crate::timer::{Timer, TimerCallback};
1615 use azul_core::refany::RefAny;
1616
1617 let interval_ms = crate::managers::cursor::CURSOR_BLINK_INTERVAL_MS;
1618
1619 let refany = RefAny::new(());
1622
1623 Timer {
1624 refany,
1625 node_id: None.into(),
1626 created: azul_core::task::Instant::now(),
1627 run_count: 0,
1628 last_run: azul_core::task::OptionInstant::None,
1629 delay: azul_core::task::OptionDuration::None,
1630 interval: azul_core::task::OptionDuration::Some(Duration::System(SystemTimeDiff::from_millis(interval_ms))),
1631 timeout: azul_core::task::OptionDuration::None,
1632 callback: TimerCallback::create(cursor_blink_timer_callback),
1633 }
1634 }
1635
1636 pub fn scroll_active_cursor_into_view(&mut self, result: &mut CallbackChangeResult) {
1641 use crate::managers::scroll_into_view;
1642
1643 let focused_node = match self.focus_manager.get_focused_node() {
1645 Some(node) => *node,
1646 None => return,
1647 };
1648
1649 let Some(node_id_internal) = focused_node.node.into_crate_internal() else {
1650 return;
1651 };
1652
1653 if !self.is_node_contenteditable_internal(focused_node.dom, node_id_internal) {
1655 return;
1656 }
1657
1658 let cursor_location = match self.cursor_manager.get_cursor_location() {
1660 Some(loc) if loc.dom_id == focused_node.dom && loc.node_id == node_id_internal => loc,
1661 _ => return,
1662 };
1663
1664 let cursor = match self.cursor_manager.get_cursor() {
1666 Some(c) => c.clone(),
1667 None => return,
1668 };
1669
1670 let layout = match self.get_inline_layout_for_node(focused_node.dom, node_id_internal) {
1672 Some(l) => l,
1673 None => return,
1674 };
1675
1676 let cursor_rect = match layout.get_cursor_rect(&cursor) {
1678 Some(r) => r,
1679 None => return,
1680 };
1681
1682 let now = azul_core::task::Instant::now();
1684 let options = scroll_into_view::ScrollIntoViewOptions::nearest();
1685
1686 let adjustments = scroll_into_view::scroll_rect_into_view(
1688 cursor_rect,
1689 focused_node.dom,
1690 node_id_internal,
1691 &self.layout_results,
1692 &mut self.scroll_manager,
1693 options,
1694 now,
1695 );
1696
1697 for adj in adjustments {
1699 let current_pos = self.scroll_manager
1700 .get_current_offset(adj.scroll_container_dom_id, adj.scroll_container_node_id)
1701 .unwrap_or(LogicalPosition::zero());
1702
1703 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(adj.scroll_container_node_id));
1704 result
1705 .nodes_scrolled
1706 .entry(adj.scroll_container_dom_id)
1707 .or_insert_with(BTreeMap::new)
1708 .insert(hierarchy_id, current_pos);
1709 }
1710 }
1711
1712 fn is_node_contenteditable_internal(&self, dom_id: DomId, node_id: NodeId) -> bool {
1714 use crate::solver3::getters::is_node_contenteditable;
1715
1716 let Some(layout_result) = self.layout_results.get(&dom_id) else {
1717 return false;
1718 };
1719
1720 is_node_contenteditable(&layout_result.styled_dom, node_id)
1721 }
1722
1723 fn is_node_contenteditable_inherited_internal(&self, dom_id: DomId, node_id: NodeId) -> bool {
1729 use crate::solver3::getters::is_node_contenteditable_inherited;
1730
1731 let Some(layout_result) = self.layout_results.get(&dom_id) else {
1732 return false;
1733 };
1734
1735 is_node_contenteditable_inherited(&layout_result.styled_dom, node_id)
1736 }
1737
1738 pub fn handle_focus_change_for_cursor_blink(
1758 &mut self,
1759 new_focus: Option<azul_core::dom::DomNodeId>,
1760 current_window_state: &FullWindowState,
1761 ) -> CursorBlinkTimerAction {
1762 let contenteditable_info = match new_focus {
1765 Some(focus_node) => {
1766 if let Some(node_id) = focus_node.node.into_crate_internal() {
1767 if self.is_node_contenteditable_inherited_internal(focus_node.dom, node_id) {
1769 let text_node_id = self.find_last_text_child(focus_node.dom, node_id)
1771 .unwrap_or(node_id);
1772 Some((focus_node.dom, node_id, text_node_id))
1773 } else {
1774 None
1775 }
1776 } else {
1777 None
1778 }
1779 }
1780 None => None,
1781 };
1782
1783 let timer_was_active = self.cursor_manager.is_blink_timer_active();
1785
1786 if let Some((dom_id, container_node_id, text_node_id)) = contenteditable_info {
1787
1788 self.focus_manager.set_pending_contenteditable_focus(
1791 dom_id,
1792 container_node_id,
1793 text_node_id,
1794 );
1795
1796 let now = azul_core::task::Instant::now();
1798 self.cursor_manager.reset_blink_on_input(now);
1799 self.cursor_manager.set_blink_timer_active(true);
1800
1801 if !timer_was_active {
1802 let timer = self.create_cursor_blink_timer(current_window_state);
1804 return CursorBlinkTimerAction::Start(timer);
1805 } else {
1806 return CursorBlinkTimerAction::NoChange;
1808 }
1809 } else {
1810 self.cursor_manager.clear();
1814 self.focus_manager.clear_pending_contenteditable_focus();
1815
1816 if timer_was_active {
1817 self.cursor_manager.set_blink_timer_active(false);
1819 return CursorBlinkTimerAction::Stop;
1820 } else {
1821 return CursorBlinkTimerAction::NoChange;
1822 }
1823 }
1824 }
1825
1826 pub fn finalize_pending_focus_changes(&mut self) -> bool {
1848 let pending = match self.focus_manager.take_pending_contenteditable_focus() {
1850 Some(p) => p,
1851 None => return false,
1852 };
1853
1854 let text_layout = self.get_inline_layout_for_node(pending.dom_id, pending.text_node_id).cloned();
1856
1857 self.cursor_manager.initialize_cursor_at_end(
1859 pending.dom_id,
1860 pending.text_node_id,
1861 text_layout.as_ref(),
1862 )
1863 }
1864
1865 pub fn apply_callback_changes(
1875 &mut self,
1876 changes: Vec<crate::callbacks::CallbackChange>,
1877 current_window_state: &FullWindowState,
1878 image_cache: &mut ImageCache,
1879 system_fonts: &mut FcFontCache,
1880 ) -> CallbackChangeResult {
1881 use crate::callbacks::CallbackChange;
1882
1883 let mut result = CallbackChangeResult {
1884 modified_window_state: current_window_state.clone(),
1885 ..Default::default()
1886 };
1887 for change in changes {
1888 match change {
1889 CallbackChange::ModifyWindowState { state } => {
1890 result.modified_window_state = state;
1891 }
1892 CallbackChange::QueueWindowStateSequence { states } => {
1893 result.queued_window_states.extend(states);
1897 }
1898 CallbackChange::CreateNewWindow { options } => {
1899 result.windows_created.push(options);
1900 }
1901 CallbackChange::CloseWindow => {
1902 result.modified_window_state.flags.close_requested = true;
1904 }
1905 CallbackChange::SetFocusTarget { target } => {
1906 result.focus_target = Some(target);
1907 }
1908 CallbackChange::StopPropagation => {
1909 result.stop_propagation = true;
1910 }
1911 CallbackChange::StopImmediatePropagation => {
1912 result.stop_immediate_propagation = true;
1913 result.stop_propagation = true; }
1915 CallbackChange::PreventDefault => {
1916 result.prevent_default = true;
1917 }
1918 CallbackChange::AddTimer { timer_id, timer } => {
1919 result.timers.insert(timer_id, timer);
1920 }
1921 CallbackChange::RemoveTimer { timer_id } => {
1922 result.timers_removed.insert(timer_id);
1923 }
1924 CallbackChange::AddThread { thread_id, thread } => {
1925 result.threads.insert(thread_id, thread);
1926 }
1927 CallbackChange::RemoveThread { thread_id } => {
1928 result.threads_removed.insert(thread_id);
1929 }
1930 CallbackChange::ChangeNodeText { node_id, text } => {
1931 let dom_id = node_id.dom;
1932 let internal_node_id = match node_id.node.into_crate_internal() {
1933 Some(id) => id,
1934 None => continue,
1935 };
1936 result
1937 .words_changed
1938 .entry(dom_id)
1939 .or_insert_with(BTreeMap::new)
1940 .insert(internal_node_id, text);
1941 }
1942 CallbackChange::ChangeNodeImage {
1943 dom_id,
1944 node_id,
1945 image,
1946 update_type,
1947 } => {
1948 result
1949 .images_changed
1950 .entry(dom_id)
1951 .or_insert_with(BTreeMap::new)
1952 .insert(node_id, (image, update_type));
1953 }
1954 CallbackChange::UpdateImageCallback { dom_id, node_id } => {
1955 result
1956 .image_callbacks_changed
1957 .entry(dom_id)
1958 .or_insert_with(FastBTreeSet::new)
1959 .insert(node_id);
1960 }
1961 CallbackChange::UpdateIFrame { dom_id, node_id } => {
1962 result
1963 .iframes_to_update
1964 .entry(dom_id)
1965 .or_insert_with(FastBTreeSet::new)
1966 .insert(node_id);
1967 }
1968 CallbackChange::ChangeNodeImageMask {
1969 dom_id,
1970 node_id,
1971 mask,
1972 } => {
1973 result
1974 .image_masks_changed
1975 .entry(dom_id)
1976 .or_insert_with(BTreeMap::new)
1977 .insert(node_id, mask);
1978 }
1979 CallbackChange::ChangeNodeCssProperties {
1980 dom_id,
1981 node_id,
1982 properties,
1983 } => {
1984 result
1985 .css_properties_changed
1986 .entry(dom_id)
1987 .or_insert_with(BTreeMap::new)
1988 .insert(node_id, properties);
1989 }
1990 CallbackChange::ScrollTo {
1991 dom_id,
1992 node_id,
1993 position,
1994 } => {
1995 result
1996 .nodes_scrolled
1997 .entry(dom_id)
1998 .or_insert_with(BTreeMap::new)
1999 .insert(node_id, position);
2000 }
2001 CallbackChange::ScrollIntoView { node_id, options } => {
2002 use crate::managers::scroll_into_view;
2004 let now = azul_core::task::Instant::now();
2005 let adjustments = scroll_into_view::scroll_node_into_view(
2006 node_id,
2007 &self.layout_results,
2008 &mut self.scroll_manager,
2009 options,
2010 now,
2011 );
2012 for adj in adjustments {
2016 let current_pos = self.scroll_manager
2018 .get_current_offset(adj.scroll_container_dom_id, adj.scroll_container_node_id)
2019 .unwrap_or(LogicalPosition::zero());
2020
2021 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(adj.scroll_container_node_id));
2022 result
2023 .nodes_scrolled
2024 .entry(adj.scroll_container_dom_id)
2025 .or_insert_with(BTreeMap::new)
2026 .insert(hierarchy_id, current_pos);
2027 }
2028 }
2029 CallbackChange::AddImageToCache { id, image } => {
2030 image_cache.add_css_image_id(id, image);
2031 }
2032 CallbackChange::RemoveImageFromCache { id } => {
2033 image_cache.delete_css_image_id(&id);
2034 }
2035 CallbackChange::ReloadSystemFonts => {
2036 *system_fonts = FcFontCache::build();
2037 }
2038 CallbackChange::OpenMenu { menu, position } => {
2039 result.menus_to_open.push((menu, position));
2040 }
2041 CallbackChange::ShowTooltip { text, position } => {
2042 result.tooltips_to_show.push((text, position));
2043 }
2044 CallbackChange::HideTooltip => {
2045 result.hide_tooltip = true;
2046 }
2047 CallbackChange::InsertText {
2048 dom_id,
2049 node_id,
2050 text,
2051 } => {
2052 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
2054 let dom_node_id = DomNodeId {
2055 dom: dom_id,
2056 node: hierarchy_id,
2057 };
2058
2059 let old_inline_content = self.get_text_before_textinput(dom_id, node_id);
2061 let old_text = self.extract_text_from_inline_content(&old_inline_content);
2062
2063 use crate::managers::text_input::TextInputSource;
2065 self.text_input_manager.record_input(
2066 dom_node_id,
2067 text.to_string(),
2068 old_text,
2069 TextInputSource::Programmatic,
2070 );
2071 }
2072 CallbackChange::DeleteBackward { dom_id, node_id } => {
2073 if let Some(cursor) = self.cursor_manager.get_cursor() {
2075 let content = self.get_text_before_textinput(dom_id, node_id);
2077
2078 use crate::text3::edit::{delete_backward, TextEdit};
2081 let mut new_content = content.clone();
2082 let (updated_content, new_cursor) =
2083 delete_backward(&mut new_content, cursor);
2084
2085 self.cursor_manager
2087 .move_cursor_to(new_cursor, dom_id, node_id);
2088
2089 self.update_text_cache_after_edit(dom_id, node_id, updated_content);
2091
2092 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
2094 let dom_node_id = DomNodeId {
2095 dom: dom_id,
2096 node: hierarchy_id,
2097 };
2098 }
2100 }
2101 CallbackChange::DeleteForward { dom_id, node_id } => {
2102 if let Some(cursor) = self.cursor_manager.get_cursor() {
2104 let content = self.get_text_before_textinput(dom_id, node_id);
2106
2107 use crate::text3::edit::{delete_forward, TextEdit};
2110 let mut new_content = content.clone();
2111 let (updated_content, new_cursor) =
2112 delete_forward(&mut new_content, cursor);
2113
2114 self.cursor_manager
2116 .move_cursor_to(new_cursor, dom_id, node_id);
2117
2118 self.update_text_cache_after_edit(dom_id, node_id, updated_content);
2120 }
2121 }
2122 CallbackChange::MoveCursor {
2123 dom_id,
2124 node_id,
2125 cursor,
2126 } => {
2127 self.cursor_manager.move_cursor_to(cursor, dom_id, node_id);
2129 }
2130 CallbackChange::SetSelection {
2131 dom_id,
2132 node_id,
2133 selection,
2134 } => {
2135 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
2137 let dom_node_id = DomNodeId {
2138 dom: dom_id,
2139 node: hierarchy_id,
2140 };
2141
2142 match selection {
2143 Selection::Cursor(cursor) => {
2144 self.cursor_manager.move_cursor_to(cursor, dom_id, node_id);
2145 self.selection_manager.clear_all();
2146 }
2147 Selection::Range(range) => {
2148 self.cursor_manager
2149 .move_cursor_to(range.start, dom_id, node_id);
2150 }
2153 }
2154 }
2155 CallbackChange::SetTextChangeset { changeset } => {
2156 self.text_input_manager.pending_changeset = Some(changeset);
2159 }
2160 CallbackChange::MoveCursorLeft {
2162 dom_id,
2163 node_id,
2164 extend_selection,
2165 } => {
2166 if let Some(new_cursor) =
2167 self.move_cursor_in_node(dom_id, node_id, |layout, cursor| {
2168 layout.move_cursor_left(*cursor, &mut None)
2169 })
2170 {
2171 self.handle_cursor_movement(dom_id, node_id, new_cursor, extend_selection);
2172 }
2173 }
2174 CallbackChange::MoveCursorRight {
2175 dom_id,
2176 node_id,
2177 extend_selection,
2178 } => {
2179 if let Some(new_cursor) =
2180 self.move_cursor_in_node(dom_id, node_id, |layout, cursor| {
2181 layout.move_cursor_right(*cursor, &mut None)
2182 })
2183 {
2184 self.handle_cursor_movement(dom_id, node_id, new_cursor, extend_selection);
2185 }
2186 }
2187 CallbackChange::MoveCursorUp {
2188 dom_id,
2189 node_id,
2190 extend_selection,
2191 } => {
2192 if let Some(new_cursor) =
2193 self.move_cursor_in_node(dom_id, node_id, |layout, cursor| {
2194 layout.move_cursor_up(*cursor, &mut None, &mut None)
2195 })
2196 {
2197 self.handle_cursor_movement(dom_id, node_id, new_cursor, extend_selection);
2198 }
2199 }
2200 CallbackChange::MoveCursorDown {
2201 dom_id,
2202 node_id,
2203 extend_selection,
2204 } => {
2205 if let Some(new_cursor) =
2206 self.move_cursor_in_node(dom_id, node_id, |layout, cursor| {
2207 layout.move_cursor_down(*cursor, &mut None, &mut None)
2208 })
2209 {
2210 self.handle_cursor_movement(dom_id, node_id, new_cursor, extend_selection);
2211 }
2212 }
2213 CallbackChange::MoveCursorToLineStart {
2214 dom_id,
2215 node_id,
2216 extend_selection,
2217 } => {
2218 if let Some(new_cursor) =
2219 self.move_cursor_in_node(dom_id, node_id, |layout, cursor| {
2220 layout.move_cursor_to_line_start(*cursor, &mut None)
2221 })
2222 {
2223 self.handle_cursor_movement(dom_id, node_id, new_cursor, extend_selection);
2224 }
2225 }
2226 CallbackChange::MoveCursorToLineEnd {
2227 dom_id,
2228 node_id,
2229 extend_selection,
2230 } => {
2231 if let Some(new_cursor) =
2232 self.move_cursor_in_node(dom_id, node_id, |layout, cursor| {
2233 layout.move_cursor_to_line_end(*cursor, &mut None)
2234 })
2235 {
2236 self.handle_cursor_movement(dom_id, node_id, new_cursor, extend_selection);
2237 }
2238 }
2239 CallbackChange::MoveCursorToDocumentStart {
2240 dom_id,
2241 node_id,
2242 extend_selection,
2243 } => {
2244 if let Some(new_cursor) = self.get_inline_layout_for_node(dom_id, node_id) {
2246 if let Some(first_cluster) = new_cursor
2247 .items
2248 .first()
2249 .and_then(|item| item.item.as_cluster())
2250 {
2251 let doc_start_cursor = TextCursor {
2252 cluster_id: first_cluster.source_cluster_id,
2253 affinity: CursorAffinity::Leading,
2254 };
2255 self.handle_cursor_movement(
2256 dom_id,
2257 node_id,
2258 doc_start_cursor,
2259 extend_selection,
2260 );
2261 }
2262 }
2263 }
2264 CallbackChange::MoveCursorToDocumentEnd {
2265 dom_id,
2266 node_id,
2267 extend_selection,
2268 } => {
2269 if let Some(layout) = self.get_inline_layout_for_node(dom_id, node_id) {
2271 if let Some(last_cluster) =
2272 layout.items.last().and_then(|item| item.item.as_cluster())
2273 {
2274 let doc_end_cursor = TextCursor {
2275 cluster_id: last_cluster.source_cluster_id,
2276 affinity: CursorAffinity::Trailing,
2277 };
2278 self.handle_cursor_movement(
2279 dom_id,
2280 node_id,
2281 doc_end_cursor,
2282 extend_selection,
2283 );
2284 }
2285 }
2286 }
2287 CallbackChange::SetCopyContent { target, content } => {
2289 self.clipboard_manager.set_copy_content(content);
2292 }
2293 CallbackChange::SetCutContent { target, content } => {
2294 self.clipboard_manager.set_copy_content(content);
2296 }
2297 CallbackChange::SetSelectAllRange { target, range } => {
2298 if let Some(node_id_internal) = target.node.into_crate_internal() {
2301 let dom_node_id = azul_core::dom::DomNodeId {
2302 dom: target.dom,
2303 node: target.node,
2304 };
2305 self.selection_manager
2306 .set_range(target.dom, dom_node_id, range);
2307 }
2308 }
2309 CallbackChange::RequestHitTestUpdate { position } => {
2310 result.hit_test_update_requested = Some(position);
2313 }
2314 CallbackChange::ProcessTextSelectionClick { position, time_ms } => {
2315 let _ = self.process_mouse_click_for_selection(position, time_ms);
2319 }
2320 CallbackChange::SetCursorVisibility { visible: _ } => {
2321 let now = azul_core::task::Instant::now();
2323 if self.cursor_manager.should_blink(&now) {
2324 self.cursor_manager.toggle_visibility();
2326 } else {
2327 self.cursor_manager.set_visibility(true);
2329 }
2330 }
2331 CallbackChange::ResetCursorBlink => {
2332 let now = azul_core::task::Instant::now();
2334 self.cursor_manager.reset_blink_on_input(now);
2335 }
2336 CallbackChange::StartCursorBlinkTimer => {
2337 if !self.cursor_manager.is_blink_timer_active() {
2339 let timer = self.create_cursor_blink_timer(current_window_state);
2340 result.timers.insert(azul_core::task::CURSOR_BLINK_TIMER_ID, timer);
2341 self.cursor_manager.set_blink_timer_active(true);
2342 }
2343 }
2344 CallbackChange::StopCursorBlinkTimer => {
2345 if self.cursor_manager.is_blink_timer_active() {
2347 result.timers_removed.insert(azul_core::task::CURSOR_BLINK_TIMER_ID);
2348 self.cursor_manager.set_blink_timer_active(false);
2349 }
2350 }
2351 CallbackChange::ScrollActiveCursorIntoView => {
2352 self.scroll_active_cursor_into_view(&mut result);
2354 }
2355 CallbackChange::CreateTextInput { text } => {
2356 let affected_nodes = self.process_text_input(text.as_str());
2361
2362 for (node, (events, _)) in affected_nodes {
2365 result.text_input_triggered.push((node, events));
2366 }
2367 }
2368 CallbackChange::BeginInteractiveMove => {
2369 result.begin_interactive_move = true;
2372 }
2373 CallbackChange::SetDragData { mime_type, data } => {
2374 if let Some(ctx) = self.gesture_drag_manager.get_drag_context_mut() {
2376 if let Some(node_drag) = ctx.as_node_drag_mut() {
2377 node_drag.drag_data.set_data(mime_type, data);
2378 }
2379 }
2380 }
2381 CallbackChange::AcceptDrop => {
2382 if let Some(ctx) = self.gesture_drag_manager.get_drag_context_mut() {
2384 if let Some(node_drag) = ctx.as_node_drag_mut() {
2385 node_drag.drop_accepted = true;
2386 }
2387 }
2388 }
2389 CallbackChange::SetDropEffect { effect } => {
2390 if let Some(ctx) = self.gesture_drag_manager.get_drag_context_mut() {
2392 if let Some(node_drag) = ctx.as_node_drag_mut() {
2393 node_drag.drop_effect = effect;
2394 }
2395 }
2396 }
2397 }
2398 }
2399
2400 self.sync_cursor_to_selection_manager();
2403
2404 result
2405 }
2406
2407 fn get_inline_layout_for_node(
2417 &self,
2418 dom_id: DomId,
2419 node_id: NodeId,
2420 ) -> Option<&Arc<UnifiedLayout>> {
2421 let layout_result = self.layout_results.get(&dom_id)?;
2422
2423 let layout_indices = layout_result.layout_tree.dom_to_layout.get(&node_id)?;
2424 let layout_index = *layout_indices.first()?;
2425
2426 layout_result.layout_tree.get_inline_layout_for_node(layout_index)
2428 }
2429
2430 fn move_cursor_in_node<F>(
2432 &self,
2433 dom_id: DomId,
2434 node_id: NodeId,
2435 movement_fn: F,
2436 ) -> Option<TextCursor>
2437 where
2438 F: FnOnce(&UnifiedLayout, &TextCursor) -> TextCursor,
2439 {
2440 let current_cursor = self.cursor_manager.get_cursor()?;
2441 let layout = self.get_inline_layout_for_node(dom_id, node_id)?;
2442
2443 let new_cursor = movement_fn(layout, current_cursor);
2444
2445 if new_cursor != *current_cursor {
2447 Some(new_cursor)
2448 } else {
2449 None
2450 }
2451 }
2452
2453 fn handle_cursor_movement(
2455 &mut self,
2456 dom_id: DomId,
2457 node_id: NodeId,
2458 new_cursor: TextCursor,
2459 extend_selection: bool,
2460 ) {
2461 if extend_selection {
2462 if let Some(old_cursor) = self.cursor_manager.get_cursor() {
2464 let dom_node_id = azul_core::dom::DomNodeId {
2466 dom: dom_id,
2467 node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
2468 };
2469
2470 let selection_range = if new_cursor.cluster_id.start_byte_in_run
2472 < old_cursor.cluster_id.start_byte_in_run
2473 {
2474 SelectionRange {
2476 start: new_cursor,
2477 end: *old_cursor,
2478 }
2479 } else {
2480 SelectionRange {
2482 start: *old_cursor,
2483 end: new_cursor,
2484 }
2485 };
2486
2487 self.selection_manager
2489 .set_range(dom_id, dom_node_id, selection_range);
2490 }
2491
2492 self.cursor_manager
2494 .move_cursor_to(new_cursor, dom_id, node_id);
2495 } else {
2496 self.cursor_manager
2498 .move_cursor_to(new_cursor, dom_id, node_id);
2499
2500 self.selection_manager.clear_selection(&dom_id);
2502 }
2503 }
2504
2505 pub fn get_gpu_cache(&self, dom_id: &DomId) -> Option<&GpuValueCache> {
2509 self.gpu_state_manager.caches.get(dom_id)
2510 }
2511
2512 pub fn get_gpu_cache_mut(&mut self, dom_id: &DomId) -> Option<&mut GpuValueCache> {
2514 self.gpu_state_manager.caches.get_mut(dom_id)
2515 }
2516
2517 pub fn get_or_create_gpu_cache(&mut self, dom_id: DomId) -> &mut GpuValueCache {
2519 self.gpu_state_manager
2520 .caches
2521 .entry(dom_id)
2522 .or_insert_with(GpuValueCache::default)
2523 }
2524
2525 pub fn get_layout_result(&self, dom_id: &DomId) -> Option<&DomLayoutResult> {
2529 self.layout_results.get(dom_id)
2530 }
2531
2532 pub fn get_layout_result_mut(&mut self, dom_id: &DomId) -> Option<&mut DomLayoutResult> {
2534 self.layout_results.get_mut(dom_id)
2535 }
2536
2537 pub fn get_dom_ids(&self) -> DomIdVec {
2539 self.layout_results
2540 .keys()
2541 .copied()
2542 .collect::<Vec<_>>()
2543 .into()
2544 }
2545
2546 pub fn compute_cursor_type_hit_test(
2553 &self,
2554 hit_test: &crate::hit_test::FullHitTest,
2555 ) -> crate::hit_test::CursorTypeHitTest {
2556 crate::hit_test::CursorTypeHitTest::new(hit_test, self)
2557 }
2558
2559 fn calculate_scrollbar_opacity(
2586 last_activity: Option<Instant>,
2587 now: Instant,
2588 fade_delay: Duration,
2589 fade_duration: Duration,
2590 ) -> f32 {
2591 let Some(last_activity) = last_activity else {
2592 return 0.0;
2593 };
2594
2595 let time_since_activity = now.duration_since(&last_activity);
2596
2597 if time_since_activity.div(&fade_delay) < 1.0 {
2599 return 1.0;
2600 }
2601
2602 let time_into_fade_ms = time_since_activity.div(&fade_delay) - 1.0;
2604 let fade_progress = (time_into_fade_ms * fade_duration.div(&fade_duration)).min(1.0);
2605
2606 (1.0 - fade_progress).max(0.0)
2608 }
2609
2610 pub fn synchronize_scrollbar_opacity(
2615 gpu_state_manager: &mut GpuStateManager,
2616 scroll_manager: &ScrollManager,
2617 dom_id: DomId,
2618 layout_tree: &LayoutTree,
2619 system_callbacks: &ExternalSystemCallbacks,
2620 fade_delay: azul_core::task::Duration,
2621 fade_duration: azul_core::task::Duration,
2622 ) -> Vec<azul_core::gpu::GpuScrollbarOpacityEvent> {
2623 let mut events = Vec::new();
2624 let gpu_cache = gpu_state_manager.caches.entry(dom_id).or_default();
2625
2626 let now = (system_callbacks.get_system_time_fn.cb)();
2628
2629 for (node_idx, node) in layout_tree.nodes.iter().enumerate() {
2631 let scrollbar_info = match &node.scrollbar_info {
2633 Some(info) => info,
2634 None => continue,
2635 };
2636
2637 let node_id = match node.dom_node_id {
2638 Some(nid) => nid,
2639 None => continue, };
2641
2642 let vertical_opacity = if scrollbar_info.needs_vertical {
2644 Self::calculate_scrollbar_opacity(
2645 scroll_manager.get_last_activity_time(dom_id, node_id),
2646 now.clone(),
2647 fade_delay,
2648 fade_duration,
2649 )
2650 } else {
2651 0.0
2652 };
2653
2654 let horizontal_opacity = if scrollbar_info.needs_horizontal {
2655 Self::calculate_scrollbar_opacity(
2656 scroll_manager.get_last_activity_time(dom_id, node_id),
2657 now.clone(),
2658 fade_delay,
2659 fade_duration,
2660 )
2661 } else {
2662 0.0
2663 };
2664
2665 if scrollbar_info.needs_vertical && vertical_opacity > 0.001 {
2667 let key = (dom_id, node_id);
2668 let existing = gpu_cache.scrollbar_v_opacity_values.get(&key);
2669
2670 match existing {
2671 None => {
2672 let opacity_key = OpacityKey::unique();
2673 gpu_cache.scrollbar_v_opacity_keys.insert(key, opacity_key);
2674 gpu_cache
2675 .scrollbar_v_opacity_values
2676 .insert(key, vertical_opacity);
2677 events.push(GpuScrollbarOpacityEvent::VerticalAdded(
2678 dom_id,
2679 node_id,
2680 opacity_key,
2681 vertical_opacity,
2682 ));
2683 }
2684 Some(&old_opacity) if (old_opacity - vertical_opacity).abs() > 0.001 => {
2685 let opacity_key = gpu_cache.scrollbar_v_opacity_keys[&key];
2686 gpu_cache
2687 .scrollbar_v_opacity_values
2688 .insert(key, vertical_opacity);
2689 events.push(GpuScrollbarOpacityEvent::VerticalChanged(
2690 dom_id,
2691 node_id,
2692 opacity_key,
2693 old_opacity,
2694 vertical_opacity,
2695 ));
2696 }
2697 _ => {}
2698 }
2699 } else {
2700 let key = (dom_id, node_id);
2702 if let Some(opacity_key) = gpu_cache.scrollbar_v_opacity_keys.remove(&key) {
2703 gpu_cache.scrollbar_v_opacity_values.remove(&key);
2704 events.push(GpuScrollbarOpacityEvent::VerticalRemoved(
2705 dom_id,
2706 node_id,
2707 opacity_key,
2708 ));
2709 }
2710 }
2711
2712 if scrollbar_info.needs_horizontal && horizontal_opacity > 0.001 {
2714 let key = (dom_id, node_id);
2715 let existing = gpu_cache.scrollbar_h_opacity_values.get(&key);
2716
2717 match existing {
2718 None => {
2719 let opacity_key = OpacityKey::unique();
2720 gpu_cache.scrollbar_h_opacity_keys.insert(key, opacity_key);
2721 gpu_cache
2722 .scrollbar_h_opacity_values
2723 .insert(key, horizontal_opacity);
2724 events.push(GpuScrollbarOpacityEvent::HorizontalAdded(
2725 dom_id,
2726 node_id,
2727 opacity_key,
2728 horizontal_opacity,
2729 ));
2730 }
2731 Some(&old_opacity) if (old_opacity - horizontal_opacity).abs() > 0.001 => {
2732 let opacity_key = gpu_cache.scrollbar_h_opacity_keys[&key];
2733 gpu_cache
2734 .scrollbar_h_opacity_values
2735 .insert(key, horizontal_opacity);
2736 events.push(GpuScrollbarOpacityEvent::HorizontalChanged(
2737 dom_id,
2738 node_id,
2739 opacity_key,
2740 old_opacity,
2741 horizontal_opacity,
2742 ));
2743 }
2744 _ => {}
2745 }
2746 } else {
2747 let key = (dom_id, node_id);
2749 if let Some(opacity_key) = gpu_cache.scrollbar_h_opacity_keys.remove(&key) {
2750 gpu_cache.scrollbar_h_opacity_values.remove(&key);
2751 events.push(GpuScrollbarOpacityEvent::HorizontalRemoved(
2752 dom_id,
2753 node_id,
2754 opacity_key,
2755 ));
2756 }
2757 }
2758 }
2759
2760 events
2761 }
2762
2763 pub fn compute_scroll_ids(
2772 layout_tree: &LayoutTree,
2773 styled_dom: &azul_core::styled_dom::StyledDom,
2774 ) -> (BTreeMap<usize, u64>, BTreeMap<u64, NodeId>) {
2775 use azul_css::props::layout::LayoutOverflow;
2776
2777 use crate::solver3::getters::{get_overflow_x, get_overflow_y};
2778
2779 let mut scroll_ids = BTreeMap::new();
2780 let mut scroll_id_to_node_id = BTreeMap::new();
2781
2782 for (layout_idx, node) in layout_tree.nodes.iter().enumerate() {
2784 let Some(dom_node_id) = node.dom_node_id else {
2785 continue;
2786 };
2787
2788 let styled_node_state = styled_dom
2790 .styled_nodes
2791 .as_container()
2792 .get(dom_node_id)
2793 .map(|n| n.styled_node_state.clone())
2794 .unwrap_or_default();
2795
2796 let overflow_x = get_overflow_x(styled_dom, dom_node_id, &styled_node_state);
2798 let overflow_y = get_overflow_y(styled_dom, dom_node_id, &styled_node_state);
2799
2800 let is_scrollable = overflow_x.is_scroll() || overflow_y.is_scroll();
2801
2802 if !is_scrollable {
2803 continue;
2804 }
2805
2806 let scroll_id = node.node_data_hash;
2809
2810 scroll_ids.insert(layout_idx, scroll_id);
2811 scroll_id_to_node_id.insert(scroll_id, dom_node_id);
2812 }
2813
2814 (scroll_ids, scroll_id_to_node_id)
2815 }
2816
2817 pub fn get_node_layout_rect(
2824 &self,
2825 node_id: azul_core::dom::DomNodeId,
2826 ) -> Option<azul_core::geom::LogicalRect> {
2827 let layout_tree = self.layout_cache.tree.as_ref()?;
2829
2830 let target_node_id = node_id.node.into_crate_internal();
2833 let layout_idx = layout_tree
2834 .nodes
2835 .iter()
2836 .position(|node| node.dom_node_id == target_node_id)?;
2837
2838 let calc_pos = self.layout_cache.calculated_positions.get(layout_idx)?;
2840
2841 let layout_node = layout_tree.nodes.get(layout_idx)?;
2843
2844 let used_size = layout_node.used_size?;
2846
2847 let hidpi_factor = self
2849 .current_window_state
2850 .size
2851 .get_hidpi_factor()
2852 .inner
2853 .get();
2854
2855 Some(LogicalRect::new(
2856 LogicalPosition::new(calc_pos.x as f32, calc_pos.y as f32),
2857 LogicalSize::new(
2858 used_size.width / hidpi_factor,
2859 used_size.height / hidpi_factor,
2860 ),
2861 ))
2862 }
2863
2864 pub fn get_focused_cursor_rect(&self) -> Option<azul_core::geom::LogicalRect> {
2879 let focused_node = self.focus_manager.focused_node?;
2881
2882 let cursor = self.cursor_manager.get_cursor()?;
2884
2885 let layout_tree = self.layout_cache.tree.as_ref()?;
2887
2888 let target_node_id = focused_node.node.into_crate_internal();
2890 let layout_idx = layout_tree
2891 .nodes
2892 .iter()
2893 .position(|node| node.dom_node_id == target_node_id)?;
2894
2895 let layout_node = layout_tree.nodes.get(layout_idx)?;
2897
2898 let cached_layout = layout_node.inline_layout_result.as_ref()?;
2900 let inline_layout = &cached_layout.layout;
2901
2902 let mut cursor_rect = inline_layout.get_cursor_rect(cursor)?;
2904
2905 let calc_pos = self.layout_cache.calculated_positions.get(layout_idx)?;
2907
2908 cursor_rect.origin.x += calc_pos.x as f32;
2910 cursor_rect.origin.y += calc_pos.y as f32;
2911
2912 Some(cursor_rect)
2914 }
2915
2916 pub fn get_focused_cursor_rect_viewport(&self) -> Option<azul_core::geom::LogicalRect> {
2934 let mut cursor_rect = self.get_focused_cursor_rect()?;
2936
2937 let focused_node = self.focus_manager.focused_node?;
2939
2940 let layout_tree = self.layout_cache.tree.as_ref()?;
2942
2943 let target_node_id = focused_node.node.into_crate_internal();
2945 let layout_idx = layout_tree
2946 .nodes
2947 .iter()
2948 .position(|node| node.dom_node_id == target_node_id)?;
2949
2950 let gpu_cache = self.gpu_state_manager.caches.get(&focused_node.dom);
2952
2953 let mut current_layout_idx = layout_idx;
2957
2958 while let Some(parent_idx) = layout_tree.nodes.get(current_layout_idx)?.parent {
2959 if let Some(parent_dom_node_id) = layout_tree.nodes.get(parent_idx)?.dom_node_id {
2961 if let Some(scroll_state) = self
2963 .scroll_manager
2964 .get_scroll_state(focused_node.dom, parent_dom_node_id)
2965 {
2966 cursor_rect.origin.x -= scroll_state.current_offset.x;
2968 cursor_rect.origin.y -= scroll_state.current_offset.y;
2969 }
2970
2971 if let Some(cache) = gpu_cache {
2973 if let Some(transform) = cache.current_transform_values.get(&parent_dom_node_id)
2974 {
2975 let inverse = transform.inverse();
2978 if let Some(transformed_origin) =
2979 inverse.transform_point2d(cursor_rect.origin)
2980 {
2981 cursor_rect.origin = transformed_origin;
2982 }
2983 }
2985 }
2986 }
2987
2988 current_layout_idx = parent_idx;
2990 }
2991
2992 Some(cursor_rect)
2993 }
2994
2995 pub fn find_scrollable_ancestor(
2999 &self,
3000 mut node_id: azul_core::dom::DomNodeId,
3001 ) -> Option<azul_core::dom::DomNodeId> {
3002 let layout_tree = self.layout_cache.tree.as_ref()?;
3004
3005 let mut current_node_id = node_id.node.into_crate_internal();
3007
3008 loop {
3010 let layout_idx = layout_tree
3012 .nodes
3013 .iter()
3014 .position(|node| node.dom_node_id == current_node_id)?;
3015
3016 let layout_node = layout_tree.nodes.get(layout_idx)?;
3017
3018 if layout_node.scrollbar_info.is_some() {
3020 let check_node_id = current_node_id?;
3022 if self
3023 .scroll_manager
3024 .get_scroll_state(node_id.dom, check_node_id)
3025 .is_some()
3026 {
3027 return Some(azul_core::dom::DomNodeId {
3029 dom: node_id.dom,
3030 node: azul_core::styled_dom::NodeHierarchyItemId::from_crate_internal(
3031 Some(check_node_id),
3032 ),
3033 });
3034 }
3035 }
3036
3037 let parent_idx = layout_node.parent?;
3039 let parent_node = layout_tree.nodes.get(parent_idx)?;
3040 current_node_id = parent_node.dom_node_id;
3041 }
3042 }
3043
3044 pub fn scroll_selection_into_view(
3070 &mut self,
3071 scroll_type: SelectionScrollType,
3072 scroll_mode: ScrollMode,
3073 ) -> bool {
3074 let bounds = match scroll_type {
3076 SelectionScrollType::Cursor => {
3077 match self.get_focused_cursor_rect() {
3079 Some(rect) => rect,
3080 None => return false, }
3082 }
3083 SelectionScrollType::Selection => {
3084 match self.get_focused_cursor_rect() {
3087 Some(rect) => rect,
3088 None => return false, }
3090 }
3097 SelectionScrollType::DragSelection { mouse_position } => {
3098 LogicalRect::new(mouse_position, LogicalSize::zero())
3100 }
3101 };
3102
3103 let focused_node = match self.focus_manager.focused_node {
3105 Some(node) => node,
3106 None => return false,
3107 };
3108
3109 let scroll_container = match self.find_scrollable_ancestor(focused_node) {
3111 Some(node) => node,
3112 None => return false, };
3114
3115 let layout_tree = match self.layout_cache.tree.as_ref() {
3117 Some(tree) => tree,
3118 None => return false,
3119 };
3120
3121 let scrollable_node_internal = match scroll_container.node.into_crate_internal() {
3122 Some(id) => id,
3123 None => return false,
3124 };
3125
3126 let layout_idx = match layout_tree
3127 .nodes
3128 .iter()
3129 .position(|n| n.dom_node_id == Some(scrollable_node_internal))
3130 {
3131 Some(idx) => idx,
3132 None => return false,
3133 };
3134
3135 let scrollable_layout_node = match layout_tree.nodes.get(layout_idx) {
3136 Some(node) => node,
3137 None => return false,
3138 };
3139
3140 let container_pos = self
3141 .layout_cache
3142 .calculated_positions
3143 .get(layout_idx)
3144 .copied()
3145 .unwrap_or_default();
3146
3147 let container_size = scrollable_layout_node.used_size.unwrap_or_default();
3148
3149 let container_rect = LogicalRect {
3150 origin: container_pos,
3151 size: container_size,
3152 };
3153
3154 let scroll_state = match self
3156 .scroll_manager
3157 .get_scroll_state(scroll_container.dom, scrollable_node_internal)
3158 {
3159 Some(state) => state,
3160 None => return false,
3161 };
3162
3163 let visible_area = LogicalRect::new(
3165 LogicalPosition::new(
3166 container_rect.origin.x + scroll_state.current_offset.x,
3167 container_rect.origin.y + scroll_state.current_offset.y,
3168 ),
3169 container_rect.size,
3170 );
3171
3172 let scroll_delta = match scroll_mode {
3174 ScrollMode::Instant => {
3175 calculate_instant_scroll_delta(bounds, visible_area)
3177 }
3178 ScrollMode::Accelerated => {
3179 let distance = calculate_edge_distance(bounds, visible_area);
3181 calculate_accelerated_scroll_delta(distance)
3182 }
3183 };
3184
3185 if scroll_delta.x != 0.0 || scroll_delta.y != 0.0 {
3187 let duration = match scroll_mode {
3188 ScrollMode::Instant => Duration::System(SystemTimeDiff { secs: 0, nanos: 0 }),
3189 ScrollMode::Accelerated => Duration::System(SystemTimeDiff {
3190 secs: 0,
3191 nanos: 16_666_667,
3192 }), };
3194
3195 let external = ExternalSystemCallbacks::rust_internal();
3196 let now = (external.get_system_time_fn.cb)();
3197
3198 let new_target = LogicalPosition {
3200 x: scroll_state.current_offset.x + scroll_delta.x,
3201 y: scroll_state.current_offset.y + scroll_delta.y,
3202 };
3203
3204 self.scroll_manager.scroll_to(
3205 scroll_container.dom,
3206 scrollable_node_internal,
3207 new_target,
3208 duration,
3209 EasingFunction::Linear,
3210 now.into(),
3211 );
3212
3213 true } else {
3215 false }
3217 }
3218
3219 fn scroll_focused_cursor_into_view(&mut self) {
3234 self.scroll_selection_into_view(SelectionScrollType::Cursor, ScrollMode::Instant);
3236 }
3237}
3238
3239#[derive(Debug, Clone, Copy)]
3241pub enum SelectionScrollType {
3242 Cursor,
3244 Selection,
3246 DragSelection { mouse_position: LogicalPosition },
3248}
3249
3250#[derive(Debug, Clone, Copy)]
3252pub enum ScrollMode {
3253 Instant,
3255 Accelerated,
3257}
3258
3259#[derive(Debug, Clone, Copy)]
3261struct EdgeDistance {
3262 left: f32,
3263 right: f32,
3264 top: f32,
3265 bottom: f32,
3266}
3267
3268fn calculate_edge_distance(rect: LogicalRect, container: LogicalRect) -> EdgeDistance {
3270 EdgeDistance {
3271 left: (rect.origin.x - container.origin.x).max(0.0),
3273 right: ((container.origin.x + container.size.width) - (rect.origin.x + rect.size.width))
3275 .max(0.0),
3276 top: (rect.origin.y - container.origin.y).max(0.0),
3278 bottom: ((container.origin.y + container.size.height) - (rect.origin.y + rect.size.height))
3280 .max(0.0),
3281 }
3282}
3283
3284fn calculate_instant_scroll_delta(
3286 bounds: LogicalRect,
3287 visible_area: LogicalRect,
3288) -> LogicalPosition {
3289 const PADDING: f32 = 5.0;
3290 let mut delta = LogicalPosition::zero();
3291
3292 if bounds.origin.x < visible_area.origin.x + PADDING {
3294 delta.x = bounds.origin.x - visible_area.origin.x - PADDING;
3295 } else if bounds.origin.x + bounds.size.width
3296 > visible_area.origin.x + visible_area.size.width - PADDING
3297 {
3298 delta.x = (bounds.origin.x + bounds.size.width)
3299 - (visible_area.origin.x + visible_area.size.width)
3300 + PADDING;
3301 }
3302
3303 if bounds.origin.y < visible_area.origin.y + PADDING {
3305 delta.y = bounds.origin.y - visible_area.origin.y - PADDING;
3306 } else if bounds.origin.y + bounds.size.height
3307 > visible_area.origin.y + visible_area.size.height - PADDING
3308 {
3309 delta.y = (bounds.origin.y + bounds.size.height)
3310 - (visible_area.origin.y + visible_area.size.height)
3311 + PADDING;
3312 }
3313
3314 delta
3315}
3316
3317fn calculate_accelerated_scroll_delta(distance: EdgeDistance) -> LogicalPosition {
3319 const DEAD_ZONE: f32 = 20.0;
3321 const SLOW_ZONE: f32 = 50.0;
3322 const MEDIUM_ZONE: f32 = 100.0;
3323 const FAST_ZONE: f32 = 200.0;
3324
3325 const SLOW_SPEED: f32 = 2.0;
3327 const MEDIUM_SPEED: f32 = 4.0;
3328 const FAST_SPEED: f32 = 8.0;
3329 const VERY_FAST_SPEED: f32 = 16.0;
3330
3331 let speed_for_distance = |dist: f32| -> f32 {
3333 if dist < DEAD_ZONE {
3334 0.0
3335 } else if dist < SLOW_ZONE {
3336 SLOW_SPEED
3337 } else if dist < MEDIUM_ZONE {
3338 MEDIUM_SPEED
3339 } else if dist < FAST_ZONE {
3340 FAST_SPEED
3341 } else {
3342 VERY_FAST_SPEED
3343 }
3344 };
3345
3346 let scroll_x = if distance.left < distance.right {
3348 -speed_for_distance(distance.left)
3350 } else {
3351 speed_for_distance(distance.right)
3353 };
3354
3355 let scroll_y = if distance.top < distance.bottom {
3357 -speed_for_distance(distance.top)
3359 } else {
3360 speed_for_distance(distance.bottom)
3362 };
3363
3364 LogicalPosition::new(scroll_x, scroll_y)
3365}
3366
3367pub struct LayoutResult {
3369 pub display_list: DisplayList,
3370 pub warnings: Vec<String>,
3371}
3372
3373impl LayoutResult {
3374 pub fn new(display_list: DisplayList, warnings: Vec<String>) -> Self {
3375 Self {
3376 display_list,
3377 warnings,
3378 }
3379 }
3380}
3381
3382impl LayoutWindow {
3383 #[cfg(feature = "std")]
3388 pub fn run_single_timer(
3389 &mut self,
3390 timer_id: usize,
3391 frame_start: Instant,
3392 current_window_handle: &RawWindowHandle,
3393 gl_context: &OptionGlContextPtr,
3394 image_cache: &mut ImageCache,
3395 system_fonts: &mut FcFontCache,
3396 system_style: std::sync::Arc<azul_css::system::SystemStyle>,
3397 system_callbacks: &ExternalSystemCallbacks,
3398 previous_window_state: &Option<FullWindowState>,
3399 current_window_state: &FullWindowState,
3400 renderer_resources: &RendererResources,
3401 ) -> CallCallbacksResult {
3402 use std::collections::BTreeMap;
3403
3404 use crate::callbacks::{CallCallbacksResult, CallbackInfo};
3405
3406 let mut ret = CallCallbacksResult {
3407 should_scroll_render: false,
3408 callbacks_update_screen: Update::DoNothing,
3409 modified_window_state: None,
3410 css_properties_changed: None,
3411 words_changed: None,
3412 images_changed: None,
3413 image_masks_changed: None,
3414 image_callbacks_changed: None,
3415 nodes_scrolled_in_callbacks: None,
3416 update_focused_node: FocusUpdateRequest::NoChange,
3417 timers: None,
3418 threads: None,
3419 timers_removed: None,
3420 threads_removed: None,
3421 windows_created: Vec::new(),
3422 menus_to_open: Vec::new(),
3423 tooltips_to_show: Vec::new(),
3424 hide_tooltip: false,
3425 cursor_changed: false,
3426 stop_propagation: false,
3427 stop_immediate_propagation: false,
3428 prevent_default: false,
3429 hit_test_update_requested: None,
3430 queued_window_states: Vec::new(),
3431 text_input_triggered: Vec::new(),
3432 begin_interactive_move: false,
3433 };
3434
3435 let mut should_terminate = TerminateTimer::Continue;
3436
3437 let current_scroll_states_nested = self.get_nested_scroll_states(DomId::ROOT_ID);
3438
3439 let timer_exists = self.timers.contains_key(&TimerId { id: timer_id });
3441 let timer_node_id = self
3442 .timers
3443 .get(&TimerId { id: timer_id })
3444 .and_then(|t| t.node_id.into_option());
3445
3446 if timer_exists {
3447 let hit_dom_node = match timer_node_id {
3449 Some(s) => s,
3450 None => DomNodeId {
3451 dom: DomId::ROOT_ID,
3452 node: NodeHierarchyItemId::from_crate_internal(None),
3453 },
3454 };
3455 let cursor_relative_to_item = OptionLogicalPosition::None;
3456 let cursor_in_viewport = OptionLogicalPosition::None;
3457
3458 let callback_changes = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
3462
3463 let timer_ctx = self
3466 .timers
3467 .get(&TimerId { id: timer_id })
3468 .map(|t| t.callback.ctx.clone())
3469 .unwrap_or(OptionRefAny::None);
3470
3471 let ref_data = crate::callbacks::CallbackInfoRefData {
3472 layout_window: self,
3473 renderer_resources,
3474 previous_window_state,
3475 current_window_state,
3476 gl_context,
3477 current_scroll_manager: ¤t_scroll_states_nested,
3478 current_window_handle,
3479 system_callbacks,
3480 system_style,
3481 monitors: self.monitors.clone(),
3482 #[cfg(feature = "icu")]
3483 icu_localizer: self.icu_localizer.clone(),
3484 ctx: timer_ctx,
3485 };
3486
3487 let callback_info = CallbackInfo::new(
3488 &ref_data,
3489 &callback_changes,
3490 hit_dom_node,
3491 cursor_relative_to_item,
3492 cursor_in_viewport,
3493 ); let timer = self.timers.get_mut(&TimerId { id: timer_id }).unwrap();
3495 let tcr = timer.invoke(&callback_info, &system_callbacks.get_system_time_fn);
3496
3497 ret.callbacks_update_screen = tcr.should_update;
3498 should_terminate = tcr.should_terminate;
3499
3500 let collected_changes = callback_changes
3503 .lock()
3504 .map(|mut guard| core::mem::take(&mut *guard))
3505 .unwrap_or_default();
3506
3507 let change_result = self.apply_callback_changes(
3509 collected_changes,
3510 current_window_state,
3511 image_cache,
3512 system_fonts,
3513 );
3514
3515 if !change_result.iframes_to_update.is_empty() {
3517 self.queue_iframe_updates(change_result.iframes_to_update.clone());
3518 }
3519
3520 ret.stop_propagation = change_result.stop_propagation;
3522 ret.prevent_default = change_result.prevent_default;
3523 ret.tooltips_to_show = change_result.tooltips_to_show;
3524 ret.hide_tooltip = change_result.hide_tooltip;
3525
3526 if !change_result.timers.is_empty() {
3527 ret.timers = Some(change_result.timers);
3528 }
3529 if !change_result.threads.is_empty() {
3530 ret.threads = Some(change_result.threads);
3531 }
3532 if change_result.modified_window_state != *current_window_state {
3533 ret.modified_window_state = Some(change_result.modified_window_state);
3534 }
3535 if !change_result.threads_removed.is_empty() {
3536 ret.threads_removed = Some(change_result.threads_removed);
3537 }
3538 if !change_result.timers_removed.is_empty() {
3539 ret.timers_removed = Some(change_result.timers_removed);
3540 }
3541 if !change_result.words_changed.is_empty() {
3542 ret.words_changed = Some(change_result.words_changed);
3543 }
3544 if !change_result.images_changed.is_empty() {
3545 ret.images_changed = Some(change_result.images_changed);
3546 }
3547 if !change_result.image_masks_changed.is_empty() {
3548 ret.image_masks_changed = Some(change_result.image_masks_changed);
3549 }
3550 if !change_result.css_properties_changed.is_empty() {
3551 ret.css_properties_changed = Some(change_result.css_properties_changed);
3552 }
3553 if !change_result.image_callbacks_changed.is_empty() {
3554 ret.image_callbacks_changed = Some(change_result.image_callbacks_changed);
3555 }
3556 if !change_result.nodes_scrolled.is_empty() {
3557 ret.nodes_scrolled_in_callbacks = Some(change_result.nodes_scrolled);
3558 }
3559
3560 if change_result.hit_test_update_requested.is_some() {
3562 ret.hit_test_update_requested = change_result.hit_test_update_requested;
3563 }
3564
3565 if !change_result.queued_window_states.is_empty() {
3567 ret.queued_window_states = change_result.queued_window_states;
3568 }
3569
3570 if !change_result.text_input_triggered.is_empty() {
3572 ret.text_input_triggered = change_result.text_input_triggered;
3573 }
3574
3575 if change_result.begin_interactive_move {
3577 ret.begin_interactive_move = true;
3578 }
3579
3580 if let Some(ft) = change_result.focus_target {
3582 if let Ok(new_focus_node) = crate::managers::focus_cursor::resolve_focus_target(
3583 &ft,
3584 &self.layout_results,
3585 self.focus_manager.get_focused_node().copied(),
3586 ) {
3587 ret.update_focused_node = match new_focus_node {
3588 Some(node) => FocusUpdateRequest::FocusNode(node),
3589 None => FocusUpdateRequest::ClearFocus,
3590 };
3591 }
3592 }
3593 }
3594
3595 if should_terminate == TerminateTimer::Terminate {
3596 ret.timers_removed
3597 .get_or_insert_with(|| std::collections::BTreeSet::new())
3598 .insert(TimerId { id: timer_id });
3599 }
3600
3601 return ret;
3602 }
3603
3604 #[cfg(feature = "std")]
3605 pub fn run_all_threads(
3606 &mut self,
3607 data: &mut RefAny,
3608 current_window_handle: &RawWindowHandle,
3609 gl_context: &OptionGlContextPtr,
3610 image_cache: &mut ImageCache,
3611 system_fonts: &mut FcFontCache,
3612 system_style: std::sync::Arc<azul_css::system::SystemStyle>,
3613 system_callbacks: &ExternalSystemCallbacks,
3614 previous_window_state: &Option<FullWindowState>,
3615 current_window_state: &FullWindowState,
3616 renderer_resources: &RendererResources,
3617 ) -> CallCallbacksResult {
3618 use std::collections::BTreeSet;
3619
3620 use crate::{
3621 callbacks::{CallCallbacksResult, CallbackInfo},
3622 thread::{OptionThreadReceiveMsg, ThreadReceiveMsg, ThreadWriteBackMsg},
3623 };
3624
3625 let mut ret = CallCallbacksResult {
3626 should_scroll_render: false,
3627 callbacks_update_screen: Update::DoNothing,
3628 modified_window_state: None,
3629 css_properties_changed: None,
3630 words_changed: None,
3631 images_changed: None,
3632 image_masks_changed: None,
3633 image_callbacks_changed: None,
3634 nodes_scrolled_in_callbacks: None,
3635 update_focused_node: FocusUpdateRequest::NoChange,
3636 timers: None,
3637 threads: None,
3638 timers_removed: None,
3639 threads_removed: None,
3640 windows_created: Vec::new(),
3641 menus_to_open: Vec::new(),
3642 tooltips_to_show: Vec::new(),
3643 hide_tooltip: false,
3644 cursor_changed: false,
3645 stop_propagation: false,
3646 stop_immediate_propagation: false,
3647 prevent_default: false,
3648 hit_test_update_requested: None,
3649 queued_window_states: Vec::new(),
3650 text_input_triggered: Vec::new(),
3651 begin_interactive_move: false,
3652 };
3653
3654 let mut ret_modified_window_state = current_window_state.clone();
3655 let ret_window_state = ret_modified_window_state.clone();
3656 let mut ret_timers = FastHashMap::new();
3657 let mut ret_timers_removed = FastBTreeSet::new();
3658 let mut ret_threads = FastHashMap::new();
3659 let mut ret_threads_removed = FastBTreeSet::new();
3660 let mut ret_words_changed = BTreeMap::new();
3661 let mut ret_images_changed = BTreeMap::new();
3662 let mut ret_image_masks_changed = BTreeMap::new();
3663 let mut ret_css_properties_changed = BTreeMap::new();
3664 let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
3665 let mut new_focus_target = None;
3666 let mut stop_propagation = false;
3667 let current_scroll_states = self.get_nested_scroll_states(DomId::ROOT_ID);
3668
3669 let thread_ids: Vec<ThreadId> = self.threads.keys().copied().collect();
3671
3672 for thread_id in thread_ids {
3673 let thread = match self.threads.get_mut(&thread_id) {
3674 Some(t) => t,
3675 None => continue,
3676 };
3677
3678 let hit_dom_node = DomNodeId {
3679 dom: DomId::ROOT_ID,
3680 node: NodeHierarchyItemId::from_crate_internal(None),
3681 };
3682 let cursor_relative_to_item = OptionLogicalPosition::None;
3683 let cursor_in_viewport = OptionLogicalPosition::None;
3684
3685 let (msg, writeback_data_ptr, is_finished) = {
3687 let thread_inner = &mut *match thread.ptr.lock().ok() {
3688 Some(s) => s,
3689 None => {
3690 ret.threads_removed
3691 .get_or_insert_with(|| BTreeSet::default())
3692 .insert(thread_id);
3693 continue;
3694 }
3695 };
3696
3697 let _ = thread_inner.sender_send(ThreadSendMsg::Tick);
3698 let update = thread_inner.receiver_try_recv();
3699 let msg = match update {
3700 OptionThreadReceiveMsg::None => continue,
3701 OptionThreadReceiveMsg::Some(s) => s,
3702 };
3703
3704 let writeback_data_ptr: *mut RefAny = &mut thread_inner.writeback_data as *mut _;
3705 let is_finished = thread_inner.is_finished();
3706
3707 (msg, writeback_data_ptr, is_finished)
3708 };
3710
3711 let ThreadWriteBackMsg {
3712 refany: mut data,
3713 callback,
3714 } = match msg {
3715 ThreadReceiveMsg::Update(update_screen) => {
3716 ret.callbacks_update_screen.max_self(update_screen);
3717 continue;
3718 }
3719 ThreadReceiveMsg::WriteBack(t) => t,
3720 };
3721
3722 let callback_changes = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
3724
3725 let ref_data = crate::callbacks::CallbackInfoRefData {
3727 layout_window: self,
3728 renderer_resources,
3729 previous_window_state,
3730 current_window_state,
3731 gl_context,
3732 current_scroll_manager: ¤t_scroll_states,
3733 current_window_handle,
3734 system_callbacks,
3735 system_style: system_style.clone(),
3736 monitors: self.monitors.clone(),
3737 #[cfg(feature = "icu")]
3738 icu_localizer: self.icu_localizer.clone(),
3739 ctx: callback.ctx.clone(),
3740 };
3741
3742 let callback_info = CallbackInfo::new(
3743 &ref_data,
3744 &callback_changes,
3745 hit_dom_node,
3746 cursor_relative_to_item,
3747 cursor_in_viewport,
3748 );
3749 let callback_update = (callback.cb)(
3750 unsafe { (*writeback_data_ptr).clone() },
3751 data.clone(),
3752 callback_info,
3753 );
3754 ret.callbacks_update_screen.max_self(callback_update);
3755
3756 let collected_changes = callback_changes
3758 .lock()
3759 .map(|mut guard| core::mem::take(&mut *guard))
3760 .unwrap_or_default();
3761
3762 let change_result = self.apply_callback_changes(
3764 collected_changes,
3765 current_window_state,
3766 image_cache,
3767 system_fonts,
3768 );
3769
3770 self.queue_iframe_updates(change_result.iframes_to_update);
3772
3773 ret.stop_propagation = ret.stop_propagation || change_result.stop_propagation;
3774 ret.prevent_default = ret.prevent_default || change_result.prevent_default;
3775 ret.tooltips_to_show.extend(change_result.tooltips_to_show);
3776 ret.hide_tooltip = ret.hide_tooltip || change_result.hide_tooltip;
3777 ret.begin_interactive_move = ret.begin_interactive_move || change_result.begin_interactive_move;
3778
3779 if change_result.hit_test_update_requested.is_some() {
3781 ret.hit_test_update_requested = change_result.hit_test_update_requested;
3782 }
3783
3784 ret_timers.extend(change_result.timers);
3786 ret_threads.extend(change_result.threads);
3787 ret_timers_removed.extend(change_result.timers_removed);
3788 ret_threads_removed.extend(change_result.threads_removed);
3789
3790 for (dom_id, nodes) in change_result.words_changed {
3791 ret_words_changed
3792 .entry(dom_id)
3793 .or_insert_with(BTreeMap::new)
3794 .extend(nodes);
3795 }
3796 for (dom_id, nodes) in change_result.images_changed {
3797 ret_images_changed
3798 .entry(dom_id)
3799 .or_insert_with(BTreeMap::new)
3800 .extend(nodes);
3801 }
3802 for (dom_id, nodes) in change_result.image_masks_changed {
3803 ret_image_masks_changed
3804 .entry(dom_id)
3805 .or_insert_with(BTreeMap::new)
3806 .extend(nodes);
3807 }
3808 for (dom_id, nodes) in change_result.css_properties_changed {
3809 ret_css_properties_changed
3810 .entry(dom_id)
3811 .or_insert_with(BTreeMap::new)
3812 .extend(nodes);
3813 }
3814 for (dom_id, nodes) in change_result.nodes_scrolled {
3815 ret_nodes_scrolled_in_callbacks
3816 .entry(dom_id)
3817 .or_insert_with(BTreeMap::new)
3818 .extend(nodes);
3819 }
3820
3821 if change_result.modified_window_state != *current_window_state {
3822 ret_modified_window_state = change_result.modified_window_state;
3823 }
3824
3825 if let Some(ft) = change_result.focus_target {
3826 new_focus_target = Some(ft);
3827 }
3828
3829 if is_finished {
3830 ret.threads_removed
3831 .get_or_insert_with(|| BTreeSet::default())
3832 .insert(thread_id);
3833 }
3834 }
3835
3836 if !ret_timers.is_empty() {
3837 ret.timers = Some(ret_timers);
3838 }
3839 if !ret_threads.is_empty() {
3840 ret.threads = Some(ret_threads);
3841 }
3842 if ret_modified_window_state != ret_window_state {
3843 ret.modified_window_state = Some(ret_modified_window_state);
3844 }
3845 if !ret_threads_removed.is_empty() {
3846 ret.threads_removed = Some(ret_threads_removed);
3847 }
3848 if !ret_timers_removed.is_empty() {
3849 ret.timers_removed = Some(ret_timers_removed);
3850 }
3851 if !ret_words_changed.is_empty() {
3852 ret.words_changed = Some(ret_words_changed);
3853 }
3854 if !ret_images_changed.is_empty() {
3855 ret.images_changed = Some(ret_images_changed);
3856 }
3857 if !ret_image_masks_changed.is_empty() {
3858 ret.image_masks_changed = Some(ret_image_masks_changed);
3859 }
3860 if !ret_css_properties_changed.is_empty() {
3861 ret.css_properties_changed = Some(ret_css_properties_changed);
3862 }
3863 if !ret_nodes_scrolled_in_callbacks.is_empty() {
3864 ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
3865 }
3866
3867 if let Some(ft) = new_focus_target {
3868 if let Ok(new_focus_node) = crate::managers::focus_cursor::resolve_focus_target(
3869 &ft,
3870 &self.layout_results,
3871 self.focus_manager.get_focused_node().copied(),
3872 ) {
3873 ret.update_focused_node = match new_focus_node {
3874 Some(node) => FocusUpdateRequest::FocusNode(node),
3875 None => FocusUpdateRequest::ClearFocus,
3876 };
3877 }
3878 }
3879
3880 return ret;
3881 }
3882
3883 pub fn invoke_single_callback(
3885 &mut self,
3886 callback: &mut Callback,
3887 data: &mut RefAny,
3888 current_window_handle: &RawWindowHandle,
3889 gl_context: &OptionGlContextPtr,
3890 image_cache: &mut ImageCache,
3891 system_fonts: &mut FcFontCache,
3892 system_style: std::sync::Arc<azul_css::system::SystemStyle>,
3893 system_callbacks: &ExternalSystemCallbacks,
3894 previous_window_state: &Option<FullWindowState>,
3895 current_window_state: &FullWindowState,
3896 renderer_resources: &RendererResources,
3897 ) -> CallCallbacksResult {
3898 use crate::callbacks::{CallCallbacksResult, Callback, CallbackInfo};
3899
3900 let hit_dom_node = DomNodeId {
3901 dom: DomId::ROOT_ID,
3902 node: NodeHierarchyItemId::from_crate_internal(None),
3903 };
3904
3905 let mut ret = CallCallbacksResult {
3906 should_scroll_render: false,
3907 callbacks_update_screen: Update::DoNothing,
3908 modified_window_state: None,
3909 css_properties_changed: None,
3910 words_changed: None,
3911 images_changed: None,
3912 image_masks_changed: None,
3913 image_callbacks_changed: None,
3914 nodes_scrolled_in_callbacks: None,
3915 update_focused_node: FocusUpdateRequest::NoChange,
3916 timers: None,
3917 threads: None,
3918 timers_removed: None,
3919 threads_removed: None,
3920 windows_created: Vec::new(),
3921 menus_to_open: Vec::new(),
3922 tooltips_to_show: Vec::new(),
3923 hide_tooltip: false,
3924 cursor_changed: false,
3925 stop_propagation: false,
3926 stop_immediate_propagation: false,
3927 prevent_default: false,
3928 hit_test_update_requested: None,
3929 queued_window_states: Vec::new(),
3930 text_input_triggered: Vec::new(),
3931 begin_interactive_move: false,
3932 };
3933
3934 let mut ret_modified_window_state = current_window_state.clone();
3935 let ret_window_state = ret_modified_window_state.clone();
3936 let mut ret_timers = FastHashMap::new();
3937 let mut ret_timers_removed = FastBTreeSet::new();
3938 let mut ret_threads = FastHashMap::new();
3939 let mut ret_threads_removed = FastBTreeSet::new();
3940 let mut ret_words_changed = BTreeMap::new();
3941 let mut ret_images_changed = BTreeMap::new();
3942 let mut ret_image_masks_changed = BTreeMap::new();
3943 let mut ret_css_properties_changed = BTreeMap::new();
3944 let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
3945 let mut new_focus_target = None;
3946 let mut stop_propagation = false;
3947 let current_scroll_states = self.get_nested_scroll_states(DomId::ROOT_ID);
3948
3949 let cursor_relative_to_item = OptionLogicalPosition::None;
3950 let cursor_in_viewport = match current_window_state.mouse_state.cursor_position.get_position() {
3951 Some(pos) => OptionLogicalPosition::Some(pos),
3952 None => OptionLogicalPosition::None,
3953 };
3954
3955 let callback_changes = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
3957
3958 let ref_data = crate::callbacks::CallbackInfoRefData {
3960 layout_window: self,
3961 renderer_resources,
3962 previous_window_state,
3963 current_window_state,
3964 gl_context,
3965 current_scroll_manager: ¤t_scroll_states,
3966 current_window_handle,
3967 system_callbacks,
3968 system_style,
3969 monitors: self.monitors.clone(),
3970 #[cfg(feature = "icu")]
3971 icu_localizer: self.icu_localizer.clone(),
3972 ctx: OptionRefAny::None,
3973 };
3974
3975 let callback_info = CallbackInfo::new(
3976 &ref_data,
3977 &callback_changes,
3978 hit_dom_node,
3979 cursor_relative_to_item,
3980 cursor_in_viewport,
3981 );
3982
3983 ret.callbacks_update_screen = (callback.cb)(data.clone(), callback_info);
3984
3985 let collected_changes = callback_changes
3987 .lock()
3988 .map(|mut guard| core::mem::take(&mut *guard))
3989 .unwrap_or_default();
3990
3991 let change_result = self.apply_callback_changes(
3993 collected_changes,
3994 current_window_state,
3995 image_cache,
3996 system_fonts,
3997 );
3998
3999 self.queue_iframe_updates(change_result.iframes_to_update);
4001
4002 ret.stop_propagation = change_result.stop_propagation;
4003 ret.prevent_default = change_result.prevent_default;
4004 ret.tooltips_to_show = change_result.tooltips_to_show;
4005 ret.hide_tooltip = change_result.hide_tooltip;
4006 ret.begin_interactive_move = ret.begin_interactive_move || change_result.begin_interactive_move;
4007
4008 if change_result.hit_test_update_requested.is_some() {
4010 ret.hit_test_update_requested = change_result.hit_test_update_requested;
4011 }
4012
4013 ret_timers.extend(change_result.timers);
4014 ret_threads.extend(change_result.threads);
4015 ret_timers_removed.extend(change_result.timers_removed);
4016 ret_threads_removed.extend(change_result.threads_removed);
4017 ret_words_changed.extend(change_result.words_changed);
4018 ret_images_changed.extend(change_result.images_changed);
4019 ret_image_masks_changed.extend(change_result.image_masks_changed);
4020 ret_css_properties_changed.extend(change_result.css_properties_changed);
4021 ret_nodes_scrolled_in_callbacks.append(&mut change_result.nodes_scrolled.clone());
4022
4023 if change_result.modified_window_state != *current_window_state {
4024 ret_modified_window_state = change_result.modified_window_state;
4025 }
4026
4027 new_focus_target = change_result.focus_target.or(new_focus_target);
4028
4029 if !ret_timers.is_empty() {
4030 ret.timers = Some(ret_timers);
4031 }
4032 if !ret_threads.is_empty() {
4033 ret.threads = Some(ret_threads);
4034 }
4035 if ret_modified_window_state != ret_window_state {
4036 ret.modified_window_state = Some(ret_modified_window_state);
4037 }
4038 if !ret_threads_removed.is_empty() {
4039 ret.threads_removed = Some(ret_threads_removed);
4040 }
4041 if !ret_timers_removed.is_empty() {
4042 ret.timers_removed = Some(ret_timers_removed);
4043 }
4044 if !ret_words_changed.is_empty() {
4045 ret.words_changed = Some(ret_words_changed);
4046 }
4047 if !ret_images_changed.is_empty() {
4048 ret.images_changed = Some(ret_images_changed);
4049 }
4050 if !ret_image_masks_changed.is_empty() {
4051 ret.image_masks_changed = Some(ret_image_masks_changed);
4052 }
4053 if !ret_css_properties_changed.is_empty() {
4054 ret.css_properties_changed = Some(ret_css_properties_changed);
4055 }
4056 if !ret_nodes_scrolled_in_callbacks.is_empty() {
4057 ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
4058 }
4059
4060 if let Some(ft) = new_focus_target {
4061 if let Ok(new_focus_node) = crate::managers::focus_cursor::resolve_focus_target(
4062 &ft,
4063 &self.layout_results,
4064 self.focus_manager.get_focused_node().copied(),
4065 ) {
4066 ret.update_focused_node = match new_focus_node {
4067 Some(node) => FocusUpdateRequest::FocusNode(node),
4068 None => FocusUpdateRequest::ClearFocus,
4069 };
4070 }
4071 }
4072
4073 return ret;
4074 }
4075
4076 pub fn invoke_menu_callback(
4078 &mut self,
4079 menu_callback: &mut MenuCallback,
4080 hit_dom_node: DomNodeId,
4081 current_window_handle: &RawWindowHandle,
4082 gl_context: &OptionGlContextPtr,
4083 image_cache: &mut ImageCache,
4084 system_fonts: &mut FcFontCache,
4085 system_style: std::sync::Arc<azul_css::system::SystemStyle>,
4086 system_callbacks: &ExternalSystemCallbacks,
4087 previous_window_state: &Option<FullWindowState>,
4088 current_window_state: &FullWindowState,
4089 renderer_resources: &RendererResources,
4090 ) -> CallCallbacksResult {
4091 use crate::callbacks::{CallCallbacksResult, CallbackInfo, MenuCallback};
4092
4093 let mut ret = CallCallbacksResult {
4094 should_scroll_render: false,
4095 callbacks_update_screen: Update::DoNothing,
4096 modified_window_state: None,
4097 css_properties_changed: None,
4098 words_changed: None,
4099 images_changed: None,
4100 image_masks_changed: None,
4101 image_callbacks_changed: None,
4102 nodes_scrolled_in_callbacks: None,
4103 update_focused_node: FocusUpdateRequest::NoChange,
4104 timers: None,
4105 threads: None,
4106 timers_removed: None,
4107 threads_removed: None,
4108 windows_created: Vec::new(),
4109 menus_to_open: Vec::new(),
4110 tooltips_to_show: Vec::new(),
4111 hide_tooltip: false,
4112 cursor_changed: false,
4113 stop_propagation: false,
4114 stop_immediate_propagation: false,
4115 prevent_default: false,
4116 hit_test_update_requested: None,
4117 queued_window_states: Vec::new(),
4118 text_input_triggered: Vec::new(),
4119 begin_interactive_move: false,
4120 };
4121
4122 let mut ret_modified_window_state = current_window_state.clone();
4123 let ret_window_state = ret_modified_window_state.clone();
4124 let mut ret_timers = FastHashMap::new();
4125 let mut ret_timers_removed = FastBTreeSet::new();
4126 let mut ret_threads = FastHashMap::new();
4127 let mut ret_threads_removed = FastBTreeSet::new();
4128 let mut ret_words_changed = BTreeMap::new();
4129 let mut ret_images_changed = BTreeMap::new();
4130 let mut ret_image_masks_changed = BTreeMap::new();
4131 let mut ret_css_properties_changed = BTreeMap::new();
4132 let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
4133 let mut new_focus_target = None;
4134 let mut stop_propagation = false;
4135 let current_scroll_states = self.get_nested_scroll_states(DomId::ROOT_ID);
4136
4137 let cursor_relative_to_item = OptionLogicalPosition::None;
4138 let cursor_in_viewport = match current_window_state.mouse_state.cursor_position.get_position() {
4139 Some(pos) => OptionLogicalPosition::Some(pos),
4140 None => OptionLogicalPosition::None,
4141 };
4142
4143 let callback_changes = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
4145
4146 let ref_data = crate::callbacks::CallbackInfoRefData {
4148 layout_window: self,
4149 renderer_resources,
4150 previous_window_state,
4151 current_window_state,
4152 gl_context,
4153 current_scroll_manager: ¤t_scroll_states,
4154 current_window_handle,
4155 system_callbacks,
4156 system_style,
4157 monitors: self.monitors.clone(),
4158 #[cfg(feature = "icu")]
4159 icu_localizer: self.icu_localizer.clone(),
4160 ctx: OptionRefAny::None,
4161 };
4162
4163 let callback_info = CallbackInfo::new(
4164 &ref_data,
4165 &callback_changes,
4166 hit_dom_node,
4167 cursor_relative_to_item,
4168 cursor_in_viewport,
4169 );
4170
4171 ret.callbacks_update_screen =
4172 (menu_callback.callback.cb)(menu_callback.refany.clone(), callback_info);
4173
4174 let collected_changes = callback_changes
4176 .lock()
4177 .map(|mut guard| core::mem::take(&mut *guard))
4178 .unwrap_or_default();
4179
4180 let change_result = self.apply_callback_changes(
4182 collected_changes,
4183 current_window_state,
4184 image_cache,
4185 system_fonts,
4186 );
4187
4188 self.queue_iframe_updates(change_result.iframes_to_update);
4190
4191 ret.stop_propagation = change_result.stop_propagation;
4192 ret.prevent_default = change_result.prevent_default;
4193 ret.tooltips_to_show = change_result.tooltips_to_show;
4194 ret.hide_tooltip = change_result.hide_tooltip;
4195 ret.begin_interactive_move = ret.begin_interactive_move || change_result.begin_interactive_move;
4196
4197 if change_result.hit_test_update_requested.is_some() {
4199 ret.hit_test_update_requested = change_result.hit_test_update_requested;
4200 }
4201
4202 ret_timers.extend(change_result.timers);
4203 ret_threads.extend(change_result.threads);
4204 ret_timers_removed.extend(change_result.timers_removed);
4205 ret_threads_removed.extend(change_result.threads_removed);
4206 ret_words_changed.extend(change_result.words_changed);
4207 ret_images_changed.extend(change_result.images_changed);
4208 ret_image_masks_changed.extend(change_result.image_masks_changed);
4209 ret_css_properties_changed.extend(change_result.css_properties_changed);
4210 ret_nodes_scrolled_in_callbacks.append(&mut change_result.nodes_scrolled.clone());
4211
4212 if change_result.modified_window_state != *current_window_state {
4213 ret_modified_window_state = change_result.modified_window_state;
4214 }
4215
4216 new_focus_target = change_result.focus_target.or(new_focus_target);
4217
4218 if !ret_timers.is_empty() {
4219 ret.timers = Some(ret_timers);
4220 }
4221 if !ret_threads.is_empty() {
4222 ret.threads = Some(ret_threads);
4223 }
4224 if ret_modified_window_state != ret_window_state {
4225 ret.modified_window_state = Some(ret_modified_window_state);
4226 }
4227 if !ret_threads_removed.is_empty() {
4228 ret.threads_removed = Some(ret_threads_removed);
4229 }
4230 if !ret_timers_removed.is_empty() {
4231 ret.timers_removed = Some(ret_timers_removed);
4232 }
4233 if !ret_words_changed.is_empty() {
4234 ret.words_changed = Some(ret_words_changed);
4235 }
4236 if !ret_images_changed.is_empty() {
4237 ret.images_changed = Some(ret_images_changed);
4238 }
4239 if !ret_image_masks_changed.is_empty() {
4240 ret.image_masks_changed = Some(ret_image_masks_changed);
4241 }
4242 if !ret_css_properties_changed.is_empty() {
4243 ret.css_properties_changed = Some(ret_css_properties_changed);
4244 }
4245 if !ret_nodes_scrolled_in_callbacks.is_empty() {
4246 ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
4247 }
4248
4249 if let Some(ft) = new_focus_target {
4250 if let Ok(new_focus_node) = crate::managers::focus_cursor::resolve_focus_target(
4251 &ft,
4252 &self.layout_results,
4253 self.focus_manager.get_focused_node().copied(),
4254 ) {
4255 ret.update_focused_node = match new_focus_node {
4256 Some(node) => FocusUpdateRequest::FocusNode(node),
4257 None => FocusUpdateRequest::ClearFocus,
4258 };
4259 }
4260 }
4261
4262 return ret;
4263 }
4264
4265 pub fn set_system_style(&mut self, system_style: std::sync::Arc<azul_css::system::SystemStyle>) {
4273 #[cfg(feature = "icu")]
4274 {
4275 self.icu_localizer = crate::icu::IcuLocalizerHandle::from_system_language(&system_style.language);
4276 }
4277 self.system_style = Some(system_style);
4278 }
4279}
4280
4281#[cfg(feature = "icu")]
4284impl LayoutWindow {
4285 pub fn set_icu_locale(&mut self, locale: &str) {
4293 self.icu_localizer.set_locale(locale);
4294 }
4295
4296 pub fn init_icu_from_system_style(&mut self, system_style: &azul_css::system::SystemStyle) {
4300 self.icu_localizer = IcuLocalizerHandle::from_system_language(&system_style.language);
4301 }
4302
4303 pub fn get_icu_localizer(&self) -> IcuLocalizerHandle {
4307 self.icu_localizer.clone()
4308 }
4309
4310 pub fn load_icu_data_blob(&mut self, data: Vec<u8>) -> bool {
4315 self.icu_localizer.load_data_blob(&data)
4316 }
4317}
4318
4319#[cfg(test)]
4320mod tests {
4321 use super::*;
4322 use crate::{thread::Thread, timer::Timer};
4323
4324 #[test]
4325 fn test_timer_add_remove() {
4326 let fc_cache = FcFontCache::default();
4327 let mut window = LayoutWindow::new(fc_cache).unwrap();
4328
4329 let timer_id = TimerId { id: 1 };
4330 let timer = Timer::default();
4331
4332 window.add_timer(timer_id, timer);
4334 assert!(window.get_timer(&timer_id).is_some());
4335 assert_eq!(window.get_timer_ids().len(), 1);
4336
4337 let removed = window.remove_timer(&timer_id);
4339 assert!(removed.is_some());
4340 assert!(window.get_timer(&timer_id).is_none());
4341 assert_eq!(window.get_timer_ids().len(), 0);
4342 }
4343
4344 #[test]
4345 fn test_timer_get_mut() {
4346 let fc_cache = FcFontCache::default();
4347 let mut window = LayoutWindow::new(fc_cache).unwrap();
4348
4349 let timer_id = TimerId { id: 1 };
4350 let timer = Timer::default();
4351
4352 window.add_timer(timer_id, timer);
4353
4354 let timer_mut = window.get_timer_mut(&timer_id);
4356 assert!(timer_mut.is_some());
4357 }
4358
4359 #[test]
4360 fn test_multiple_timers() {
4361 let fc_cache = FcFontCache::default();
4362 let mut window = LayoutWindow::new(fc_cache).unwrap();
4363
4364 let timer1 = TimerId { id: 1 };
4365 let timer2 = TimerId { id: 2 };
4366 let timer3 = TimerId { id: 3 };
4367
4368 window.add_timer(timer1, Timer::default());
4369 window.add_timer(timer2, Timer::default());
4370 window.add_timer(timer3, Timer::default());
4371
4372 assert_eq!(window.get_timer_ids().len(), 3);
4373
4374 window.remove_timer(&timer2);
4375 assert_eq!(window.get_timer_ids().len(), 2);
4376 assert!(window.get_timer(&timer1).is_some());
4377 assert!(window.get_timer(&timer2).is_none());
4378 assert!(window.get_timer(&timer3).is_some());
4379 }
4380
4381 #[test]
4386 fn test_gpu_cache_management() {
4387 let fc_cache = FcFontCache::default();
4388 let mut window = LayoutWindow::new(fc_cache).unwrap();
4389
4390 let dom_id = DomId { inner: 0 };
4391
4392 assert!(window.get_gpu_cache(&dom_id).is_none());
4394
4395 let cache = window.get_or_create_gpu_cache(dom_id);
4397 assert!(cache.transform_keys.is_empty());
4398
4399 assert!(window.get_gpu_cache(&dom_id).is_some());
4401
4402 let cache_mut = window.get_gpu_cache_mut(&dom_id);
4404 assert!(cache_mut.is_some());
4405 }
4406
4407 #[test]
4408 fn test_gpu_cache_multiple_doms() {
4409 let fc_cache = FcFontCache::default();
4410 let mut window = LayoutWindow::new(fc_cache).unwrap();
4411
4412 let dom1 = DomId { inner: 0 };
4413 let dom2 = DomId { inner: 1 };
4414
4415 window.get_or_create_gpu_cache(dom1);
4416 window.get_or_create_gpu_cache(dom2);
4417
4418 assert!(window.get_gpu_cache(&dom1).is_some());
4419 assert!(window.get_gpu_cache(&dom2).is_some());
4420 }
4421
4422 #[test]
4423 fn test_compute_cursor_type_empty_hit_test() {
4424 use crate::hit_test::FullHitTest;
4425
4426 let fc_cache = FcFontCache::default();
4427 let window = LayoutWindow::new(fc_cache).unwrap();
4428
4429 let empty_hit = FullHitTest::empty(None);
4430 let cursor_test = window.compute_cursor_type_hit_test(&empty_hit);
4431
4432 assert_eq!(
4434 cursor_test.cursor_icon,
4435 azul_core::window::MouseCursorType::Default
4436 );
4437 assert!(cursor_test.cursor_node.is_none());
4438 }
4439
4440 #[test]
4441 fn test_layout_result_access() {
4442 let fc_cache = FcFontCache::default();
4443 let window = LayoutWindow::new(fc_cache).unwrap();
4444
4445 let dom_id = DomId { inner: 0 };
4446
4447 assert!(window.get_layout_result(&dom_id).is_none());
4449 assert_eq!(window.get_dom_ids().len(), 0);
4450 }
4451
4452 #[test]
4455 fn test_scroll_manager_initialization() {
4456 let fc_cache = FcFontCache::default();
4457 let window = LayoutWindow::new(fc_cache).unwrap();
4458
4459 let dom_id = DomId::ROOT_ID;
4460 let node_id = NodeId::new(0);
4461
4462 let scroll_offsets = window.scroll_manager.get_scroll_states_for_dom(dom_id);
4464 assert!(scroll_offsets.is_empty());
4465
4466 let offset = window.scroll_manager.get_current_offset(dom_id, node_id);
4468 assert_eq!(offset, None);
4469 }
4470
4471 #[test]
4472 fn test_scroll_manager_tick_updates_activity() {
4473 let fc_cache = FcFontCache::default();
4474 let mut window = LayoutWindow::new(fc_cache).unwrap();
4475
4476 let dom_id = DomId::ROOT_ID;
4477 let node_id = NodeId::new(0);
4478
4479 let scroll_event = crate::managers::scroll_state::ScrollEvent {
4481 dom_id,
4482 node_id,
4483 delta: LogicalPosition::new(10.0, 20.0),
4484 source: azul_core::events::EventSource::User,
4485 duration: None,
4486 easing: EasingFunction::Linear,
4487 };
4488
4489 #[cfg(feature = "std")]
4490 let now = Instant::System(std::time::Instant::now().into());
4491 #[cfg(not(feature = "std"))]
4492 let now = Instant::Tick(azul_core::task::SystemTick { tick_counter: 0 });
4493
4494 let did_scroll = window
4495 .scroll_manager
4496 .process_scroll_event(scroll_event, now.clone());
4497
4498 assert!(did_scroll);
4500 }
4501
4502 #[test]
4503 fn test_scroll_manager_programmatic_scroll() {
4504 let fc_cache = FcFontCache::default();
4505 let mut window = LayoutWindow::new(fc_cache).unwrap();
4506
4507 let dom_id = DomId::ROOT_ID;
4508 let node_id = NodeId::new(0);
4509
4510 #[cfg(feature = "std")]
4511 let now = Instant::System(std::time::Instant::now().into());
4512 #[cfg(not(feature = "std"))]
4513 let now = Instant::Tick(azul_core::task::SystemTick { tick_counter: 0 });
4514
4515 window.scroll_manager.scroll_to(
4517 dom_id,
4518 node_id,
4519 LogicalPosition::new(100.0, 200.0),
4520 Duration::System(SystemTimeDiff::from_millis(300)),
4521 EasingFunction::EaseOut,
4522 now.clone(),
4523 );
4524
4525 let tick_result = window.scroll_manager.tick(now);
4526
4527 assert!(tick_result.needs_repaint);
4529 }
4530
4531 #[test]
4532 fn test_scroll_manager_iframe_edge_detection() {
4533 }
4541
4542 #[test]
4543 fn test_scroll_manager_iframe_invocation_tracking() {
4544 }
4551
4552 #[test]
4553 fn test_scrollbar_opacity_fading() {
4554 }
4565
4566 #[test]
4567 fn test_iframe_callback_reason_initial_render() {
4568 }
4576
4577 #[test]
4578 fn test_gpu_cache_scrollbar_opacity_keys() {
4579 let fc_cache = FcFontCache::default();
4580 let mut window = LayoutWindow::new(fc_cache).unwrap();
4581
4582 let dom_id = DomId::ROOT_ID;
4583 let node_id = NodeId::new(0);
4584
4585 let gpu_cache = window.get_or_create_gpu_cache(dom_id);
4587
4588 assert!(gpu_cache.scrollbar_v_opacity_keys.is_empty());
4590 assert!(gpu_cache.scrollbar_h_opacity_keys.is_empty());
4591
4592 let opacity_key = azul_core::resources::OpacityKey::unique();
4594 gpu_cache
4595 .scrollbar_v_opacity_keys
4596 .insert((dom_id, node_id), opacity_key);
4597 gpu_cache
4598 .scrollbar_v_opacity_values
4599 .insert((dom_id, node_id), 1.0);
4600
4601 assert_eq!(gpu_cache.scrollbar_v_opacity_keys.len(), 1);
4603 assert_eq!(
4604 gpu_cache.scrollbar_v_opacity_values.get(&(dom_id, node_id)),
4605 Some(&1.0)
4606 );
4607 }
4608
4609 #[test]
4610 fn test_frame_lifecycle_begin_end() {
4611 }
4622}
4623
4624impl LayoutWindow {
4626 pub fn find_next_text_node(
4639 &self,
4640 dom_id: &DomId,
4641 current_node: NodeId,
4642 ) -> Option<(DomId, NodeId)> {
4643 let layout_result = self.get_layout_result(dom_id)?;
4644 let styled_dom = &layout_result.styled_dom;
4645
4646 let start_idx = current_node.index() + 1;
4648 let node_hierarchy = &styled_dom.node_hierarchy;
4649
4650 for i in start_idx..node_hierarchy.len() {
4651 let node_id = NodeId::new(i);
4652
4653 if self.node_has_text_content(styled_dom, node_id) {
4655 if self.is_text_selectable(styled_dom, node_id) {
4657 return Some((*dom_id, node_id));
4658 }
4659 }
4660 }
4661
4662 None
4663 }
4664
4665 pub fn find_prev_text_node(
4678 &self,
4679 dom_id: &DomId,
4680 current_node: NodeId,
4681 ) -> Option<(DomId, NodeId)> {
4682 let layout_result = self.get_layout_result(dom_id)?;
4683 let styled_dom = &layout_result.styled_dom;
4684
4685 let current_idx = current_node.index();
4687
4688 for i in (0..current_idx).rev() {
4689 let node_id = NodeId::new(i);
4690
4691 if self.node_has_text_content(styled_dom, node_id) {
4693 if self.is_text_selectable(styled_dom, node_id) {
4695 return Some((*dom_id, node_id));
4696 }
4697 }
4698 }
4699
4700 None
4701 }
4702
4703 fn find_last_text_child(&self, dom_id: DomId, parent_node_id: NodeId) -> Option<NodeId> {
4709 let layout_result = self.layout_results.get(&dom_id)?;
4710 let styled_dom = &layout_result.styled_dom;
4711 let node_data_container = styled_dom.node_data.as_container();
4712 let hierarchy_container = styled_dom.node_hierarchy.as_container();
4713
4714 let parent_type = node_data_container[parent_node_id].get_node_type();
4716 if matches!(parent_type, NodeType::Text(_)) {
4717 return Some(parent_node_id);
4718 }
4719
4720 let parent_item = &hierarchy_container[parent_node_id];
4722 let mut last_text_child: Option<NodeId> = None;
4723 let mut current_child = parent_item.first_child_id(parent_node_id);
4724 while let Some(child_id) = current_child {
4725 let child_type = node_data_container[child_id].get_node_type();
4726 if matches!(child_type, NodeType::Text(_)) {
4727 last_text_child = Some(child_id);
4728 }
4729 current_child = hierarchy_container[child_id].next_sibling_id();
4730 }
4731
4732 last_text_child
4733 }
4734
4735 fn node_has_text_content(&self, styled_dom: &StyledDom, node_id: NodeId) -> bool {
4737 let node_data_container = styled_dom.node_data.as_container();
4739 let node_type = node_data_container[node_id].get_node_type();
4740 if matches!(node_type, NodeType::Text(_)) {
4741 return true;
4742 }
4743
4744 let hierarchy_container = styled_dom.node_hierarchy.as_container();
4746 let node_item = &hierarchy_container[node_id];
4747
4748 let mut current_child = node_item.first_child_id(node_id);
4750 while let Some(child_id) = current_child {
4751 let child_type = node_data_container[child_id].get_node_type();
4752 if matches!(child_type, NodeType::Text(_)) {
4753 return true;
4754 }
4755
4756 current_child = hierarchy_container[child_id].next_sibling_id();
4758 }
4759
4760 false
4761 }
4762
4763 fn is_text_selectable(&self, styled_dom: &StyledDom, node_id: NodeId) -> bool {
4765 let node_state = &styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
4766 crate::solver3::getters::is_text_selectable(styled_dom, node_id, node_state)
4767 }
4768
4769 #[cfg(feature = "a11y")]
4788 pub fn process_accessibility_action(
4789 &mut self,
4790 dom_id: DomId,
4791 node_id: NodeId,
4792 action: azul_core::dom::AccessibilityAction,
4793 now: std::time::Instant,
4794 ) -> BTreeMap<DomNodeId, (Vec<azul_core::events::EventFilter>, bool)> {
4795 use crate::managers::text_input::TextInputSource;
4796
4797 let mut affected_nodes = BTreeMap::new();
4798
4799 match action {
4800 AccessibilityAction::Focus => {
4802 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
4803 let dom_node_id = DomNodeId {
4804 dom: dom_id,
4805 node: hierarchy_id,
4806 };
4807 self.focus_manager.set_focused_node(Some(dom_node_id));
4808
4809 if let Some(layout_result) = self.layout_results.get(&dom_id) {
4811 if let Some(styled_node) = layout_result
4812 .styled_dom
4813 .node_data
4814 .as_ref()
4815 .get(node_id.index())
4816 {
4817 let is_contenteditable = styled_node.contenteditable
4821 || styled_node.attributes.as_ref().iter().any(|attr| {
4822 matches!(attr, azul_core::dom::AttributeType::ContentEditable(_))
4823 });
4824
4825 if is_contenteditable {
4826 let inline_layout = self.get_inline_layout_for_node(dom_id, node_id).cloned();
4829 if inline_layout.is_some() {
4830 self.cursor_manager.initialize_cursor_at_end(
4831 dom_id,
4832 node_id,
4833 inline_layout.as_ref(),
4834 );
4835
4836 self.scroll_cursor_into_view_if_needed(dom_id, node_id, now);
4838 }
4839 } else {
4840 self.cursor_manager.clear();
4842 }
4843 }
4844 }
4845
4846 self.scroll_to_node_if_needed(dom_id, node_id, now);
4848 }
4849 AccessibilityAction::Blur => {
4850 self.focus_manager.clear_focus();
4851 self.cursor_manager.clear();
4852 }
4853 AccessibilityAction::SetSequentialFocusNavigationStartingPoint => {
4854 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
4855 let dom_node_id = DomNodeId {
4856 dom: dom_id,
4857 node: hierarchy_id,
4858 };
4859 self.focus_manager.set_focused_node(Some(dom_node_id));
4860 self.cursor_manager.clear();
4862 }
4863
4864 AccessibilityAction::ScrollIntoView => {
4866 self.scroll_to_node_if_needed(dom_id, node_id, now);
4867 }
4868 AccessibilityAction::ScrollUp => {
4869 self.scroll_manager.scroll_by(
4870 dom_id,
4871 node_id,
4872 LogicalPosition { x: 0.0, y: -100.0 },
4873 std::time::Duration::from_millis(200).into(),
4874 azul_core::events::EasingFunction::EaseOut,
4875 now.into(),
4876 );
4877 }
4878 AccessibilityAction::ScrollDown => {
4879 self.scroll_manager.scroll_by(
4880 dom_id,
4881 node_id,
4882 LogicalPosition { x: 0.0, y: 100.0 },
4883 std::time::Duration::from_millis(200).into(),
4884 azul_core::events::EasingFunction::EaseOut,
4885 now.into(),
4886 );
4887 }
4888 AccessibilityAction::ScrollLeft => {
4889 self.scroll_manager.scroll_by(
4890 dom_id,
4891 node_id,
4892 LogicalPosition { x: -100.0, y: 0.0 },
4893 std::time::Duration::from_millis(200).into(),
4894 azul_core::events::EasingFunction::EaseOut,
4895 now.into(),
4896 );
4897 }
4898 AccessibilityAction::ScrollRight => {
4899 self.scroll_manager.scroll_by(
4900 dom_id,
4901 node_id,
4902 LogicalPosition { x: 100.0, y: 0.0 },
4903 std::time::Duration::from_millis(200).into(),
4904 azul_core::events::EasingFunction::EaseOut,
4905 now.into(),
4906 );
4907 }
4908 AccessibilityAction::ScrollUp => {
4909 if let Some(size) = self.get_node_used_size_a11y(dom_id, node_id) {
4911 let scroll_amount = size.height.min(100.0); self.scroll_manager.scroll_by(
4913 dom_id,
4914 node_id,
4915 LogicalPosition {
4916 x: 0.0,
4917 y: -scroll_amount,
4918 },
4919 std::time::Duration::from_millis(300).into(),
4920 azul_core::events::EasingFunction::EaseInOut,
4921 now.into(),
4922 );
4923 }
4924 }
4925 AccessibilityAction::ScrollDown => {
4926 if let Some(size) = self.get_node_used_size_a11y(dom_id, node_id) {
4928 let scroll_amount = size.height.min(100.0); self.scroll_manager.scroll_by(
4930 dom_id,
4931 node_id,
4932 LogicalPosition {
4933 x: 0.0,
4934 y: scroll_amount,
4935 },
4936 std::time::Duration::from_millis(300).into(),
4937 azul_core::events::EasingFunction::EaseInOut,
4938 now.into(),
4939 );
4940 }
4941 }
4942 AccessibilityAction::SetScrollOffset(pos) => {
4943 self.scroll_manager.scroll_to(
4944 dom_id,
4945 node_id,
4946 pos,
4947 std::time::Duration::from_millis(0).into(),
4948 azul_core::events::EasingFunction::Linear,
4949 now.into(),
4950 );
4951 }
4952 AccessibilityAction::ScrollToPoint(pos) => {
4953 self.scroll_manager.scroll_to(
4954 dom_id,
4955 node_id,
4956 pos,
4957 std::time::Duration::from_millis(300).into(),
4958 azul_core::events::EasingFunction::EaseInOut,
4959 now.into(),
4960 );
4961 }
4962
4963 AccessibilityAction::Default => {
4967 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
4969 let dom_node_id = DomNodeId {
4970 dom: dom_id,
4971 node: hierarchy_id,
4972 };
4973
4974 let event_filter = if let Some(layout_result) = self.layout_results.get(&dom_id) {
4976 if let Some(styled_node) = layout_result
4977 .styled_dom
4978 .node_data
4979 .as_ref()
4980 .get(node_id.index())
4981 {
4982 let has_default_callback =
4983 styled_node.callbacks.as_ref().iter().any(|cb| {
4984 matches!(cb.event, EventFilter::Hover(HoverEventFilter::MouseUp))
4986 });
4987
4988 if has_default_callback {
4989 EventFilter::Hover(HoverEventFilter::MouseUp)
4990 } else {
4991 EventFilter::Hover(HoverEventFilter::MouseUp)
4992 }
4993 } else {
4994 EventFilter::Hover(HoverEventFilter::MouseUp)
4995 }
4996 } else {
4997 EventFilter::Hover(HoverEventFilter::MouseUp)
4998 };
4999
5000 affected_nodes.insert(dom_node_id, (vec![event_filter], false));
5001 }
5002
5003 AccessibilityAction::Increment | AccessibilityAction::Decrement => {
5004 let is_increment = matches!(action, AccessibilityAction::Increment);
5014
5015 let current_value = if let Some(layout_result) = self.layout_results.get(&dom_id) {
5017 if let Some(styled_node) = layout_result
5018 .styled_dom
5019 .node_data
5020 .as_ref()
5021 .get(node_id.index())
5022 {
5023 styled_node
5025 .attributes
5026 .as_ref()
5027 .iter()
5028 .find_map(|attr| {
5029 if let AttributeType::Value(v) = attr {
5030 Some(v.as_str().to_string())
5031 } else {
5032 None
5033 }
5034 })
5035 .or_else(|| {
5036 if let NodeType::Text(text) = styled_node.get_node_type() {
5038 Some(text.as_str().to_string())
5039 } else {
5040 None
5041 }
5042 })
5043 } else {
5044 None
5045 }
5046 } else {
5047 None
5048 };
5049
5050 if let Some(value_str) = current_value {
5052 let parsed: Result<f64, _> = value_str.trim().parse();
5053
5054 let new_value_str = if let Ok(num) = parsed {
5055 let new_num = if is_increment { num + 1.0 } else { num - 1.0 };
5057 if num.fract() == 0.0 {
5059 format!("{}", new_num as i64)
5060 } else {
5061 format!("{}", new_num)
5062 }
5063 } else {
5064 if is_increment {
5066 "1".to_string()
5067 } else {
5068 "-1".to_string()
5069 }
5070 };
5071
5072 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
5074 let dom_node_id = DomNodeId {
5075 dom: dom_id,
5076 node: hierarchy_id,
5077 };
5078
5079 let old_inline_content = self.get_text_before_textinput(dom_id, node_id);
5081 let old_text = self.extract_text_from_inline_content(&old_inline_content);
5082
5083 self.text_input_manager.record_input(
5085 dom_node_id,
5086 new_value_str,
5087 old_text,
5088 TextInputSource::Accessibility,
5089 );
5090
5091 affected_nodes.insert(
5093 dom_node_id,
5094 (vec![EventFilter::Focus(FocusEventFilter::TextInput)], false),
5095 );
5096 }
5097 }
5098
5099 AccessibilityAction::Collapse | AccessibilityAction::Expand => {
5100 let event_type = match action {
5102 AccessibilityAction::Collapse => On::Collapse,
5103 AccessibilityAction::Expand => On::Expand,
5104 _ => unreachable!(),
5105 };
5106
5107 if let Some(layout_result) = self.layout_results.get(&dom_id) {
5109 if let Some(styled_node) = layout_result
5110 .styled_dom
5111 .node_data
5112 .as_ref()
5113 .get(node_id.index())
5114 {
5115 let has_callback = styled_node
5117 .callbacks
5118 .as_ref()
5119 .iter()
5120 .any(|cb| cb.event == event_type.into());
5121
5122 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
5123 let dom_node_id = DomNodeId {
5124 dom: dom_id,
5125 node: hierarchy_id,
5126 };
5127
5128 if has_callback {
5129 affected_nodes.insert(dom_node_id, (vec![event_type.into()], false));
5131 } else {
5132 affected_nodes.insert(
5134 dom_node_id,
5135 (vec![EventFilter::Hover(HoverEventFilter::MouseUp)], false),
5136 );
5137 }
5138 }
5139 }
5140 }
5141
5142 AccessibilityAction::ShowContextMenu => {
5144 let layout_result = match self.layout_results.get(&dom_id) {
5146 Some(lr) => lr,
5147 None => {
5148 return affected_nodes;
5149 }
5150 };
5151
5152 let styled_node = match layout_result
5154 .styled_dom
5155 .node_data
5156 .as_ref()
5157 .get(node_id.index())
5158 {
5159 Some(node) => node,
5160 None => {
5161 return affected_nodes;
5162 }
5163 };
5164
5165 let has_context_menu = styled_node.get_context_menu().is_some();
5167
5168 if has_context_menu {
5169 } else {
5172 }
5174 }
5175
5176 AccessibilityAction::ReplaceSelectedText(ref text) => {
5178 let nodes = self.edit_text_node(
5179 dom_id,
5180 node_id,
5181 TextEditType::ReplaceSelection(text.as_str().to_string()),
5182 );
5183 for node in nodes {
5184 affected_nodes.insert(node, (Vec::new(), true)); }
5186 }
5187 AccessibilityAction::SetValue(ref text) => {
5188 let nodes = self.edit_text_node(
5189 dom_id,
5190 node_id,
5191 TextEditType::SetValue(text.as_str().to_string()),
5192 );
5193 for node in nodes {
5194 affected_nodes.insert(node, (Vec::new(), true));
5195 }
5196 }
5197 AccessibilityAction::SetNumericValue(value) => {
5198 let nodes = self.edit_text_node(
5199 dom_id,
5200 node_id,
5201 TextEditType::SetNumericValue(value.get() as f64),
5202 );
5203 for node in nodes {
5204 affected_nodes.insert(node, (Vec::new(), true));
5205 }
5206 }
5207 AccessibilityAction::SetTextSelection(selection) => {
5208 let text_layout = self.get_node_inline_layout(dom_id, node_id);
5210
5211 if let Some(inline_layout) = text_layout {
5212 let start_cursor = self.byte_offset_to_cursor(
5214 inline_layout.as_ref(),
5215 selection.selection_start as u32,
5216 );
5217 let end_cursor = self.byte_offset_to_cursor(
5218 inline_layout.as_ref(),
5219 selection.selection_end as u32,
5220 );
5221
5222 if let (Some(start), Some(end)) = (start_cursor, end_cursor) {
5223 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
5224 let dom_node_id = DomNodeId {
5225 dom: dom_id,
5226 node: hierarchy_id,
5227 };
5228
5229 if start == end {
5230 self.cursor_manager.move_cursor_to(start, dom_id, node_id);
5232
5233 self.selection_manager.clear_selection(&dom_id);
5235 } else {
5236 let selection = Selection::Range(SelectionRange { start, end });
5238
5239 let selection_state = SelectionState {
5240 selections: vec![selection].into(),
5241 node_id: dom_node_id,
5242 };
5243
5244 self.selection_manager
5246 .set_selection(dom_id, selection_state);
5247
5248 self.cursor_manager.move_cursor_to(start, dom_id, node_id);
5250 }
5251 } else {
5252 }
5254 } else {
5255 }
5257 }
5258
5259 AccessibilityAction::ShowTooltip | AccessibilityAction::HideTooltip => {
5261 }
5263
5264 AccessibilityAction::CustomAction(_id) => {
5265 }
5267 }
5268
5269 self.sync_cursor_to_selection_manager();
5271
5272 affected_nodes
5273 }
5274
5275 pub fn record_text_input(
5297 &mut self,
5298 text_input: &str,
5299 ) -> BTreeMap<azul_core::dom::DomNodeId, (Vec<azul_core::events::EventFilter>, bool)> {
5300 use std::collections::BTreeMap;
5301
5302 use crate::managers::text_input::TextInputSource;
5303
5304 let mut affected_nodes = BTreeMap::new();
5305
5306 if text_input.is_empty() {
5307 return affected_nodes;
5308 }
5309
5310 let focused_node = match self.focus_manager.get_focused_node().copied() {
5312 Some(node) => node,
5313 None => {
5314 return affected_nodes;
5315 }
5316 };
5317
5318 let node_id = match focused_node.node.into_crate_internal() {
5319 Some(id) => id,
5320 None => {
5321 return affected_nodes;
5322 }
5323 };
5324
5325 let old_inline_content = self.get_text_before_textinput(focused_node.dom, node_id);
5327 let old_text = self.extract_text_from_inline_content(&old_inline_content);
5328
5329 self.text_input_manager.record_input(
5331 focused_node,
5332 text_input.to_string(),
5333 old_text,
5334 TextInputSource::Keyboard, );
5336
5337 let text_input_event = vec![EventFilter::Focus(FocusEventFilter::TextInput)];
5339
5340 affected_nodes.insert(focused_node, (text_input_event, false)); affected_nodes
5343 }
5344
5345 pub fn apply_text_changeset(&mut self) -> Vec<azul_core::dom::DomNodeId> {
5354 let changeset = match self.text_input_manager.get_pending_changeset() {
5356 Some(cs) => cs.clone(),
5357 None => {
5358 return Vec::new();
5359 }
5360 };
5361
5362 let node_id = match changeset.node.node.into_crate_internal() {
5363 Some(id) => id,
5364 None => {
5365 self.text_input_manager.clear_changeset();
5366 return Vec::new();
5367 }
5368 };
5369
5370 let dom_id = changeset.node.dom;
5371
5372 let layout_result = match self.layout_results.get(&dom_id) {
5374 Some(lr) => lr,
5375 None => {
5376 self.text_input_manager.clear_changeset();
5377 return Vec::new();
5378 }
5379 };
5380
5381 let styled_node = match layout_result
5382 .styled_dom
5383 .node_data
5384 .as_ref()
5385 .get(node_id.index())
5386 {
5387 Some(node) => node,
5388 None => {
5389 self.text_input_manager.clear_changeset();
5390 return Vec::new();
5391 }
5392 };
5393
5394 let is_contenteditable = styled_node.contenteditable
5398 || styled_node.attributes.as_ref().iter().any(|attr| {
5399 matches!(attr, azul_core::dom::AttributeType::ContentEditable(_))
5400 });
5401
5402 if !is_contenteditable {
5403 self.text_input_manager.clear_changeset();
5404 return Vec::new();
5405 }
5406
5407 let content = self.get_text_before_textinput(dom_id, node_id);
5409
5410 let current_selection = if let Some(cursor) = self.cursor_manager.get_cursor() {
5412 vec![Selection::Cursor(cursor.clone())]
5413 } else {
5414 vec![Selection::Cursor(TextCursor {
5416 cluster_id: GraphemeClusterId {
5417 source_run: 0,
5418 start_byte_in_run: 0,
5419 },
5420 affinity: CursorAffinity::Leading,
5421 })]
5422 };
5423
5424 let old_text = self.extract_text_from_inline_content(&content);
5426 let old_cursor = current_selection.first().and_then(|sel| {
5427 if let Selection::Cursor(c) = sel {
5428 Some(c.clone())
5429 } else {
5430 None
5431 }
5432 });
5433 let old_selection_range = current_selection.first().and_then(|sel| {
5434 if let Selection::Range(r) = sel {
5435 Some(*r)
5436 } else {
5437 None
5438 }
5439 });
5440
5441 let pre_state = crate::managers::undo_redo::NodeStateSnapshot {
5442 node_id: azul_core::id::NodeId::new(node_id.index()),
5443 text_content: old_text.into(),
5444 cursor_position: old_cursor.into(),
5445 selection_range: old_selection_range.into(),
5446 #[cfg(feature = "std")]
5447 timestamp: azul_core::task::Instant::System(std::time::Instant::now().into()),
5448 #[cfg(not(feature = "std"))]
5449 timestamp: azul_core::task::Instant::Tick(azul_core::task::SystemTick { tick_counter: 0 }),
5450 };
5451
5452 use crate::text3::edit::{edit_text, TextEdit};
5454 let text_edit = TextEdit::Insert(changeset.inserted_text.as_str().to_string());
5455 let (new_content, new_selections) = edit_text(&content, ¤t_selection, &text_edit);
5456
5457 if let Some(Selection::Cursor(new_cursor)) = new_selections.first() {
5460 self.cursor_manager
5461 .move_cursor_to(new_cursor.clone(), dom_id, node_id);
5462 }
5463
5464 self.update_text_cache_after_edit(dom_id, node_id, new_content);
5466
5467 use crate::managers::changeset::{TextChangeset, TextOpInsertText, TextOperation};
5470
5471 let new_cursor = new_selections
5473 .first()
5474 .and_then(|sel| {
5475 if let Selection::Cursor(c) = sel {
5476 Some(CursorPosition::InWindow(
5480 azul_core::geom::LogicalPosition::new(0.0, 0.0),
5481 ))
5482 } else {
5483 None
5484 }
5485 })
5486 .unwrap_or(CursorPosition::Uninitialized);
5487
5488 let old_cursor_pos = old_cursor
5489 .as_ref()
5490 .map(|_| {
5491 CursorPosition::InWindow(azul_core::geom::LogicalPosition::new(0.0, 0.0))
5493 })
5494 .unwrap_or(CursorPosition::Uninitialized);
5495
5496 static CHANGESET_COUNTER: std::sync::atomic::AtomicUsize =
5498 std::sync::atomic::AtomicUsize::new(0);
5499 let changeset_id = CHANGESET_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
5500
5501 let undo_changeset = TextChangeset {
5502 id: changeset_id,
5503 target: changeset.node,
5504 operation: TextOperation::InsertText(TextOpInsertText {
5505 text: changeset.inserted_text.clone(),
5506 position: old_cursor_pos,
5507 new_cursor,
5508 }),
5509 #[cfg(feature = "std")]
5510 timestamp: azul_core::task::Instant::System(std::time::Instant::now().into()),
5511 #[cfg(not(feature = "std"))]
5512 timestamp: azul_core::task::Instant::Tick(azul_core::task::SystemTick { tick_counter: 0 }),
5513 };
5514 self.undo_redo_manager
5515 .record_operation(undo_changeset, pre_state);
5516
5517 self.text_input_manager.clear_changeset();
5519
5520 let dirty_nodes = self.determine_dirty_text_nodes(dom_id, node_id);
5522 dirty_nodes
5523 }
5524
5525 fn determine_dirty_text_nodes(
5529 &self,
5530 dom_id: DomId,
5531 node_id: NodeId,
5532 ) -> Vec<azul_core::dom::DomNodeId> {
5533 let layout_result = match self.layout_results.get(&dom_id) {
5534 Some(lr) => lr,
5535 None => return Vec::new(),
5536 };
5537
5538 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
5539 let node_dom_id = azul_core::dom::DomNodeId {
5540 dom: dom_id,
5541 node: hierarchy_id,
5542 };
5543
5544 let parent_id = layout_result
5546 .styled_dom
5547 .node_hierarchy
5548 .as_container()
5549 .get(node_id)
5550 .and_then(|item| item.parent_id())
5551 .map(|parent_node_id| {
5552 let parent_hierarchy_id =
5553 NodeHierarchyItemId::from_crate_internal(Some(parent_node_id));
5554 azul_core::dom::DomNodeId {
5555 dom: dom_id,
5556 node: parent_hierarchy_id,
5557 }
5558 });
5559
5560 if let Some(parent) = parent_id {
5562 vec![node_dom_id, parent]
5563 } else {
5564 vec![node_dom_id]
5565 }
5566 }
5567
5568 #[inline]
5570 pub fn process_text_input(
5571 &mut self,
5572 text_input: &str,
5573 ) -> BTreeMap<azul_core::dom::DomNodeId, (Vec<azul_core::events::EventFilter>, bool)> {
5574 self.record_text_input(text_input)
5575 }
5576
5577 pub fn get_last_text_changeset(&self) -> Option<&PendingTextEdit> {
5579 self.text_input_manager.get_pending_changeset()
5580 }
5581
5582 pub fn get_text_before_textinput(&self, dom_id: DomId, node_id: NodeId) -> Vec<InlineContent> {
5592 if let Some(dirty_node) = self.dirty_text_nodes.get(&(dom_id, node_id)) {
5598 return dirty_node.content.clone();
5599 }
5600
5601 let layout_result = match self.layout_results.get(&dom_id) {
5604 Some(lr) => lr,
5605 None => return Vec::new(),
5606 };
5607
5608 let node_data = match layout_result
5610 .styled_dom
5611 .node_data
5612 .as_ref()
5613 .get(node_id.index())
5614 {
5615 Some(nd) => nd,
5616 None => return Vec::new(),
5617 };
5618
5619 match node_data.get_node_type() {
5621 NodeType::Text(text) => {
5622 let style = self.get_text_style_for_node(dom_id, node_id);
5624
5625 vec![InlineContent::Text(StyledRun {
5626 text: text.as_str().to_string(),
5627 style,
5628 logical_start_byte: 0,
5629 source_node_id: Some(node_id),
5630 })]
5631 }
5632 NodeType::Div | NodeType::Body | NodeType::IFrame(_) => {
5633 self.collect_text_from_children(dom_id, node_id)
5635 }
5636 _ => {
5637 Vec::new()
5639 }
5640 }
5641 }
5642
5643 fn get_text_style_for_node(
5645 &self,
5646 dom_id: DomId,
5647 node_id: NodeId,
5648 ) -> alloc::sync::Arc<StyleProperties> {
5649 use alloc::sync::Arc;
5650
5651 let layout_result = match self.layout_results.get(&dom_id) {
5652 Some(lr) => lr,
5653 None => return Arc::new(Default::default()),
5654 };
5655
5656 let styled_nodes = layout_result.styled_dom.styled_nodes.as_ref();
5658 let _styled_node = match styled_nodes.get(node_id.index()) {
5659 Some(sn) => sn,
5660 None => return Arc::new(Default::default()),
5661 };
5662
5663 Arc::new(Default::default())
5667 }
5668
5669 fn collect_text_from_children(
5671 &self,
5672 dom_id: DomId,
5673 parent_node_id: NodeId,
5674 ) -> Vec<InlineContent> {
5675 let layout_result = match self.layout_results.get(&dom_id) {
5676 Some(lr) => lr,
5677 None => return Vec::new(),
5678 };
5679
5680 let node_hierarchy = layout_result.styled_dom.node_hierarchy.as_ref();
5681 let parent_item = match node_hierarchy.get(parent_node_id.index()) {
5682 Some(item) => item,
5683 None => return Vec::new(),
5684 };
5685
5686 let mut result = Vec::new();
5687
5688 let mut current_child = parent_item.first_child_id(parent_node_id);
5690 while let Some(child_id) = current_child {
5691 let child_content = self.get_text_before_textinput(dom_id, child_id);
5693 result.extend(child_content);
5694
5695 let child_item = match node_hierarchy.get(child_id.index()) {
5697 Some(item) => item,
5698 None => break,
5699 };
5700 current_child = child_item.next_sibling_id();
5701 }
5702
5703 result
5704 }
5705
5706 pub fn extract_text_from_inline_content(&self, content: &[InlineContent]) -> String {
5710 let mut result = String::new();
5711
5712 for item in content {
5713 match item {
5714 InlineContent::Text(text_run) => {
5715 result.push_str(&text_run.text);
5716 }
5717 InlineContent::Space(_) => {
5718 result.push(' ');
5719 }
5720 InlineContent::LineBreak(_) => {
5721 result.push('\n');
5722 }
5723 InlineContent::Tab { .. } => {
5724 result.push('\t');
5725 }
5726 InlineContent::Ruby { base, .. } => {
5727 result.push_str(&self.extract_text_from_inline_content(base));
5729 }
5730 InlineContent::Marker { run, .. } => {
5731 result.push_str(&run.text);
5733 }
5734 InlineContent::Image(_) | InlineContent::Shape(_) => {}
5736 }
5737 }
5738
5739 result
5740 }
5741
5742 pub fn update_text_cache_after_edit(
5752 &mut self,
5753 dom_id: DomId,
5754 node_id: NodeId,
5755 new_inline_content: Vec<InlineContent>,
5756 ) {
5757 use crate::solver3::layout_tree::CachedInlineLayout;
5758
5759 let cursor = self.cursor_manager.get_cursor().cloned();
5761 self.dirty_text_nodes.insert(
5762 (dom_id, node_id),
5763 DirtyTextNode {
5764 content: new_inline_content.clone(),
5765 cursor,
5766 needs_ancestor_relayout: false, },
5768 );
5769
5770 let constraints = {
5773 let layout_result = match self.layout_results.get(&dom_id) {
5774 Some(r) => r,
5775 None => {
5776 return;
5777 }
5778 };
5779
5780 let layout_node = match layout_result.layout_tree.get(node_id.index()) {
5781 Some(n) => n,
5782 None => {
5783 return;
5784 }
5785 };
5786
5787 let cached_layout = match &layout_node.inline_layout_result {
5788 Some(c) => c,
5789 None => {
5790 return;
5791 }
5792 };
5793
5794 match &cached_layout.constraints {
5795 Some(c) => c.clone(),
5796 None => {
5797 return;
5798 }
5799 }
5800 };
5801
5802 let new_layout = self.relayout_text_node_internal(&new_inline_content, &constraints);
5804
5805 let Some(new_layout) = new_layout else {
5806 return;
5807 };
5808
5809 if let Some(layout_result) = self.layout_results.get_mut(&dom_id) {
5812 if let Some(layout_node) = layout_result.layout_tree.get_mut(node_id.index()) {
5814 let old_size = layout_node.used_size;
5816 let new_bounds = new_layout.bounds();
5817 let new_size = Some(LogicalSize {
5818 width: new_bounds.width,
5819 height: new_bounds.height,
5820 });
5821
5822 if let (Some(old), Some(new)) = (old_size, new_size) {
5824 if (old.height - new.height).abs() > 0.5 || (old.width - new.width).abs() > 0.5 {
5825 if let Some(dirty_node) = self.dirty_text_nodes.get_mut(&(dom_id, node_id)) {
5827 dirty_node.needs_ancestor_relayout = true;
5828 }
5829 }
5830 }
5831
5832 layout_node.inline_layout_result = Some(CachedInlineLayout::new_with_constraints(
5834 Arc::new(new_layout),
5835 constraints.available_width,
5836 false, constraints,
5838 ));
5839 }
5840 }
5841
5842 self.regenerate_display_list_for_dom(dom_id);
5846 }
5847
5848 fn regenerate_display_list_for_dom(&mut self, dom_id: DomId) {
5858 use crate::solver3::{
5859 display_list::generate_display_list,
5860 LayoutContext,
5861 };
5862
5863 let layout_result = match self.layout_results.get(&dom_id) {
5865 Some(lr) => lr,
5866 None => { return; }
5867 };
5868
5869 let tree = &layout_result.layout_tree;
5870 let calculated_positions = &layout_result.calculated_positions;
5871 let scroll_ids = &layout_result.scroll_ids;
5872 let styled_dom = &layout_result.styled_dom;
5873 let viewport = layout_result.viewport;
5874
5875 let scroll_offsets = self.scroll_manager.get_scroll_states_for_dom(dom_id);
5877
5878 let gpu_cache = self.gpu_state_manager.get_or_create_cache(dom_id).clone();
5880
5881 let cursor_is_visible = self.cursor_manager.should_draw_cursor();
5883 let cursor_location = self.cursor_manager.get_cursor_location().and_then(|loc| {
5884 self.cursor_manager.get_cursor().map(|cursor| {
5885 (loc.dom_id, loc.node_id, cursor.clone())
5886 })
5887 });
5888
5889 let mut counter_values = BTreeMap::new();
5891 let mut debug_messages: Option<Vec<LayoutDebugMessage>> = None;
5892 let cache_map = std::mem::take(&mut self.layout_cache.cache_map);
5893
5894 let mut ctx = LayoutContext {
5895 styled_dom,
5896 font_manager: &self.font_manager,
5897 selections: &self.selection_manager.selections,
5898 text_selections: &self.selection_manager.text_selections,
5899 debug_messages: &mut debug_messages,
5900 counters: &mut counter_values,
5901 viewport_size: viewport.size,
5902 fragmentation_context: None,
5903 cursor_is_visible,
5904 cursor_location,
5905 cache_map,
5906 system_style: self.system_style.clone(),
5907 get_system_time_fn: azul_core::task::GetSystemTimeCallback {
5908 cb: azul_core::task::get_system_time_libstd,
5909 },
5910 };
5911
5912 let new_display_list = generate_display_list(
5914 &mut ctx,
5915 tree,
5916 calculated_positions,
5917 &scroll_offsets,
5918 scroll_ids,
5919 Some(&gpu_cache),
5920 &self.renderer_resources,
5921 self.id_namespace,
5922 dom_id,
5923 );
5924
5925 self.layout_cache.cache_map = std::mem::take(&mut ctx.cache_map);
5927
5928 match new_display_list {
5929 Ok(display_list) => {
5930 if let Some(layout_result) = self.layout_results.get_mut(&dom_id) {
5931 layout_result.display_list = display_list;
5932 }
5933 }
5934 Err(_e) => { }
5935 }
5936 }
5937
5938 fn relayout_text_node_internal(
5940 &self,
5941 content: &[InlineContent],
5942 constraints: &UnifiedConstraints,
5943 ) -> Option<UnifiedLayout> {
5944 use crate::text3::cache::{
5945 create_logical_items, perform_fragment_layout, reorder_logical_items,
5946 shape_visual_items, BidiDirection, BreakCursor,
5947 };
5948
5949 let logical_items = create_logical_items(content, &[], &mut None);
5951
5952 if logical_items.is_empty() {
5953 return Some(UnifiedLayout {
5955 items: Vec::new(),
5956 overflow: crate::text3::cache::OverflowInfo::default(),
5957 });
5958 }
5959
5960 let base_direction = constraints.direction.unwrap_or(BidiDirection::Ltr);
5962 let visual_items = reorder_logical_items(&logical_items, base_direction, &mut None).ok()?;
5963
5964 let loaded_fonts = self.font_manager.get_loaded_fonts();
5966 let shaped_items = shape_visual_items(
5967 &visual_items,
5968 self.font_manager.get_font_chain_cache(),
5969 &self.font_manager.fc_cache,
5970 &loaded_fonts,
5971 &mut None,
5972 )
5973 .ok()?;
5974
5975 let mut cursor = BreakCursor::new(&shaped_items);
5977 perform_fragment_layout(&mut cursor, &logical_items, constraints, &mut None, &loaded_fonts)
5978 .ok()
5979 }
5980
5981 #[cfg(feature = "a11y")]
5983 fn get_node_used_size_a11y(
5984 &self,
5985 dom_id: DomId,
5986 node_id: NodeId,
5987 ) -> Option<azul_core::geom::LogicalSize> {
5988 let layout_result = self.layout_results.get(&dom_id)?;
5989 let node = layout_result.layout_tree.get(node_id.index())?;
5990 node.used_size
5991 }
5992
5993 fn get_node_bounds(
5995 &self,
5996 dom_id: DomId,
5997 node_id: NodeId,
5998 ) -> Option<azul_css::props::basic::LayoutRect> {
5999 use azul_css::props::basic::LayoutRect;
6000
6001 let layout_result = self.layout_results.get(&dom_id)?;
6002 let node = layout_result.layout_tree.get(node_id.index())?;
6003
6004 let size = node.used_size?;
6006
6007 let position = layout_result.calculated_positions.get(node_id.index())?;
6009
6010 Some(LayoutRect {
6011 origin: azul_css::props::basic::LayoutPoint {
6012 x: position.x as f32 as isize,
6013 y: position.y as f32 as isize,
6014 },
6015 size: azul_css::props::basic::LayoutSize {
6016 width: size.width as isize,
6017 height: size.height as isize,
6018 },
6019 })
6020 }
6021
6022 #[cfg(feature = "a11y")]
6024 fn scroll_to_node_if_needed(
6025 &mut self,
6026 dom_id: DomId,
6027 node_id: NodeId,
6028 now: std::time::Instant,
6029 ) {
6030 if self.get_node_bounds(dom_id, node_id).is_some() {
6038 self.scroll_manager.scroll_to(
6039 dom_id,
6040 node_id,
6041 LogicalPosition { x: 0.0, y: 0.0 },
6042 std::time::Duration::from_millis(300).into(),
6043 azul_core::events::EasingFunction::EaseOut,
6044 now.into(),
6045 );
6046 }
6047 }
6048
6049 fn scroll_cursor_into_view_if_needed(
6062 &mut self,
6063 dom_id: DomId,
6064 node_id: NodeId,
6065 now: std::time::Instant,
6066 ) {
6067 let Some(cursor) = self.cursor_manager.get_cursor() else {
6069 return;
6070 };
6071
6072 let Some(inline_layout) = self.get_node_inline_layout(dom_id, node_id) else {
6074 return;
6075 };
6076
6077 let Some(cursor_rect) = inline_layout.get_cursor_rect(cursor) else {
6079 return;
6080 };
6081
6082 let Some(node_bounds) = self.get_node_bounds(dom_id, node_id) else {
6084 return;
6085 };
6086
6087 let cursor_abs_x = node_bounds.origin.x as f32 + cursor_rect.origin.x;
6089 let cursor_abs_y = node_bounds.origin.y as f32 + cursor_rect.origin.y;
6090
6091 let current_scroll = self
6097 .scroll_manager
6098 .get_current_offset(dom_id, node_id)
6099 .unwrap_or_default();
6100
6101 let viewport_x = node_bounds.origin.x as f32 + current_scroll.x;
6103 let viewport_y = node_bounds.origin.y as f32 + current_scroll.y;
6104 let viewport_width = node_bounds.size.width as f32;
6105 let viewport_height = node_bounds.size.height as f32;
6106
6107 let cursor_visible_x = (cursor_abs_x as f32) >= viewport_x
6109 && (cursor_abs_x as f32) <= viewport_x + viewport_width;
6110 let cursor_visible_y = (cursor_abs_y as f32) >= viewport_y
6111 && (cursor_abs_y as f32) <= viewport_y + viewport_height;
6112
6113 if cursor_visible_x && cursor_visible_y {
6114 return;
6116 }
6117
6118 let mut target_scroll_x = current_scroll.x;
6120 let mut target_scroll_y = current_scroll.y;
6121
6122 if (cursor_abs_x as f32) < viewport_x {
6124 target_scroll_x = cursor_abs_x as f32 - node_bounds.origin.x as f32;
6126 } else if (cursor_abs_x as f32) > viewport_x + viewport_width {
6127 target_scroll_x = cursor_abs_x as f32 - node_bounds.origin.x as f32 - viewport_width
6129 + cursor_rect.size.width;
6130 }
6131
6132 if (cursor_abs_y as f32) < viewport_y {
6134 target_scroll_y = cursor_abs_y as f32 - node_bounds.origin.y as f32;
6136 } else if (cursor_abs_y as f32) > viewport_y + viewport_height {
6137 target_scroll_y = cursor_abs_y as f32 - node_bounds.origin.y as f32 - viewport_height
6139 + cursor_rect.size.height;
6140 }
6141
6142 self.scroll_manager.scroll_to(
6144 dom_id,
6145 node_id,
6146 LogicalPosition {
6147 x: target_scroll_x,
6148 y: target_scroll_y,
6149 },
6150 std::time::Duration::from_millis(200).into(),
6151 azul_core::events::EasingFunction::EaseOut,
6152 now.into(),
6153 );
6154 }
6155
6156 fn byte_offset_to_cursor(
6171 &self,
6172 text_layout: &UnifiedLayout,
6173 byte_offset: u32,
6174 ) -> Option<TextCursor> {
6175 if byte_offset == 0 {
6177 for item in &text_layout.items {
6179 if let ShapedItem::Cluster(cluster) = &item.item {
6180 return Some(TextCursor {
6181 cluster_id: cluster.source_cluster_id,
6182 affinity: CursorAffinity::Trailing,
6183 });
6184 }
6185 }
6186 return Some(TextCursor {
6188 cluster_id: GraphemeClusterId {
6189 source_run: 0,
6190 start_byte_in_run: 0,
6191 },
6192 affinity: CursorAffinity::Trailing,
6193 });
6194 }
6195
6196 let mut current_byte_offset = 0u32;
6198
6199 for item in &text_layout.items {
6200 if let ShapedItem::Cluster(cluster) = &item.item {
6201 let cluster_byte_length = cluster.text.len() as u32;
6203 let cluster_end_byte = current_byte_offset + cluster_byte_length;
6204
6205 if byte_offset >= current_byte_offset && byte_offset <= cluster_end_byte {
6207 return Some(TextCursor {
6209 cluster_id: cluster.source_cluster_id,
6210 affinity: CursorAffinity::Trailing,
6211 });
6212 }
6213
6214 current_byte_offset = cluster_end_byte;
6215 }
6216 }
6217
6218 for item in text_layout.items.iter().rev() {
6220 if let ShapedItem::Cluster(cluster) = &item.item {
6221 return Some(TextCursor {
6222 cluster_id: cluster.source_cluster_id,
6223 affinity: CursorAffinity::Trailing,
6224 });
6225 }
6226 }
6227
6228 Some(TextCursor {
6230 cluster_id: GraphemeClusterId {
6231 source_run: 0,
6232 start_byte_in_run: 0,
6233 },
6234 affinity: CursorAffinity::Trailing,
6235 })
6236 }
6237
6238 fn get_node_inline_layout(
6243 &self,
6244 dom_id: DomId,
6245 node_id: NodeId,
6246 ) -> Option<alloc::sync::Arc<UnifiedLayout>> {
6247 let layout_tree = self.layout_cache.tree.as_ref()?;
6249
6250 let layout_node = layout_tree
6252 .nodes
6253 .iter()
6254 .find(|node| node.dom_node_id == Some(node_id))?;
6255
6256 layout_node
6258 .inline_layout_result
6259 .as_ref()
6260 .map(|c| c.clone_layout())
6261 }
6262
6263 pub fn sync_cursor_to_selection_manager(&mut self) {
6271 if let Some(cursor) = self.cursor_manager.get_cursor() {
6272 if let Some(location) = self.cursor_manager.get_cursor_location() {
6273 let selection = Selection::Cursor(cursor.clone());
6275
6276 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(location.node_id));
6278 let dom_node_id = DomNodeId {
6279 dom: location.dom_id,
6280 node: hierarchy_id,
6281 };
6282
6283 let selection_state = SelectionState {
6284 selections: vec![selection].into(),
6285 node_id: dom_node_id,
6286 };
6287
6288 self.selection_manager
6290 .set_selection(location.dom_id, selection_state);
6291 }
6292 }
6293 }
6297
6298 #[must_use = "Returned nodes must be marked dirty for re-layout"]
6314 #[cfg(feature = "a11y")]
6315 pub fn edit_text_node(
6316 &mut self,
6317 dom_id: DomId,
6318 node_id: NodeId,
6319 edit_type: TextEditType,
6320 ) -> Vec<azul_core::dom::DomNodeId> {
6321 use crate::managers::text_input::TextInputSource;
6322
6323 let text_input = match &edit_type {
6325 TextEditType::ReplaceSelection(text) => text.clone(),
6326 TextEditType::SetValue(text) => text.clone(),
6327 TextEditType::SetNumericValue(value) => value.to_string(),
6328 };
6329
6330 let old_inline_content = self.get_text_before_textinput(dom_id, node_id);
6332 let old_text = self.extract_text_from_inline_content(&old_inline_content);
6333
6334 let hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(node_id));
6336 let dom_node_id = azul_core::dom::DomNodeId {
6337 dom: dom_id,
6338 node: hierarchy_id,
6339 };
6340
6341 self.text_input_manager.record_input(
6343 dom_node_id,
6344 text_input,
6345 old_text,
6346 TextInputSource::Accessibility, );
6348
6349 self.apply_text_changeset()
6351 }
6352
6353 #[cfg(not(feature = "a11y"))]
6354 pub fn process_accessibility_action(
6355 &mut self,
6356 _dom_id: DomId,
6357 _node_id: NodeId,
6358 _action: azul_core::dom::AccessibilityAction,
6359 _now: std::time::Instant,
6360 ) -> BTreeMap<DomNodeId, (Vec<azul_core::events::EventFilter>, bool)> {
6361 BTreeMap::new()
6363 }
6364
6365 pub fn process_mouse_click_for_selection(
6393 &mut self,
6394 position: azul_core::geom::LogicalPosition,
6395 time_ms: u64,
6396 ) -> Option<Vec<azul_core::dom::DomNodeId>> {
6397 use crate::managers::hover::InputPointId;
6398 use crate::text3::selection::{select_paragraph_at_cursor, select_word_at_cursor};
6399
6400 let mut found_selection: Option<(DomId, NodeId, SelectionRange, azul_core::geom::LogicalPosition)> = None;
6404
6405 if let Some(hit_test) = self.hover_manager.get_current(&InputPointId::Mouse) {
6407 for (dom_id, hit) in &hit_test.hovered_nodes {
6409 let layout_result = match self.layout_results.get(dom_id) {
6410 Some(lr) => lr,
6411 None => continue,
6412 };
6413 let tree = &layout_result.layout_tree;
6415
6416 let node_hierarchy = layout_result.styled_dom.node_hierarchy.as_container();
6420 let get_dom_depth = |node_id: &NodeId| -> usize {
6421 let mut depth = 0;
6422 let mut current = *node_id;
6423 while let Some(parent) = node_hierarchy.get(current).and_then(|h| h.parent_id()) {
6424 depth += 1;
6425 current = parent;
6426 }
6427 depth
6428 };
6429
6430 let mut sorted_hits: Vec<_> = hit.regular_hit_test_nodes.iter().collect();
6431 sorted_hits.sort_by(|(a_id, _), (b_id, _)| {
6432 let depth_a = get_dom_depth(a_id);
6433 let depth_b = get_dom_depth(b_id);
6434 depth_b.cmp(&depth_a).then_with(|| a_id.index().cmp(&b_id.index()))
6437 });
6438
6439 for (node_id, hit_item) in sorted_hits {
6440 if !self.is_text_selectable(&layout_result.styled_dom, *node_id) {
6442 continue;
6443 }
6444
6445 let layout_node_idx = tree.nodes.iter().position(|n| n.dom_node_id == Some(*node_id));
6447 let layout_node_idx = match layout_node_idx {
6448 Some(idx) => idx,
6449 None => continue,
6450 };
6451 let layout_node = &tree.nodes[layout_node_idx];
6452
6453 let (cached_layout, ifc_root_node_id) = if let Some(ref cached) = layout_node.inline_layout_result {
6456 (cached, *node_id)
6458 } else if let Some(ref membership) = layout_node.ifc_membership {
6459 match tree.nodes.get(membership.ifc_root_layout_index) {
6461 Some(ifc_root) => match (ifc_root.inline_layout_result.as_ref(), ifc_root.dom_node_id) {
6462 (Some(cached), Some(root_dom_id)) => (cached, root_dom_id),
6463 _ => continue,
6464 },
6465 None => continue,
6466 }
6467 } else {
6468 continue;
6470 };
6471
6472 let layout = &cached_layout.layout;
6473
6474 let local_pos = hit_item.point_relative_to_item;
6477
6478 if let Some(cursor) = layout.hittest_cursor(local_pos) {
6480 found_selection = Some((*dom_id, ifc_root_node_id, SelectionRange {
6482 start: cursor.clone(),
6483 end: cursor,
6484 }, local_pos));
6485 break;
6486 }
6487 }
6488
6489 if found_selection.is_some() {
6490 break;
6491 }
6492 }
6493 }
6494
6495 if found_selection.is_none() {
6498 for (dom_id, layout_result) in &self.layout_results {
6499 let tree = &layout_result.layout_tree;
6503
6504 for (node_idx, layout_node) in tree.nodes.iter().enumerate() {
6506 let cached_layout = match layout_node.inline_layout_result.as_ref() {
6507 Some(c) => c,
6508 None => continue, };
6510
6511 let node_id = match layout_node.dom_node_id {
6512 Some(n) => n,
6513 None => continue,
6514 };
6515
6516 if !self.is_text_selectable(&layout_result.styled_dom, node_id) {
6518 continue;
6519 }
6520
6521 let node_pos = layout_result.calculated_positions
6524 .get(node_idx)
6525 .copied()
6526 .unwrap_or_default();
6527
6528 let node_size = layout_node.used_size.unwrap_or_else(|| {
6530 let bounds = cached_layout.layout.bounds();
6531 azul_core::geom::LogicalSize::new(bounds.width, bounds.height)
6532 });
6533
6534 if position.x < node_pos.x || position.x > node_pos.x + node_size.width ||
6535 position.y < node_pos.y || position.y > node_pos.y + node_size.height {
6536 continue;
6537 }
6538
6539 let local_pos = azul_core::geom::LogicalPosition {
6541 x: position.x - node_pos.x,
6542 y: position.y - node_pos.y,
6543 };
6544
6545 let layout = &cached_layout.layout;
6546
6547 if let Some(cursor) = layout.hittest_cursor(local_pos) {
6549 found_selection = Some((*dom_id, node_id, SelectionRange {
6550 start: cursor.clone(),
6551 end: cursor,
6552 }, local_pos));
6553 break;
6554 }
6555 }
6556
6557 if found_selection.is_some() {
6558 break;
6559 }
6560 }
6561 }
6562
6563 let (dom_id, ifc_root_node_id, initial_range, _local_pos) = found_selection?;
6564
6565 let node_hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(ifc_root_node_id));
6568 let dom_node_id = azul_core::dom::DomNodeId {
6569 dom: dom_id,
6570 node: node_hierarchy_id,
6571 };
6572
6573 let click_count = self
6575 .selection_manager
6576 .update_click_count(dom_node_id, position, time_ms);
6577
6578 let final_range = if click_count > 1 {
6580 let layout_result = self.layout_results.get(&dom_id)?;
6582 let tree = &layout_result.layout_tree;
6583
6584 let layout_node = tree.nodes.iter().find(|n| n.dom_node_id == Some(ifc_root_node_id))?;
6586 let cached_layout = layout_node.inline_layout_result.as_ref()?;
6587 let layout = &cached_layout.layout;
6588
6589 match click_count {
6590 2 => select_word_at_cursor(&initial_range.start, layout.as_ref())
6591 .unwrap_or(initial_range),
6592 3 => select_paragraph_at_cursor(&initial_range.start, layout.as_ref())
6593 .unwrap_or(initial_range),
6594 _ => initial_range,
6595 }
6596 } else {
6597 initial_range
6598 };
6599
6600 let char_bounds = {
6603 let layout_result = self.layout_results.get(&dom_id)?;
6604 let tree = &layout_result.layout_tree;
6605 let layout_node = tree.nodes.iter().find(|n| n.dom_node_id == Some(ifc_root_node_id))?;
6606 let cached_layout = layout_node.inline_layout_result.as_ref()?;
6607 cached_layout.layout.get_cursor_rect(&final_range.start)
6608 .unwrap_or(azul_core::geom::LogicalRect {
6609 origin: position,
6610 size: azul_core::geom::LogicalSize { width: 1.0, height: 16.0 },
6611 })
6612 };
6613
6614 self.selection_manager.clear_text_selection(&dom_id);
6616
6617 self.selection_manager.start_selection(
6619 dom_id,
6620 ifc_root_node_id,
6621 final_range.start,
6622 char_bounds,
6623 position,
6624 );
6625
6626 self.selection_manager.clear_selection(&dom_id);
6628
6629 let state = SelectionState {
6630 selections: vec![Selection::Range(final_range)].into(),
6631 node_id: dom_node_id,
6632 };
6633
6634 self.selection_manager.set_selection(dom_id, state);
6635
6636 let is_contenteditable = self.layout_results.get(&dom_id)
6644 .map(|lr| {
6645 let node_hierarchy = lr.styled_dom.node_hierarchy.as_container();
6646 let node_data = lr.styled_dom.node_data.as_ref();
6647
6648 let mut current_node = Some(ifc_root_node_id);
6650 while let Some(node_id) = current_node {
6651 if let Some(styled_node) = node_data.get(node_id.index()) {
6652 if styled_node.contenteditable {
6656 return true;
6657 }
6658
6659 let has_contenteditable_attr = styled_node.attributes.as_ref().iter().any(|attr| {
6661 matches!(attr, azul_core::dom::AttributeType::ContentEditable(_))
6662 });
6663 if has_contenteditable_attr {
6664 return true;
6665 }
6666 }
6667 current_node = node_hierarchy.get(node_id).and_then(|h| h.parent_id());
6669 }
6670 false
6671 })
6672 .unwrap_or(false);
6673
6674 if is_contenteditable {
6676 self.focus_manager.set_focused_node(Some(dom_node_id));
6677 }
6678
6679 let now = azul_core::task::Instant::now();
6683 self.cursor_manager.move_cursor_to(
6684 final_range.start.clone(),
6685 dom_id,
6686 ifc_root_node_id,
6687 );
6688 self.cursor_manager.reset_blink_on_input(now);
6690 self.cursor_manager.set_blink_timer_active(true);
6691
6692 Some(vec![dom_node_id])
6694 }
6695
6696 pub fn process_mouse_drag_for_selection(
6713 &mut self,
6714 _start_position: azul_core::geom::LogicalPosition,
6715 current_position: azul_core::geom::LogicalPosition,
6716 ) -> Option<Vec<azul_core::dom::DomNodeId>> {
6717 use crate::managers::hover::InputPointId;
6718
6719 let dom_id = self.selection_manager.get_all_text_selections()
6721 .keys()
6722 .next()
6723 .copied()?;
6724
6725 let anchor = {
6727 let text_selection = self.selection_manager.get_text_selection(&dom_id)?;
6728 text_selection.anchor.clone()
6729 };
6730
6731 let hit_test = self.hover_manager.get_current(&InputPointId::Mouse)?;
6733
6734 let mut focus_info: Option<(NodeId, TextCursor, azul_core::geom::LogicalPosition)> = None;
6736
6737 for (hit_dom_id, hit) in &hit_test.hovered_nodes {
6738 if *hit_dom_id != dom_id {
6739 continue;
6740 }
6741
6742 let layout_result = match self.layout_results.get(hit_dom_id) {
6743 Some(lr) => lr,
6744 None => continue,
6745 };
6746 let tree = &layout_result.layout_tree;
6747
6748 for (node_id, hit_item) in &hit.regular_hit_test_nodes {
6749 if !self.is_text_selectable(&layout_result.styled_dom, *node_id) {
6750 continue;
6751 }
6752
6753 let layout_node_idx = tree.nodes.iter().position(|n| n.dom_node_id == Some(*node_id));
6754 let layout_node_idx = match layout_node_idx {
6755 Some(idx) => idx,
6756 None => continue,
6757 };
6758 let layout_node = &tree.nodes[layout_node_idx];
6759
6760 let (cached_layout, ifc_root_node_id) = if let Some(ref cached) = layout_node.inline_layout_result {
6762 (cached, *node_id)
6763 } else if let Some(ref membership) = layout_node.ifc_membership {
6764 match tree.nodes.get(membership.ifc_root_layout_index) {
6765 Some(ifc_root) => match (ifc_root.inline_layout_result.as_ref(), ifc_root.dom_node_id) {
6766 (Some(cached), Some(root_dom_id)) => (cached, root_dom_id),
6767 _ => continue,
6768 },
6769 None => continue,
6770 }
6771 } else {
6772 continue;
6773 };
6774
6775 let local_pos = hit_item.point_relative_to_item;
6776
6777 if let Some(cursor) = cached_layout.layout.hittest_cursor(local_pos) {
6778 focus_info = Some((ifc_root_node_id, cursor, current_position));
6779 break;
6780 }
6781 }
6782
6783 if focus_info.is_some() {
6784 break;
6785 }
6786 }
6787
6788 let (focus_ifc_root, focus_cursor, focus_mouse_pos) = focus_info?;
6789
6790 let layout_result = self.layout_results.get(&dom_id)?;
6792 let hierarchy = &layout_result.styled_dom.node_hierarchy;
6793
6794 let is_forward = if anchor.ifc_root_node_id == focus_ifc_root {
6796 anchor.cursor <= focus_cursor
6798 } else {
6799 is_before_in_document_order(hierarchy, anchor.ifc_root_node_id, focus_ifc_root)
6801 };
6802
6803 let (start_node, end_node) = if is_forward {
6804 (anchor.ifc_root_node_id, focus_ifc_root)
6805 } else {
6806 (focus_ifc_root, anchor.ifc_root_node_id)
6807 };
6808
6809 let nodes_in_range = collect_nodes_in_document_order(hierarchy, start_node, end_node);
6811
6812 let mut affected_nodes_map = std::collections::BTreeMap::new();
6814 let tree = &layout_result.layout_tree;
6815
6816 for node_id in &nodes_in_range {
6817 let layout_node = tree.nodes.iter().find(|n| n.dom_node_id == Some(*node_id));
6819 let layout_node = match layout_node {
6820 Some(ln) if ln.inline_layout_result.is_some() => ln,
6821 _ => continue, };
6823
6824 let cached_layout = layout_node.inline_layout_result.as_ref()?;
6825 let layout = &cached_layout.layout;
6826
6827 let range = if *node_id == anchor.ifc_root_node_id && *node_id == focus_ifc_root {
6828 SelectionRange {
6830 start: if is_forward { anchor.cursor } else { focus_cursor },
6831 end: if is_forward { focus_cursor } else { anchor.cursor },
6832 }
6833 } else if *node_id == anchor.ifc_root_node_id {
6834 if is_forward {
6836 let end_cursor = layout.get_last_cluster_cursor()
6837 .unwrap_or(anchor.cursor);
6838 SelectionRange { start: anchor.cursor, end: end_cursor }
6839 } else {
6840 let start_cursor = layout.get_first_cluster_cursor()
6841 .unwrap_or(anchor.cursor);
6842 SelectionRange { start: start_cursor, end: anchor.cursor }
6843 }
6844 } else if *node_id == focus_ifc_root {
6845 if is_forward {
6847 let start_cursor = layout.get_first_cluster_cursor()
6848 .unwrap_or(focus_cursor);
6849 SelectionRange { start: start_cursor, end: focus_cursor }
6850 } else {
6851 let end_cursor = layout.get_last_cluster_cursor()
6852 .unwrap_or(focus_cursor);
6853 SelectionRange { start: focus_cursor, end: end_cursor }
6854 }
6855 } else {
6856 let start_cursor = layout.get_first_cluster_cursor()?;
6858 let end_cursor = layout.get_last_cluster_cursor()?;
6859 SelectionRange { start: start_cursor, end: end_cursor }
6860 };
6861
6862 affected_nodes_map.insert(*node_id, range);
6863 }
6864
6865 self.selection_manager.update_selection_focus(
6868 &dom_id,
6869 focus_ifc_root,
6870 focus_cursor,
6871 focus_mouse_pos,
6872 affected_nodes_map.clone(),
6873 is_forward,
6874 );
6875
6876 if let Some(anchor_range) = affected_nodes_map.get(&anchor.ifc_root_node_id) {
6879 let node_hierarchy_id = NodeHierarchyItemId::from_crate_internal(Some(anchor.ifc_root_node_id));
6880 let dom_node_id = azul_core::dom::DomNodeId {
6881 dom: dom_id,
6882 node: node_hierarchy_id,
6883 };
6884
6885 let state = SelectionState {
6886 selections: vec![Selection::Range(*anchor_range)].into(),
6887 node_id: dom_node_id,
6888 };
6889 self.selection_manager.set_selection(dom_id, state);
6890 }
6891
6892 let affected_dom_nodes: Vec<azul_core::dom::DomNodeId> = affected_nodes_map.keys()
6894 .map(|node_id| azul_core::dom::DomNodeId {
6895 dom: dom_id,
6896 node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
6897 })
6898 .collect();
6899
6900 if affected_dom_nodes.is_empty() {
6901 None
6902 } else {
6903 Some(affected_dom_nodes)
6904 }
6905 }
6906
6907 pub fn delete_selection(
6920 &mut self,
6921 target: azul_core::dom::DomNodeId,
6922 forward: bool,
6923 ) -> Option<Vec<azul_core::dom::DomNodeId>> {
6924 let dom_id = target.dom;
6925
6926 let ranges = self.selection_manager.get_ranges(&dom_id);
6928 if ranges.is_empty() {
6929 return None; }
6931
6932 let mut earliest_cursor = None;
6939 for range in &ranges {
6940 let cursor = if forward { range.end } else { range.start };
6942
6943 if earliest_cursor.is_none() {
6944 earliest_cursor = Some(cursor);
6945 } else if let Some(current) = earliest_cursor {
6946 if cursor < current {
6949 earliest_cursor = Some(cursor);
6950 }
6951 }
6952 }
6953
6954 self.selection_manager.clear_selection(&dom_id);
6956
6957 if let Some(cursor) = earliest_cursor {
6958 let state = SelectionState {
6960 selections: vec![Selection::Range(SelectionRange {
6961 start: cursor.clone(),
6962 end: cursor,
6963 })]
6964 .into(),
6965 node_id: target,
6966 };
6967 self.selection_manager.set_selection(dom_id, state);
6968 }
6969
6970 Some(vec![target])
6972 }
6973
6974 pub fn get_selected_content_for_clipboard(
6990 &self,
6991 dom_id: &DomId,
6992 ) -> Option<crate::managers::selection::ClipboardContent> {
6993 use crate::{
6994 managers::selection::{ClipboardContent, StyledTextRun},
6995 text3::cache::ShapedItem,
6996 };
6997
6998 let ranges = self.selection_manager.get_ranges(dom_id);
7000 if ranges.is_empty() {
7001 return None;
7002 }
7003
7004 let mut plain_text = String::new();
7005 let mut styled_runs = Vec::new();
7006
7007 for cache_id in self.text_cache.get_all_layout_ids() {
7009 let layout = self.text_cache.get_layout(&cache_id)?;
7010
7011 for range in &ranges {
7013 for positioned_item in &layout.items {
7015 match &positioned_item.item {
7016 ShapedItem::Cluster(cluster) => {
7017 let cluster_id = cluster.source_cluster_id;
7019
7020 let in_range = if range.start.cluster_id <= range.end.cluster_id {
7022 cluster_id >= range.start.cluster_id
7023 && cluster_id <= range.end.cluster_id
7024 } else {
7025 cluster_id >= range.end.cluster_id
7026 && cluster_id <= range.start.cluster_id
7027 };
7028
7029 if in_range {
7030 plain_text.push_str(&cluster.text);
7032
7033 if let Some(first_glyph) = cluster.glyphs.first() {
7035 let style = &first_glyph.style;
7036
7037 let default_font = FontSelector::default();
7039 let first_font = style.font_stack.first_selector()
7040 .unwrap_or(&default_font);
7041 let font_family: OptionString =
7042 Some(AzString::from(first_font.family.as_str())).into();
7043
7044 use rust_fontconfig::FcWeight;
7046 let is_bold = matches!(
7047 first_font.weight,
7048 FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black
7049 );
7050 let is_italic = matches!(
7051 first_font.style,
7052 FontStyle::Italic | FontStyle::Oblique
7053 );
7054
7055 styled_runs.push(StyledTextRun {
7056 text: cluster.text.clone().into(),
7057 font_family,
7058 font_size_px: style.font_size_px,
7059 color: style.color,
7060 is_bold,
7061 is_italic,
7062 });
7063 }
7064 }
7065 }
7066 _ => {}
7068 }
7069 }
7070 }
7071 }
7072
7073 if plain_text.is_empty() {
7074 None
7075 } else {
7076 Some(ClipboardContent {
7077 plain_text: plain_text.into(),
7078 styled_runs: styled_runs.into(),
7079 })
7080 }
7081 }
7082
7083 pub fn process_image_callback_updates(
7098 &mut self,
7099 image_callbacks_changed: &BTreeMap<DomId, FastBTreeSet<NodeId>>,
7100 gl_context: &OptionGlContextPtr,
7101 ) -> Vec<(DomId, NodeId, azul_core::gl::Texture)> {
7102 use crate::callbacks::{RenderImageCallback, RenderImageCallbackInfo};
7103
7104 let mut updated_textures = Vec::new();
7105
7106 for (dom_id, node_ids) in image_callbacks_changed {
7107 let layout_result = match self.layout_results.get_mut(dom_id) {
7108 Some(lr) => lr,
7109 None => continue,
7110 };
7111
7112 for node_id in node_ids {
7113 let node_data_container = layout_result.styled_dom.node_data.as_container();
7115 let node_data = match node_data_container.get(*node_id) {
7116 Some(nd) => nd,
7117 None => continue,
7118 };
7119
7120 let has_callback = matches!(node_data.get_node_type(), NodeType::Image(img_ref)
7122 if img_ref.get_image_callback().is_some());
7123
7124 if !has_callback {
7125 continue;
7126 }
7127
7128 let layout_indices = match layout_result.layout_tree.dom_to_layout.get(node_id) {
7131 Some(indices) if !indices.is_empty() => indices,
7132 _ => continue,
7133 };
7134
7135 let layout_index = layout_indices[0];
7137
7138 let position = match layout_result.calculated_positions.get(layout_index) {
7140 Some(pos) => *pos,
7141 None => continue,
7142 };
7143
7144 let layout_node = match layout_result.layout_tree.get(layout_index) {
7146 Some(ln) => ln,
7147 None => continue,
7148 };
7149
7150 let (width, height) = match layout_node.used_size {
7152 Some(size) => (size.width, size.height),
7153 None => continue, };
7155
7156 let callback_domnode_id = DomNodeId {
7157 dom: *dom_id,
7158 node: azul_core::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(
7159 *node_id,
7160 )),
7161 };
7162
7163 let bounds = HidpiAdjustedBounds::from_bounds(
7164 azul_css::props::basic::LayoutSize {
7165 width: width as isize,
7166 height: height as isize,
7167 },
7168 self.current_window_state.size.get_hidpi_factor(),
7169 );
7170
7171 let mut gl_callback_info = RenderImageCallbackInfo::new(
7173 callback_domnode_id,
7174 bounds,
7175 gl_context,
7176 &self.image_cache,
7177 &self.font_manager.fc_cache,
7178 );
7179
7180 let new_image_ref = {
7182 let mut node_data_mut = layout_result.styled_dom.node_data.as_container_mut();
7183 match node_data_mut.get_mut(*node_id) {
7184 Some(nd) => {
7185 match &mut nd.node_type {
7186 NodeType::Image(img_ref) => {
7187 img_ref.get_image_callback_mut().map(|core_callback| {
7188 let callback =
7191 RenderImageCallback::from_core(&core_callback.callback);
7192 (callback.cb)(
7193 core_callback.refany.clone(),
7194 gl_callback_info,
7195 )
7196 })
7197 }
7198 _ => None,
7199 }
7200 }
7201 None => None,
7202 }
7203 };
7204
7205 #[cfg(feature = "gl_context_loader")]
7207 if let Some(gl) = gl_context.as_ref() {
7208 use gl_context_loader::gl;
7209 gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
7210 gl.disable(gl::FRAMEBUFFER_SRGB);
7211 gl.disable(gl::MULTISAMPLE);
7212 }
7213
7214 if let Some(image_ref) = new_image_ref {
7216 if let Some(decoded_image) = image_ref.into_inner() {
7217 if let azul_core::resources::DecodedImage::Gl(texture) = decoded_image {
7218 updated_textures.push((*dom_id, *node_id, texture));
7219 }
7220 }
7221 }
7222 }
7223 }
7224
7225 updated_textures
7226 }
7227
7228 pub fn process_iframe_updates(
7245 &mut self,
7246 iframes_to_update: &BTreeMap<DomId, FastBTreeSet<NodeId>>,
7247 window_state: &FullWindowState,
7248 renderer_resources: &RendererResources,
7249 system_callbacks: &ExternalSystemCallbacks,
7250 ) -> Vec<(DomId, NodeId)> {
7251 let mut updated_iframes = Vec::new();
7252
7253 for (dom_id, node_ids) in iframes_to_update {
7254 for node_id in node_ids {
7255 let bounds = match Self::get_iframe_bounds_from_layout(
7257 &self.layout_results,
7258 *dom_id,
7259 *node_id,
7260 ) {
7261 Some(b) => b,
7262 None => continue,
7263 };
7264
7265 self.iframe_manager.force_reinvoke(*dom_id, *node_id);
7267
7268 if let Some(_child_dom_id) = self.invoke_iframe_callback(
7270 *dom_id,
7271 *node_id,
7272 bounds,
7273 window_state,
7274 renderer_resources,
7275 system_callbacks,
7276 &mut None,
7277 ) {
7278 updated_iframes.push((*dom_id, *node_id));
7279 }
7280 }
7281 }
7282
7283 updated_iframes
7284 }
7285
7286 pub fn queue_iframe_updates(
7290 &mut self,
7291 iframes_to_update: BTreeMap<DomId, FastBTreeSet<NodeId>>,
7292 ) {
7293 for (dom_id, node_ids) in iframes_to_update {
7294 self.pending_iframe_updates
7295 .entry(dom_id)
7296 .or_insert_with(FastBTreeSet::new)
7297 .extend(node_ids);
7298 }
7299 }
7300
7301 pub fn process_pending_iframe_updates(
7305 &mut self,
7306 window_state: &FullWindowState,
7307 renderer_resources: &RendererResources,
7308 system_callbacks: &ExternalSystemCallbacks,
7309 ) -> Vec<(DomId, NodeId)> {
7310 if self.pending_iframe_updates.is_empty() {
7311 return Vec::new();
7312 }
7313
7314 let iframes_to_update = core::mem::take(&mut self.pending_iframe_updates);
7316
7317 self.process_iframe_updates(
7319 &iframes_to_update,
7320 window_state,
7321 renderer_resources,
7322 system_callbacks,
7323 )
7324 }
7325
7326 fn get_iframe_bounds_from_layout(
7330 layout_results: &BTreeMap<DomId, DomLayoutResult>,
7331 dom_id: DomId,
7332 node_id: NodeId,
7333 ) -> Option<LogicalRect> {
7334 let layout_result = layout_results.get(&dom_id)?;
7335
7336 let node_data_container = layout_result.styled_dom.node_data.as_container();
7338 let node_data = node_data_container.get(node_id)?;
7339
7340 if !matches!(node_data.get_node_type(), NodeType::IFrame(_)) {
7341 return None;
7342 }
7343
7344 let layout_indices = layout_result.layout_tree.dom_to_layout.get(&node_id)?;
7346 if layout_indices.is_empty() {
7347 return None;
7348 }
7349
7350 let layout_index = layout_indices[0];
7351
7352 let position = *layout_result.calculated_positions.get(layout_index)?;
7354
7355 let layout_node = layout_result.layout_tree.get(layout_index)?;
7357 let size = layout_node.used_size?;
7358
7359 Some(LogicalRect::new(
7360 position,
7361 LogicalSize::new(size.width as f32, size.height as f32),
7362 ))
7363 }
7364}
7365
7366#[cfg(feature = "a11y")]
7367#[derive(Debug, Clone)]
7368pub enum TextEditType {
7369 ReplaceSelection(String),
7370 SetValue(String),
7371 SetNumericValue(f64),
7372}