1use rustc_hash::FxHashMap;
5
6use crate::align::{AlignX, AlignY};
7use crate::color::Color;
8use crate::elements::BorderPosition;
9use crate::renderer::ImageSource;
10use crate::shaders::ShaderConfig;
11use crate::elements::{
12 FloatingAttachToElement, FloatingClipToElement, PointerCaptureMode,
13};
14use crate::layout::{LayoutDirection, CornerRadius};
15use crate::math::{BoundingBox, Dimensions, Vector2};
16use crate::text::{TextConfig, WrapMode};
17
18const DEFAULT_MAX_ELEMENT_COUNT: i32 = 8192;
19const DEFAULT_MAX_MEASURE_TEXT_WORD_CACHE_COUNT: i32 = 16384;
20const MAXFLOAT: f32 = 3.40282346638528859812e+38;
21const EPSILON: f32 = 0.01;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24#[repr(u8)]
25pub enum SizingType {
26 #[default]
27 Fit,
28 Grow,
29 Percent,
30 Fixed,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34#[repr(u8)]
35pub enum RenderCommandType {
36 #[default]
37 None,
38 Rectangle,
39 Border,
40 Text,
41 Image,
42 ScissorStart,
43 ScissorEnd,
44 Custom,
45 GroupBegin,
46 GroupEnd,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
50#[repr(u8)]
51pub enum PointerDataInteractionState {
52 PressedThisFrame,
53 Pressed,
54 ReleasedThisFrame,
55 #[default]
56 Released,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum ArrowDirection {
61 Left,
62 Right,
63 Up,
64 Down,
65}
66
67#[derive(Debug, Clone)]
69pub enum TextInputAction {
70 MoveLeft { shift: bool },
71 MoveRight { shift: bool },
72 MoveWordLeft { shift: bool },
73 MoveWordRight { shift: bool },
74 MoveHome { shift: bool },
75 MoveEnd { shift: bool },
76 MoveUp { shift: bool },
77 MoveDown { shift: bool },
78 Backspace,
79 Delete,
80 BackspaceWord,
81 DeleteWord,
82 SelectAll,
83 Copy,
84 Cut,
85 Paste { text: String },
86 Submit,
87 Undo,
88 Redo,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[repr(u8)]
93pub enum ElementConfigType {
94 Shared,
95 Text,
96 Image,
97 Floating,
98 Custom,
99 Clip,
100 Border,
101 Aspect,
102 TextInput,
103}
104
105#[derive(Debug, Clone, Copy, Default)]
106pub struct SizingMinMax {
107 pub min: f32,
108 pub max: f32,
109}
110
111#[derive(Debug, Clone, Copy)]
112pub struct SizingAxis {
113 pub type_: SizingType,
114 pub min_max: SizingMinMax,
115 pub percent: f32,
116 pub grow_weight: f32,
117}
118
119impl Default for SizingAxis {
120 fn default() -> Self {
121 Self {
122 type_: SizingType::Fit,
123 min_max: SizingMinMax::default(),
124 percent: 0.0,
125 grow_weight: 1.0,
126 }
127 }
128}
129
130#[derive(Debug, Clone, Copy, Default)]
131pub struct SizingConfig {
132 pub width: SizingAxis,
133 pub height: SizingAxis,
134}
135
136#[derive(Debug, Clone, Copy, Default)]
137pub struct PaddingConfig {
138 pub left: u16,
139 pub right: u16,
140 pub top: u16,
141 pub bottom: u16,
142}
143
144#[derive(Debug, Clone, Copy, Default)]
145pub struct ChildAlignmentConfig {
146 pub x: AlignX,
147 pub y: AlignY,
148}
149
150#[derive(Debug, Clone, Copy, Default)]
151pub struct LayoutConfig {
152 pub sizing: SizingConfig,
153 pub padding: PaddingConfig,
154 pub child_gap: u16,
155 pub wrap: bool,
156 pub wrap_gap: u16,
157 pub child_alignment: ChildAlignmentConfig,
158 pub layout_direction: LayoutDirection,
159}
160
161
162#[derive(Debug, Clone, Copy)]
163pub struct VisualRotationConfig {
164 pub rotation_radians: f32,
166 pub pivot_x: f32,
168 pub pivot_y: f32,
170 pub flip_x: bool,
172 pub flip_y: bool,
174}
175
176impl Default for VisualRotationConfig {
177 fn default() -> Self {
178 Self {
179 rotation_radians: 0.0,
180 pivot_x: 0.5,
181 pivot_y: 0.5,
182 flip_x: false,
183 flip_y: false,
184 }
185 }
186}
187
188impl VisualRotationConfig {
189 pub fn is_noop(&self) -> bool {
191 self.rotation_radians == 0.0 && !self.flip_x && !self.flip_y
192 }
193}
194
195#[derive(Debug, Clone, Copy)]
196pub struct ShapeRotationConfig {
197 pub rotation_radians: f32,
199 pub flip_x: bool,
201 pub flip_y: bool,
203}
204
205impl Default for ShapeRotationConfig {
206 fn default() -> Self {
207 Self {
208 rotation_radians: 0.0,
209 flip_x: false,
210 flip_y: false,
211 }
212 }
213}
214
215impl ShapeRotationConfig {
216 pub fn is_noop(&self) -> bool {
218 self.rotation_radians == 0.0 && !self.flip_x && !self.flip_y
219 }
220}
221
222#[derive(Debug, Clone, Copy, Default)]
223pub struct FloatingAttachPoints {
224 pub element_x: AlignX,
225 pub element_y: AlignY,
226 pub parent_x: AlignX,
227 pub parent_y: AlignY,
228}
229
230#[derive(Debug, Clone, Copy, Default)]
231pub struct FloatingConfig {
232 pub offset: Vector2,
233 pub parent_id: u32,
234 pub z_index: i16,
235 pub attach_points: FloatingAttachPoints,
236 pub pointer_capture_mode: PointerCaptureMode,
237 pub attach_to: FloatingAttachToElement,
238 pub clip_to: FloatingClipToElement,
239}
240
241#[derive(Debug, Clone, Copy, Default)]
242pub struct ClipConfig {
243 pub horizontal: bool,
244 pub vertical: bool,
245 pub scroll_x: bool,
246 pub scroll_y: bool,
247 pub no_drag_scroll: bool,
248 pub child_offset: Vector2,
249 pub scrollbar: Option<ScrollbarConfig>,
250}
251
252#[derive(Debug, Clone, Copy)]
253pub struct ScrollbarConfig {
254 pub width: f32,
255 pub corner_radius: f32,
256 pub thumb_color: Color,
257 pub track_color: Option<Color>,
258 pub min_thumb_size: f32,
259 pub hide_after_frames: Option<u32>,
260}
261
262impl Default for ScrollbarConfig {
263 fn default() -> Self {
264 Self {
265 width: 6.0,
266 corner_radius: 3.0,
267 thumb_color: Color::rgba(128.0, 128.0, 128.0, 128.0),
268 track_color: None,
269 min_thumb_size: 20.0,
270 hide_after_frames: None,
271 }
272 }
273}
274
275#[derive(Debug, Clone, Copy, Default)]
276pub struct BorderWidth {
277 pub left: u16,
278 pub right: u16,
279 pub top: u16,
280 pub bottom: u16,
281 pub between_children: u16,
282}
283
284impl BorderWidth {
285 pub fn is_zero(&self) -> bool {
286 self.left == 0
287 && self.right == 0
288 && self.top == 0
289 && self.bottom == 0
290 && self.between_children == 0
291 }
292}
293
294#[derive(Debug, Clone, Copy, Default)]
295pub struct BorderConfig {
296 pub color: Color,
297 pub width: BorderWidth,
298 pub position: BorderPosition,
299}
300
301#[derive(Debug, Clone)]
303pub struct ElementDeclaration<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
304 pub layout: LayoutConfig,
305 pub background_color: Color,
306 pub corner_radius: CornerRadius,
307 pub aspect_ratio: f32,
308 pub cover_aspect_ratio: bool,
309 pub image_data: Option<ImageSource>,
310 pub floating: FloatingConfig,
311 pub custom_data: Option<CustomElementData>,
312 pub clip: ClipConfig,
313 pub border: BorderConfig,
314 pub user_data: usize,
315 pub effects: Vec<ShaderConfig>,
316 pub shaders: Vec<ShaderConfig>,
317 pub visual_rotation: Option<VisualRotationConfig>,
318 pub shape_rotation: Option<ShapeRotationConfig>,
319 pub accessibility: Option<crate::accessibility::AccessibilityConfig>,
320 pub text_input: Option<crate::text_input::TextInputConfig>,
321 pub preserve_focus: bool,
322}
323
324impl<CustomElementData: Clone + Default + std::fmt::Debug> Default for ElementDeclaration<CustomElementData> {
325 fn default() -> Self {
326 Self {
327 layout: LayoutConfig::default(),
328 background_color: Color::rgba(0.0, 0.0, 0.0, 0.0),
329 corner_radius: CornerRadius::default(),
330 aspect_ratio: 0.0,
331 cover_aspect_ratio: false,
332 image_data: None,
333 floating: FloatingConfig::default(),
334 custom_data: None,
335 clip: ClipConfig::default(),
336 border: BorderConfig::default(),
337 user_data: 0,
338 effects: Vec::new(),
339 shaders: Vec::new(),
340 visual_rotation: None,
341 shape_rotation: None,
342 accessibility: None,
343 text_input: None,
344 preserve_focus: false,
345 }
346 }
347}
348
349use crate::id::{Id, StringId};
350
351#[derive(Debug, Clone, Copy, Default)]
352struct SharedElementConfig {
353 background_color: Color,
354 corner_radius: CornerRadius,
355 user_data: usize,
356}
357
358#[derive(Debug, Clone, Copy)]
359struct ElementConfig {
360 config_type: ElementConfigType,
361 config_index: usize,
362}
363
364#[derive(Debug, Clone, Copy, Default)]
365struct ElementConfigSlice {
366 start: usize,
367 length: i32,
368}
369
370#[derive(Debug, Clone, Copy, Default)]
371struct WrappedTextLine {
372 dimensions: Dimensions,
373 start: usize,
374 length: usize,
375}
376
377#[derive(Debug, Clone)]
378struct TextElementData {
379 text: String,
380 preferred_dimensions: Dimensions,
381 element_index: i32,
382 wrapped_lines_start: usize,
383 wrapped_lines_length: i32,
384}
385
386#[derive(Debug, Clone, Copy, Default)]
387struct LayoutElement {
388 children_start: usize,
390 children_length: u16,
391 text_data_index: i32, dimensions: Dimensions,
394 min_dimensions: Dimensions,
395 layout_config_index: usize,
396 element_configs: ElementConfigSlice,
397 id: u32,
398 floating_children_count: u16,
399}
400
401#[derive(Default)]
402struct LayoutElementHashMapItem {
403 bounding_box: BoundingBox,
404 element_id: Id,
405 layout_element_index: i32,
406 on_hover_fn: Option<Box<dyn FnMut(Id, PointerData)>>,
407 on_press_fn: Option<Box<dyn FnMut(Id, PointerData)>>,
408 on_release_fn: Option<Box<dyn FnMut(Id, PointerData)>>,
409 on_focus_fn: Option<Box<dyn FnMut(Id)>>,
410 on_unfocus_fn: Option<Box<dyn FnMut(Id)>>,
411 on_text_changed_fn: Option<Box<dyn FnMut(&str)>>,
412 on_text_submit_fn: Option<Box<dyn FnMut(&str)>>,
413 is_text_input: bool,
414 preserve_focus: bool,
415 generation: u32,
416 collision: bool,
417 collapsed: bool,
418}
419
420impl Clone for LayoutElementHashMapItem {
421 fn clone(&self) -> Self {
422 Self {
423 bounding_box: self.bounding_box,
424 element_id: self.element_id.clone(),
425 layout_element_index: self.layout_element_index,
426 on_hover_fn: None, on_press_fn: None,
428 on_release_fn: None,
429 on_focus_fn: None,
430 on_unfocus_fn: None,
431 on_text_changed_fn: None,
432 on_text_submit_fn: None,
433 is_text_input: self.is_text_input,
434 preserve_focus: self.preserve_focus,
435 generation: self.generation,
436 collision: self.collision,
437 collapsed: self.collapsed,
438 }
439 }
440}
441
442#[derive(Debug, Clone, Copy, Default)]
443struct MeasuredWord {
444 start_offset: i32,
445 length: i32,
446 width: f32,
447 next: i32,
448}
449
450#[derive(Debug, Clone, Copy, Default)]
451#[allow(dead_code)]
452struct MeasureTextCacheItem {
453 unwrapped_dimensions: Dimensions,
454 measured_words_start_index: i32,
455 min_width: f32,
456 contains_newlines: bool,
457 id: u32,
458 generation: u32,
459}
460
461#[derive(Debug, Clone, Copy, Default)]
462#[allow(dead_code)]
463struct ScrollContainerDataInternal {
464 bounding_box: BoundingBox,
465 content_size: Dimensions,
466 scroll_origin: Vector2,
467 pointer_origin: Vector2,
468 scroll_momentum: Vector2,
469 scroll_position: Vector2,
470 previous_delta: Vector2,
471 scrollbar: Option<ScrollbarConfig>,
472 scroll_x_enabled: bool,
473 scroll_y_enabled: bool,
474 no_drag_scroll: bool,
475 scrollbar_idle_frames: u32,
476 scrollbar_activity_this_frame: bool,
477 scrollbar_thumb_drag_active_x: bool,
478 scrollbar_thumb_drag_active_y: bool,
479 scrollbar_drag_origin: Vector2,
480 scrollbar_drag_scroll_origin: Vector2,
481 element_id: u32,
482 layout_element_index: i32,
483 open_this_frame: bool,
484 pointer_scroll_active: bool,
485}
486
487#[derive(Debug, Clone, Copy, Default)]
488struct LayoutElementTreeNode {
489 layout_element_index: i32,
490 position: Vector2,
491 next_child_offset: Vector2,
492}
493
494#[derive(Debug, Clone, Copy, Default)]
495struct LayoutElementTreeRoot {
496 layout_element_index: i32,
497 parent_id: u32,
498 clip_element_id: u32,
499 z_index: i16,
500 pointer_offset: Vector2,
501}
502
503#[derive(Debug, Clone, Copy)]
504struct FocusableEntry {
505 element_id: u32,
506 tab_index: Option<i32>,
507 insertion_order: u32,
508}
509
510#[derive(Debug, Clone, Copy, Default)]
511pub struct PointerData {
512 pub position: Vector2,
513 pub state: PointerDataInteractionState,
514}
515
516#[derive(Debug, Clone, Copy, Default)]
517#[allow(dead_code)]
518struct BooleanWarnings {
519 max_elements_exceeded: bool,
520 text_measurement_fn_not_set: bool,
521 max_text_measure_cache_exceeded: bool,
522 max_render_commands_exceeded: bool,
523}
524
525#[derive(Debug, Clone)]
526pub struct InternalRenderCommand<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
527 pub bounding_box: BoundingBox,
528 pub command_type: RenderCommandType,
529 pub render_data: InternalRenderData<CustomElementData>,
530 pub user_data: usize,
531 pub id: u32,
532 pub z_index: i16,
533 pub effects: Vec<ShaderConfig>,
534 pub visual_rotation: Option<VisualRotationConfig>,
535 pub shape_rotation: Option<ShapeRotationConfig>,
536}
537
538#[derive(Debug, Clone)]
539pub enum InternalRenderData<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
540 None,
541 Rectangle {
542 background_color: Color,
543 corner_radius: CornerRadius,
544 },
545 Text {
546 text: String,
547 text_color: Color,
548 font_size: u16,
549 letter_spacing: u16,
550 line_height: u16,
551 font_asset: Option<&'static crate::renderer::FontAsset>,
552 },
553 Image {
554 background_color: Color,
555 corner_radius: CornerRadius,
556 image_data: ImageSource,
557 },
558 Custom {
559 background_color: Color,
560 corner_radius: CornerRadius,
561 custom_data: CustomElementData,
562 },
563 Border {
564 color: Color,
565 corner_radius: CornerRadius,
566 width: BorderWidth,
567 position: BorderPosition,
568 },
569 Clip {
570 horizontal: bool,
571 vertical: bool,
572 },
573}
574
575impl<CustomElementData: Clone + Default + std::fmt::Debug> Default for InternalRenderData<CustomElementData> {
576 fn default() -> Self {
577 Self::None
578 }
579}
580
581impl<CustomElementData: Clone + Default + std::fmt::Debug> Default for InternalRenderCommand<CustomElementData> {
582 fn default() -> Self {
583 Self {
584 bounding_box: BoundingBox::default(),
585 command_type: RenderCommandType::None,
586 render_data: InternalRenderData::None,
587 user_data: 0,
588 id: 0,
589 z_index: 0,
590 effects: Vec::new(),
591 visual_rotation: None,
592 shape_rotation: None,
593 }
594 }
595}
596
597#[derive(Debug, Clone, Copy)]
598pub struct ScrollContainerData {
599 pub scroll_position: Vector2,
600 pub scroll_container_dimensions: Dimensions,
601 pub content_dimensions: Dimensions,
602 pub horizontal: bool,
603 pub vertical: bool,
604 pub found: bool,
605}
606
607impl Default for ScrollContainerData {
608 fn default() -> Self {
609 Self {
610 scroll_position: Vector2::default(),
611 scroll_container_dimensions: Dimensions::default(),
612 content_dimensions: Dimensions::default(),
613 horizontal: false,
614 vertical: false,
615 found: false,
616 }
617 }
618}
619
620pub struct PlyContext<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
621 pub max_element_count: i32,
623 pub max_measure_text_cache_word_count: i32,
624 pub debug_mode_enabled: bool,
625 debug_view_width: f32,
626 pub culling_disabled: bool,
627 pub external_scroll_handling_enabled: bool,
628 pub debug_selected_element_id: u32,
629 pub generation: u32,
630
631 boolean_warnings: BooleanWarnings,
633
634 pointer_info: PointerData,
636 pub layout_dimensions: Dimensions,
637
638 dynamic_element_index: u32,
640
641 measure_text_fn: Option<Box<dyn Fn(&str, &TextConfig) -> Dimensions>>,
643
644 layout_elements: Vec<LayoutElement>,
646 render_commands: Vec<InternalRenderCommand<CustomElementData>>,
647 open_layout_element_stack: Vec<i32>,
648 layout_element_children: Vec<i32>,
649 layout_element_children_buffer: Vec<i32>,
650 text_element_data: Vec<TextElementData>,
651 aspect_ratio_element_indexes: Vec<i32>,
652 reusable_element_index_buffer: Vec<i32>,
653 layout_element_clip_element_ids: Vec<i32>,
654
655 layout_configs: Vec<LayoutConfig>,
657 element_configs: Vec<ElementConfig>,
658 text_element_configs: Vec<TextConfig>,
659 aspect_ratio_configs: Vec<f32>,
660 aspect_ratio_cover_configs: Vec<bool>,
661 image_element_configs: Vec<ImageSource>,
662 floating_element_configs: Vec<FloatingConfig>,
663 clip_element_configs: Vec<ClipConfig>,
664 custom_element_configs: Vec<CustomElementData>,
665 border_element_configs: Vec<BorderConfig>,
666 shared_element_configs: Vec<SharedElementConfig>,
667
668 element_effects: Vec<Vec<ShaderConfig>>,
670 element_shaders: Vec<Vec<ShaderConfig>>,
672
673 element_visual_rotations: Vec<Option<VisualRotationConfig>>,
675
676 element_shape_rotations: Vec<Option<ShapeRotationConfig>>,
678 element_pre_rotation_dimensions: Vec<Option<Dimensions>>,
680
681 layout_element_id_strings: Vec<StringId>,
683
684 wrapped_text_lines: Vec<WrappedTextLine>,
686
687 tree_node_array: Vec<LayoutElementTreeNode>,
689 layout_element_tree_roots: Vec<LayoutElementTreeRoot>,
690
691 layout_element_map: FxHashMap<u32, LayoutElementHashMapItem>,
693
694 measure_text_cache: FxHashMap<u32, MeasureTextCacheItem>,
696 measured_words: Vec<MeasuredWord>,
697 measured_words_free_list: Vec<i32>,
698
699 open_clip_element_stack: Vec<i32>,
701 pointer_over_ids: Vec<Id>,
702 pressed_element_ids: Vec<Id>,
703 pressed_this_frame_ids: Vec<Id>,
704 pressed_this_frame_generation: u32,
705 released_this_frame_ids: Vec<Id>,
706 released_this_frame_generation: u32,
707 keyboard_press_this_frame_generation: u32,
708 scroll_container_datas: Vec<ScrollContainerDataInternal>,
709
710 pub focused_element_id: u32, pub(crate) focus_from_keyboard: bool,
714 focusable_elements: Vec<FocusableEntry>,
715 pub(crate) accessibility_configs: FxHashMap<u32, crate::accessibility::AccessibilityConfig>,
716 pub(crate) accessibility_element_order: Vec<u32>,
717
718 pub(crate) text_edit_states: FxHashMap<u32, crate::text_input::TextEditState>,
720 text_input_configs: Vec<crate::text_input::TextInputConfig>,
721 pub(crate) text_input_element_ids: Vec<u32>,
723 pub(crate) pending_text_click: Option<(u32, f32, f32, bool)>,
725 pub(crate) text_input_scrollbar_idle_frames: FxHashMap<u32, u32>,
727 pub(crate) text_input_drag_active: bool,
729 pub(crate) text_input_drag_origin: crate::math::Vector2,
730 pub(crate) text_input_drag_scroll_origin: crate::math::Vector2,
731 pub(crate) text_input_drag_element_id: u32,
732 pub(crate) text_input_drag_from_touch: bool,
733 pub(crate) text_input_scrollbar_drag_active: bool,
734 pub(crate) text_input_scrollbar_drag_vertical: bool,
735 pub(crate) text_input_scrollbar_drag_origin: f32,
736 pub(crate) text_input_scrollbar_drag_scroll_origin: f32,
737 pub(crate) current_time: f64,
739 pub(crate) frame_delta_time: f32,
741
742 tree_node_visited: Vec<bool>,
744
745 dynamic_string_data: Vec<u8>,
747
748 font_height_cache: FxHashMap<(&'static str, u16), f32>,
751
752 pub(crate) default_font_key: &'static str,
754
755 }
757
758fn hash_data_scalar(data: &[u8]) -> u64 {
759 let mut hash: u64 = 0;
760 for &b in data {
761 hash = hash.wrapping_add(b as u64);
762 hash = hash.wrapping_add(hash << 10);
763 hash ^= hash >> 6;
764 }
765 hash
766}
767
768pub fn hash_string(key: &str, seed: u32) -> Id {
769 let mut hash: u32 = seed;
770 for b in key.bytes() {
771 hash = hash.wrapping_add(b as u32);
772 hash = hash.wrapping_add(hash << 10);
773 hash ^= hash >> 6;
774 }
775 hash = hash.wrapping_add(hash << 3);
776 hash ^= hash >> 11;
777 hash = hash.wrapping_add(hash << 15);
778 Id {
779 id: hash.wrapping_add(1),
780 offset: 0,
781 base_id: hash.wrapping_add(1),
782 string_id: StringId::from_str(key),
783 }
784}
785
786pub fn hash_string_with_offset(key: &str, offset: u32, seed: u32) -> Id {
787 let mut base: u32 = seed;
788 for b in key.bytes() {
789 base = base.wrapping_add(b as u32);
790 base = base.wrapping_add(base << 10);
791 base ^= base >> 6;
792 }
793 let mut hash = base;
794 hash = hash.wrapping_add(offset);
795 hash = hash.wrapping_add(hash << 10);
796 hash ^= hash >> 6;
797
798 hash = hash.wrapping_add(hash << 3);
799 base = base.wrapping_add(base << 3);
800 hash ^= hash >> 11;
801 base ^= base >> 11;
802 hash = hash.wrapping_add(hash << 15);
803 base = base.wrapping_add(base << 15);
804 Id {
805 id: hash.wrapping_add(1),
806 offset,
807 base_id: base.wrapping_add(1),
808 string_id: StringId::from_str(key),
809 }
810}
811
812fn hash_number(offset: u32, seed: u32) -> Id {
813 let mut hash = seed;
814 hash = hash.wrapping_add(offset.wrapping_add(48));
815 hash = hash.wrapping_add(hash << 10);
816 hash ^= hash >> 6;
817 hash = hash.wrapping_add(hash << 3);
818 hash ^= hash >> 11;
819 hash = hash.wrapping_add(hash << 15);
820 Id {
821 id: hash.wrapping_add(1),
822 offset,
823 base_id: seed,
824 string_id: StringId::empty(),
825 }
826}
827
828fn hash_string_contents_with_config(
829 text: &str,
830 config: &TextConfig,
831) -> u32 {
832 let mut hash: u32 = (hash_data_scalar(text.as_bytes()) % u32::MAX as u64) as u32;
833 for &b in config.font_asset.map(|a| a.key()).unwrap_or("").as_bytes() {
835 hash = hash.wrapping_add(b as u32);
836 hash = hash.wrapping_add(hash << 10);
837 hash ^= hash >> 6;
838 }
839 hash = hash.wrapping_add(config.font_size as u32);
840 hash = hash.wrapping_add(hash << 10);
841 hash ^= hash >> 6;
842 hash = hash.wrapping_add(config.letter_spacing as u32);
843 hash = hash.wrapping_add(hash << 10);
844 hash ^= hash >> 6;
845 hash = hash.wrapping_add(hash << 3);
846 hash ^= hash >> 11;
847 hash = hash.wrapping_add(hash << 15);
848 hash.wrapping_add(1)
849}
850
851fn float_equal(left: f32, right: f32) -> bool {
852 let diff = left - right;
853 diff < EPSILON && diff > -EPSILON
854}
855
856fn point_is_inside_rect(point: Vector2, rect: BoundingBox) -> bool {
857 point.x >= rect.x
858 && point.x <= rect.x + rect.width
859 && point.y >= rect.y
860 && point.y <= rect.y + rect.height
861}
862
863#[derive(Debug, Clone, Copy)]
864struct ScrollbarAxisGeometry {
865 track_bbox: BoundingBox,
866 thumb_bbox: BoundingBox,
867 max_scroll: f32,
868 thumb_travel: f32,
869}
870
871#[derive(Debug, Clone, Copy, Default)]
872struct WrappedLayoutLine {
873 start_child_offset: usize,
874 end_child_offset: usize,
875 main_size: f32,
876 cross_size: f32,
877}
878
879fn scrollbar_visibility_alpha(config: ScrollbarConfig, idle_frames: u32) -> f32 {
880 match config.hide_after_frames {
881 None => 1.0,
882 Some(hide) => {
883 if hide == 0 {
884 return 0.0;
885 }
886 if idle_frames <= hide {
887 return 1.0;
888 }
889 let fade_frames = ((hide as f32) * 0.25).ceil().max(1.0) as u32;
890 let fade_progress = (idle_frames - hide) as f32 / fade_frames as f32;
891 (1.0 - fade_progress).clamp(0.0, 1.0)
892 }
893 }
894}
895
896fn apply_alpha(color: Color, alpha_mul: f32) -> Color {
897 Color::rgba(
898 color.r,
899 color.g,
900 color.b,
901 (color.a * alpha_mul).clamp(0.0, 255.0),
902 )
903}
904
905fn compute_vertical_scrollbar_geometry(
906 bbox: BoundingBox,
907 content_height: f32,
908 scroll_position_positive: f32,
909 config: ScrollbarConfig,
910) -> Option<ScrollbarAxisGeometry> {
911 let viewport = bbox.height;
912 let max_scroll = (content_height - viewport).max(0.0);
913 if viewport <= 0.0 || max_scroll <= 0.0 {
914 return None;
915 }
916
917 let thickness = config.width.max(1.0);
918 let track_len = viewport;
919 let thumb_len = (track_len * (viewport / content_height.max(viewport)))
920 .max(config.min_thumb_size.max(1.0))
921 .min(track_len);
922 let thumb_travel = (track_len - thumb_len).max(0.0);
923 let thumb_offset = if thumb_travel <= 0.0 {
924 0.0
925 } else {
926 (scroll_position_positive.clamp(0.0, max_scroll) / max_scroll) * thumb_travel
927 };
928
929 Some(ScrollbarAxisGeometry {
930 track_bbox: BoundingBox::new(
931 bbox.x + bbox.width - thickness,
932 bbox.y,
933 thickness,
934 track_len,
935 ),
936 thumb_bbox: BoundingBox::new(
937 bbox.x + bbox.width - thickness,
938 bbox.y + thumb_offset,
939 thickness,
940 thumb_len,
941 ),
942 max_scroll,
943 thumb_travel,
944 })
945}
946
947fn compute_horizontal_scrollbar_geometry(
948 bbox: BoundingBox,
949 content_width: f32,
950 scroll_position_positive: f32,
951 config: ScrollbarConfig,
952) -> Option<ScrollbarAxisGeometry> {
953 let viewport = bbox.width;
954 let max_scroll = (content_width - viewport).max(0.0);
955 if viewport <= 0.0 || max_scroll <= 0.0 {
956 return None;
957 }
958
959 let thickness = config.width.max(1.0);
960 let track_len = viewport;
961 let thumb_len = (track_len * (viewport / content_width.max(viewport)))
962 .max(config.min_thumb_size.max(1.0))
963 .min(track_len);
964 let thumb_travel = (track_len - thumb_len).max(0.0);
965 let thumb_offset = if thumb_travel <= 0.0 {
966 0.0
967 } else {
968 (scroll_position_positive.clamp(0.0, max_scroll) / max_scroll) * thumb_travel
969 };
970
971 Some(ScrollbarAxisGeometry {
972 track_bbox: BoundingBox::new(
973 bbox.x,
974 bbox.y + bbox.height - thickness,
975 track_len,
976 thickness,
977 ),
978 thumb_bbox: BoundingBox::new(
979 bbox.x + thumb_offset,
980 bbox.y + bbox.height - thickness,
981 thumb_len,
982 thickness,
983 ),
984 max_scroll,
985 thumb_travel,
986 })
987}
988
989impl<CustomElementData: Clone + Default + std::fmt::Debug> PlyContext<CustomElementData> {
990 pub fn new(dimensions: Dimensions) -> Self {
991 let max_element_count = DEFAULT_MAX_ELEMENT_COUNT;
992 let max_measure_text_cache_word_count = DEFAULT_MAX_MEASURE_TEXT_WORD_CACHE_COUNT;
993
994 let ctx = Self {
995 max_element_count,
996 max_measure_text_cache_word_count,
997 debug_mode_enabled: false,
998 debug_view_width: Self::DEBUG_VIEW_DEFAULT_WIDTH,
999 culling_disabled: false,
1000 external_scroll_handling_enabled: false,
1001 debug_selected_element_id: 0,
1002 generation: 0,
1003 boolean_warnings: BooleanWarnings::default(),
1004 pointer_info: PointerData::default(),
1005 layout_dimensions: dimensions,
1006 dynamic_element_index: 0,
1007 measure_text_fn: None,
1008 layout_elements: Vec::new(),
1009 render_commands: Vec::new(),
1010 open_layout_element_stack: Vec::new(),
1011 layout_element_children: Vec::new(),
1012 layout_element_children_buffer: Vec::new(),
1013 text_element_data: Vec::new(),
1014 aspect_ratio_element_indexes: Vec::new(),
1015 reusable_element_index_buffer: Vec::new(),
1016 layout_element_clip_element_ids: Vec::new(),
1017 layout_configs: Vec::new(),
1018 element_configs: Vec::new(),
1019 text_element_configs: Vec::new(),
1020 aspect_ratio_configs: Vec::new(),
1021 aspect_ratio_cover_configs: Vec::new(),
1022 image_element_configs: Vec::new(),
1023 floating_element_configs: Vec::new(),
1024 clip_element_configs: Vec::new(),
1025 custom_element_configs: Vec::new(),
1026 border_element_configs: Vec::new(),
1027 shared_element_configs: Vec::new(),
1028 element_effects: Vec::new(),
1029 element_shaders: Vec::new(),
1030 element_visual_rotations: Vec::new(),
1031 element_shape_rotations: Vec::new(),
1032 element_pre_rotation_dimensions: Vec::new(),
1033 layout_element_id_strings: Vec::new(),
1034 wrapped_text_lines: Vec::new(),
1035 tree_node_array: Vec::new(),
1036 layout_element_tree_roots: Vec::new(),
1037 layout_element_map: FxHashMap::default(),
1038 measure_text_cache: FxHashMap::default(),
1039 measured_words: Vec::new(),
1040 measured_words_free_list: Vec::new(),
1041 open_clip_element_stack: Vec::new(),
1042 pointer_over_ids: Vec::new(),
1043 pressed_element_ids: Vec::new(),
1044 pressed_this_frame_ids: Vec::new(),
1045 pressed_this_frame_generation: 0,
1046 released_this_frame_ids: Vec::new(),
1047 released_this_frame_generation: 0,
1048 keyboard_press_this_frame_generation: 0,
1049 scroll_container_datas: Vec::new(),
1050 focused_element_id: 0,
1051 focus_from_keyboard: false,
1052 focusable_elements: Vec::new(),
1053 accessibility_configs: FxHashMap::default(),
1054 accessibility_element_order: Vec::new(),
1055 text_edit_states: FxHashMap::default(),
1056 text_input_configs: Vec::new(),
1057 text_input_element_ids: Vec::new(),
1058 pending_text_click: None,
1059 text_input_scrollbar_idle_frames: FxHashMap::default(),
1060 text_input_drag_active: false,
1061 text_input_drag_origin: Vector2::default(),
1062 text_input_drag_scroll_origin: Vector2::default(),
1063 text_input_drag_element_id: 0,
1064 text_input_drag_from_touch: false,
1065 text_input_scrollbar_drag_active: false,
1066 text_input_scrollbar_drag_vertical: false,
1067 text_input_scrollbar_drag_origin: 0.0,
1068 text_input_scrollbar_drag_scroll_origin: 0.0,
1069 current_time: 0.0,
1070 frame_delta_time: 0.0,
1071 tree_node_visited: Vec::new(),
1072 dynamic_string_data: Vec::new(),
1073 font_height_cache: FxHashMap::default(),
1074 default_font_key: "",
1075 };
1076 ctx
1077 }
1078
1079 fn get_open_layout_element(&self) -> usize {
1080 let idx = *self.open_layout_element_stack.last().unwrap();
1081 idx as usize
1082 }
1083
1084 pub fn get_open_element_id(&self) -> u32 {
1086 let open_idx = self.get_open_layout_element();
1087 self.layout_elements[open_idx].id
1088 }
1089
1090 pub fn get_parent_element_id(&self) -> u32 {
1091 let stack_len = self.open_layout_element_stack.len();
1092 let parent_idx = self.open_layout_element_stack[stack_len - 2] as usize;
1093 self.layout_elements[parent_idx].id
1094 }
1095
1096 fn add_hash_map_item(
1097 &mut self,
1098 element_id: &Id,
1099 layout_element_index: i32,
1100 ) {
1101 let gen = self.generation;
1102 match self.layout_element_map.entry(element_id.id) {
1103 std::collections::hash_map::Entry::Occupied(mut entry) => {
1104 let item = entry.get_mut();
1105 if item.generation <= gen {
1106 item.element_id = element_id.clone();
1107 item.generation = gen + 1;
1108 item.layout_element_index = layout_element_index;
1109 item.collision = false;
1110 item.on_hover_fn = None;
1111 item.on_press_fn = None;
1112 item.on_release_fn = None;
1113 item.on_focus_fn = None;
1114 item.on_unfocus_fn = None;
1115 item.on_text_changed_fn = None;
1116 item.on_text_submit_fn = None;
1117 item.is_text_input = false;
1118 item.preserve_focus = false;
1119 } else {
1120 item.collision = true;
1122 }
1123 }
1124 std::collections::hash_map::Entry::Vacant(entry) => {
1125 entry.insert(LayoutElementHashMapItem {
1126 element_id: element_id.clone(),
1127 layout_element_index,
1128 generation: gen + 1,
1129 bounding_box: BoundingBox::default(),
1130 on_hover_fn: None,
1131 on_press_fn: None,
1132 on_release_fn: None,
1133 on_focus_fn: None,
1134 on_unfocus_fn: None,
1135 on_text_changed_fn: None,
1136 on_text_submit_fn: None,
1137 is_text_input: false,
1138 preserve_focus: false,
1139 collision: false,
1140 collapsed: false,
1141 });
1142 }
1143 }
1144 }
1145
1146 fn generate_id_for_anonymous_element(&mut self, open_element_index: usize) -> Id {
1147 let stack_len = self.open_layout_element_stack.len();
1148 let parent_idx = self.open_layout_element_stack[stack_len - 2] as usize;
1149 let parent = &self.layout_elements[parent_idx];
1150 let offset =
1151 parent.children_length as u32 + parent.floating_children_count as u32;
1152 let parent_id = parent.id;
1153 let element_id = hash_number(offset, parent_id);
1154 self.layout_elements[open_element_index].id = element_id.id;
1155 self.add_hash_map_item(&element_id, open_element_index as i32);
1156 if self.debug_mode_enabled {
1157 self.layout_element_id_strings.push(element_id.string_id.clone());
1158 }
1159 element_id
1160 }
1161
1162 fn element_has_config(
1163 &self,
1164 element_index: usize,
1165 config_type: ElementConfigType,
1166 ) -> bool {
1167 let element = &self.layout_elements[element_index];
1168 let start = element.element_configs.start;
1169 let length = element.element_configs.length;
1170 for i in 0..length {
1171 let config = &self.element_configs[start + i as usize];
1172 if config.config_type == config_type {
1173 return true;
1174 }
1175 }
1176 false
1177 }
1178
1179 fn find_element_config_index(
1180 &self,
1181 element_index: usize,
1182 config_type: ElementConfigType,
1183 ) -> Option<usize> {
1184 let element = &self.layout_elements[element_index];
1185 let start = element.element_configs.start;
1186 let length = element.element_configs.length;
1187 for i in 0..length {
1188 let config = &self.element_configs[start + i as usize];
1189 if config.config_type == config_type {
1190 return Some(config.config_index);
1191 }
1192 }
1193 None
1194 }
1195
1196 fn update_aspect_ratio_box(&mut self, element_index: usize) {
1197 if let Some(config_idx) =
1198 self.find_element_config_index(element_index, ElementConfigType::Aspect)
1199 {
1200 let aspect_ratio = self.aspect_ratio_configs[config_idx];
1201 if aspect_ratio == 0.0 {
1202 return;
1203 }
1204 let elem = &mut self.layout_elements[element_index];
1205 if elem.dimensions.width == 0.0 && elem.dimensions.height != 0.0 {
1206 elem.dimensions.width = elem.dimensions.height * aspect_ratio;
1207 } else if elem.dimensions.width != 0.0 && elem.dimensions.height == 0.0 {
1208 elem.dimensions.height = elem.dimensions.width * (1.0 / aspect_ratio);
1209 }
1210 }
1211 }
1212
1213 pub fn store_text_element_config(
1214 &mut self,
1215 config: TextConfig,
1216 ) -> usize {
1217 self.text_element_configs.push(config);
1218 self.text_element_configs.len() - 1
1219 }
1220
1221 fn store_layout_config(&mut self, config: LayoutConfig) -> usize {
1222 self.layout_configs.push(config);
1223 self.layout_configs.len() - 1
1224 }
1225
1226 fn store_shared_config(&mut self, config: SharedElementConfig) -> usize {
1227 self.shared_element_configs.push(config);
1228 self.shared_element_configs.len() - 1
1229 }
1230
1231 fn attach_element_config(&mut self, config_type: ElementConfigType, config_index: usize) {
1232 if self.boolean_warnings.max_elements_exceeded {
1233 return;
1234 }
1235 let open_idx = self.get_open_layout_element();
1236 self.layout_elements[open_idx].element_configs.length += 1;
1237 self.element_configs.push(ElementConfig {
1238 config_type,
1239 config_index,
1240 });
1241 }
1242
1243 pub fn open_element(&mut self) {
1244 if self.boolean_warnings.max_elements_exceeded {
1245 return;
1246 }
1247 let elem = LayoutElement {
1248 text_data_index: -1,
1249 ..Default::default()
1250 };
1251 self.layout_elements.push(elem);
1252 let idx = (self.layout_elements.len() - 1) as i32;
1253 self.open_layout_element_stack.push(idx);
1254
1255 while self.layout_element_clip_element_ids.len() < self.layout_elements.len() {
1257 self.layout_element_clip_element_ids.push(0);
1258 }
1259
1260 self.generate_id_for_anonymous_element(idx as usize);
1261
1262 if !self.open_clip_element_stack.is_empty() {
1263 let clip_id = *self.open_clip_element_stack.last().unwrap();
1264 self.layout_element_clip_element_ids[idx as usize] = clip_id;
1265 } else {
1266 self.layout_element_clip_element_ids[idx as usize] = 0;
1267 }
1268 }
1269
1270 pub fn open_element_with_id(&mut self, element_id: &Id) {
1271 if self.boolean_warnings.max_elements_exceeded {
1272 return;
1273 }
1274 let mut elem = LayoutElement {
1275 text_data_index: -1,
1276 ..Default::default()
1277 };
1278 elem.id = element_id.id;
1279 self.layout_elements.push(elem);
1280 let idx = (self.layout_elements.len() - 1) as i32;
1281 self.open_layout_element_stack.push(idx);
1282
1283 while self.layout_element_clip_element_ids.len() < self.layout_elements.len() {
1284 self.layout_element_clip_element_ids.push(0);
1285 }
1286
1287 self.add_hash_map_item(element_id, idx);
1288 if self.debug_mode_enabled {
1289 self.layout_element_id_strings.push(element_id.string_id.clone());
1290 }
1291
1292 if !self.open_clip_element_stack.is_empty() {
1293 let clip_id = *self.open_clip_element_stack.last().unwrap();
1294 self.layout_element_clip_element_ids[idx as usize] = clip_id;
1295 } else {
1296 self.layout_element_clip_element_ids[idx as usize] = 0;
1297 }
1298 }
1299
1300 pub fn configure_open_element(&mut self, declaration: &ElementDeclaration<CustomElementData>) {
1301 if self.boolean_warnings.max_elements_exceeded {
1302 return;
1303 }
1304 let open_idx = self.get_open_layout_element();
1305 let layout_config_index = self.store_layout_config(declaration.layout);
1306 self.layout_elements[open_idx].layout_config_index = layout_config_index;
1307
1308 self.layout_elements[open_idx].element_configs.start = self.element_configs.len();
1310
1311 let mut shared_config_index: Option<usize> = None;
1313 if declaration.background_color.a > 0.0 {
1314 let idx = self.store_shared_config(SharedElementConfig {
1315 background_color: declaration.background_color,
1316 corner_radius: CornerRadius::default(),
1317 user_data: 0,
1318 });
1319 shared_config_index = Some(idx);
1320 self.attach_element_config(ElementConfigType::Shared, idx);
1321 }
1322 if !declaration.corner_radius.is_zero() {
1323 if let Some(idx) = shared_config_index {
1324 self.shared_element_configs[idx].corner_radius = declaration.corner_radius;
1325 } else {
1326 let idx = self.store_shared_config(SharedElementConfig {
1327 background_color: Color::rgba(0.0, 0.0, 0.0, 0.0),
1328 corner_radius: declaration.corner_radius,
1329 user_data: 0,
1330 });
1331 shared_config_index = Some(idx);
1332 self.attach_element_config(ElementConfigType::Shared, idx);
1333 }
1334 }
1335 if declaration.user_data != 0 {
1336 if let Some(idx) = shared_config_index {
1337 self.shared_element_configs[idx].user_data = declaration.user_data;
1338 } else {
1339 let idx = self.store_shared_config(SharedElementConfig {
1340 background_color: Color::rgba(0.0, 0.0, 0.0, 0.0),
1341 corner_radius: CornerRadius::default(),
1342 user_data: declaration.user_data,
1343 });
1344 self.attach_element_config(ElementConfigType::Shared, idx);
1345 }
1346 }
1347
1348 if let Some(image_data) = declaration.image_data.clone() {
1350 self.image_element_configs.push(image_data);
1351 let idx = self.image_element_configs.len() - 1;
1352 self.attach_element_config(ElementConfigType::Image, idx);
1353 }
1354
1355 if declaration.aspect_ratio > 0.0 {
1357 self.aspect_ratio_configs.push(declaration.aspect_ratio);
1358 self.aspect_ratio_cover_configs
1359 .push(declaration.cover_aspect_ratio);
1360 let idx = self.aspect_ratio_configs.len() - 1;
1361 self.attach_element_config(ElementConfigType::Aspect, idx);
1362 self.aspect_ratio_element_indexes
1363 .push((self.layout_elements.len() - 1) as i32);
1364 }
1365
1366 if declaration.floating.attach_to != FloatingAttachToElement::None {
1368 let mut floating_config = declaration.floating;
1369 let stack_len = self.open_layout_element_stack.len();
1370
1371 if stack_len >= 2 {
1372 let hierarchical_parent_idx =
1373 self.open_layout_element_stack[stack_len - 2] as usize;
1374 let hierarchical_parent_id = self.layout_elements[hierarchical_parent_idx].id;
1375
1376 let mut clip_element_id: u32 = 0;
1377
1378 if declaration.floating.attach_to == FloatingAttachToElement::Parent {
1379 floating_config.parent_id = hierarchical_parent_id;
1380 if !self.open_clip_element_stack.is_empty() {
1381 clip_element_id =
1382 *self.open_clip_element_stack.last().unwrap() as u32;
1383 }
1384 } else if declaration.floating.attach_to
1385 == FloatingAttachToElement::ElementWithId
1386 {
1387 if let Some(parent_item) =
1388 self.layout_element_map.get(&floating_config.parent_id)
1389 {
1390 let parent_elem_idx = parent_item.layout_element_index as usize;
1391 clip_element_id =
1392 self.layout_element_clip_element_ids[parent_elem_idx] as u32;
1393 }
1394 } else if declaration.floating.attach_to
1395 == FloatingAttachToElement::Root
1396 {
1397 floating_config.parent_id =
1398 hash_string("Ply__RootContainer", 0).id;
1399 }
1400
1401 if declaration.floating.clip_to == FloatingClipToElement::None {
1402 clip_element_id = 0;
1403 }
1404
1405 let current_element_index =
1406 *self.open_layout_element_stack.last().unwrap();
1407 self.layout_element_clip_element_ids[current_element_index as usize] =
1408 clip_element_id as i32;
1409 self.open_clip_element_stack.push(clip_element_id as i32);
1410
1411 self.layout_element_tree_roots
1412 .push(LayoutElementTreeRoot {
1413 layout_element_index: current_element_index,
1414 parent_id: floating_config.parent_id,
1415 clip_element_id,
1416 z_index: floating_config.z_index,
1417 pointer_offset: Vector2::default(),
1418 });
1419
1420 self.floating_element_configs.push(floating_config);
1421 let idx = self.floating_element_configs.len() - 1;
1422 self.attach_element_config(ElementConfigType::Floating, idx);
1423 }
1424 }
1425
1426 if let Some(ref custom_data) = declaration.custom_data {
1428 self.custom_element_configs.push(custom_data.clone());
1429 let idx = self.custom_element_configs.len() - 1;
1430 self.attach_element_config(ElementConfigType::Custom, idx);
1431 }
1432
1433 if declaration.clip.horizontal || declaration.clip.vertical {
1435 let mut clip = declaration.clip;
1436
1437 let elem_id = self.layout_elements[open_idx].id;
1438
1439 if clip.scroll_x || clip.scroll_y {
1441 for scd in &self.scroll_container_datas {
1442 if scd.element_id == elem_id {
1443 clip.child_offset = scd.scroll_position;
1444 break;
1445 }
1446 }
1447 }
1448
1449 self.clip_element_configs.push(clip);
1450 let idx = self.clip_element_configs.len() - 1;
1451 self.attach_element_config(ElementConfigType::Clip, idx);
1452
1453 self.open_clip_element_stack.push(elem_id as i32);
1454
1455 if clip.scroll_x || clip.scroll_y {
1457 let mut found_existing = false;
1458 for scd in &mut self.scroll_container_datas {
1459 if elem_id == scd.element_id {
1460 scd.layout_element_index = open_idx as i32;
1461 scd.open_this_frame = true;
1462 scd.scrollbar = clip.scrollbar;
1463 scd.scroll_x_enabled = clip.scroll_x;
1464 scd.scroll_y_enabled = clip.scroll_y;
1465 scd.no_drag_scroll = clip.no_drag_scroll;
1466 found_existing = true;
1467 break;
1468 }
1469 }
1470 if !found_existing {
1471 self.scroll_container_datas.push(ScrollContainerDataInternal {
1472 layout_element_index: open_idx as i32,
1473 scroll_origin: Vector2::new(-1.0, -1.0),
1474 scrollbar: clip.scrollbar,
1475 scroll_x_enabled: clip.scroll_x,
1476 scroll_y_enabled: clip.scroll_y,
1477 no_drag_scroll: clip.no_drag_scroll,
1478 element_id: elem_id,
1479 open_this_frame: true,
1480 ..Default::default()
1481 });
1482 }
1483 }
1484 }
1485
1486 if !declaration.border.width.is_zero() {
1488 self.border_element_configs.push(declaration.border);
1489 let idx = self.border_element_configs.len() - 1;
1490 self.attach_element_config(ElementConfigType::Border, idx);
1491 }
1492
1493 while self.element_effects.len() <= open_idx {
1496 self.element_effects.push(Vec::new());
1497 }
1498 self.element_effects[open_idx] = declaration.effects.clone();
1499
1500 while self.element_shaders.len() <= open_idx {
1502 self.element_shaders.push(Vec::new());
1503 }
1504 self.element_shaders[open_idx] = declaration.shaders.clone();
1505
1506 while self.element_visual_rotations.len() <= open_idx {
1508 self.element_visual_rotations.push(None);
1509 }
1510 self.element_visual_rotations[open_idx] = declaration.visual_rotation;
1511
1512 while self.element_shape_rotations.len() <= open_idx {
1514 self.element_shape_rotations.push(None);
1515 }
1516 self.element_shape_rotations[open_idx] = declaration.shape_rotation;
1517
1518 if let Some(ref a11y) = declaration.accessibility {
1520 let elem_id = self.layout_elements[open_idx].id;
1521 if a11y.focusable {
1522 self.focusable_elements.push(FocusableEntry {
1523 element_id: elem_id,
1524 tab_index: a11y.tab_index,
1525 insertion_order: self.focusable_elements.len() as u32,
1526 });
1527 }
1528 self.accessibility_configs.insert(elem_id, a11y.clone());
1529 self.accessibility_element_order.push(elem_id);
1530 }
1531
1532 if let Some(ref ti_config) = declaration.text_input {
1534 let elem_id = self.layout_elements[open_idx].id;
1535 self.text_input_configs.push(ti_config.clone());
1536 let idx = self.text_input_configs.len() - 1;
1537 self.attach_element_config(ElementConfigType::TextInput, idx);
1538 self.text_input_element_ids.push(elem_id);
1539
1540 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
1542 item.is_text_input = true;
1543 }
1544
1545 self.text_edit_states.entry(elem_id)
1547 .or_insert_with(crate::text_input::TextEditState::default);
1548
1549 self.text_input_scrollbar_idle_frames
1550 .entry(elem_id)
1551 .or_insert(0);
1552
1553 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1555 state.no_styles_movement = ti_config.no_styles_movement;
1556 }
1557
1558 if let Some((click_elem, click_x, click_y, click_shift)) = self.pending_text_click.take() {
1560 if click_elem == elem_id {
1561 if let Some(ref measure_fn) = self.measure_text_fn {
1562 let state = self.text_edit_states.get(&elem_id).cloned()
1563 .unwrap_or_default();
1564 let disp_text = crate::text_input::display_text(
1565 &state.text,
1566 &ti_config.placeholder,
1567 ti_config.is_password,
1568 );
1569 if !state.text.is_empty() {
1571 let is_double_click = state.last_click_element == elem_id
1573 && (self.current_time - state.last_click_time) < 0.4;
1574
1575 if ti_config.is_multiline {
1576 let elem_width = self.layout_element_map.get(&elem_id)
1578 .map(|item| item.bounding_box.width)
1579 .unwrap_or(200.0);
1580 let visual_lines = crate::text_input::wrap_lines(
1581 &disp_text,
1582 elem_width,
1583 ti_config.font_asset,
1584 ti_config.font_size,
1585 measure_fn.as_ref(),
1586 );
1587 let font_height = if ti_config.line_height > 0 {
1588 ti_config.line_height as f32
1589 } else {
1590 let config = crate::text::TextConfig {
1591 font_asset: ti_config.font_asset,
1592 font_size: ti_config.font_size,
1593 ..Default::default()
1594 };
1595 measure_fn(&"Mg", &config).height
1596 };
1597 let adjusted_y = click_y + state.scroll_offset_y;
1598 let clicked_line = (adjusted_y / font_height).floor().max(0.0) as usize;
1599 let clicked_line = clicked_line.min(visual_lines.len().saturating_sub(1));
1600
1601 let vl = &visual_lines[clicked_line];
1602 let line_char_x_positions = crate::text_input::compute_char_x_positions(
1603 &vl.text,
1604 ti_config.font_asset,
1605 ti_config.font_size,
1606 measure_fn.as_ref(),
1607 );
1608 let col = crate::text_input::find_nearest_char_boundary(
1609 click_x, &line_char_x_positions,
1610 );
1611 let global_pos = vl.global_char_start + col;
1612
1613 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1614 #[cfg(feature = "text-styling")]
1615 {
1616 let visual_pos = crate::text_input::styling::raw_to_cursor(&state.text, global_pos);
1617 if is_double_click {
1618 state.select_word_at_styled(visual_pos);
1619 } else {
1620 state.click_to_cursor_styled(visual_pos, click_shift);
1621 }
1622 }
1623 #[cfg(not(feature = "text-styling"))]
1624 {
1625 if is_double_click {
1626 state.select_word_at(global_pos);
1627 } else {
1628 if click_shift {
1629 if state.selection_anchor.is_none() {
1630 state.selection_anchor = Some(state.cursor_pos);
1631 }
1632 } else {
1633 state.selection_anchor = None;
1634 }
1635 state.cursor_pos = global_pos;
1636 state.reset_blink();
1637 }
1638 }
1639 state.last_click_time = self.current_time;
1640 state.last_click_element = elem_id;
1641 }
1642 } else {
1643 let char_x_positions = crate::text_input::compute_char_x_positions(
1645 &disp_text,
1646 ti_config.font_asset,
1647 ti_config.font_size,
1648 measure_fn.as_ref(),
1649 );
1650 let adjusted_x = click_x + state.scroll_offset;
1651
1652 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1653 let raw_click_pos = crate::text_input::find_nearest_char_boundary(
1654 adjusted_x, &char_x_positions,
1655 );
1656 #[cfg(feature = "text-styling")]
1657 {
1658 let visual_pos = crate::text_input::styling::raw_to_cursor(&state.text, raw_click_pos);
1659 if is_double_click {
1660 state.select_word_at_styled(visual_pos);
1661 } else {
1662 state.click_to_cursor_styled(visual_pos, click_shift);
1663 }
1664 }
1665 #[cfg(not(feature = "text-styling"))]
1666 {
1667 if is_double_click {
1668 state.select_word_at(raw_click_pos);
1669 } else {
1670 state.click_to_cursor(adjusted_x, &char_x_positions, click_shift);
1671 }
1672 }
1673 state.last_click_time = self.current_time;
1674 state.last_click_element = elem_id;
1675 }
1676 }
1677 } else if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1678 state.cursor_pos = 0;
1679 state.selection_anchor = None;
1680 state.last_click_time = self.current_time;
1681 state.last_click_element = elem_id;
1682 state.reset_blink();
1683 }
1684 }
1685 } else {
1686 self.pending_text_click = Some((click_elem, click_x, click_y, click_shift));
1688 }
1689 }
1690
1691 if declaration.accessibility.is_none() || !declaration.accessibility.as_ref().unwrap().focusable {
1693 let already = self.focusable_elements.iter().any(|e| e.element_id == elem_id);
1695 if !already {
1696 self.focusable_elements.push(FocusableEntry {
1697 element_id: elem_id,
1698 tab_index: None,
1699 insertion_order: self.focusable_elements.len() as u32,
1700 });
1701 }
1702 }
1703 }
1704
1705 if declaration.preserve_focus {
1707 let elem_id = self.layout_elements[open_idx].id;
1708 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
1709 item.preserve_focus = true;
1710 }
1711 }
1712 }
1713
1714 pub fn close_element(&mut self) {
1715 if self.boolean_warnings.max_elements_exceeded {
1716 return;
1717 }
1718
1719 let open_idx = self.get_open_layout_element();
1720 let layout_config_index = self.layout_elements[open_idx].layout_config_index;
1721 let layout_config = self.layout_configs[layout_config_index];
1722
1723 let mut element_has_clip_horizontal = false;
1725 let mut element_has_clip_vertical = false;
1726 let element_configs_start = self.layout_elements[open_idx].element_configs.start;
1727 let element_configs_length = self.layout_elements[open_idx].element_configs.length;
1728
1729 for i in 0..element_configs_length {
1730 let config = &self.element_configs[element_configs_start + i as usize];
1731 if config.config_type == ElementConfigType::Clip {
1732 let clip = &self.clip_element_configs[config.config_index];
1733 element_has_clip_horizontal = clip.horizontal;
1734 element_has_clip_vertical = clip.vertical;
1735 self.open_clip_element_stack.pop();
1736 break;
1737 } else if config.config_type == ElementConfigType::Floating {
1738 self.open_clip_element_stack.pop();
1739 }
1740 }
1741
1742 let left_right_padding =
1743 (layout_config.padding.left + layout_config.padding.right) as f32;
1744 let top_bottom_padding =
1745 (layout_config.padding.top + layout_config.padding.bottom) as f32;
1746
1747 let children_length = self.layout_elements[open_idx].children_length;
1748
1749 let children_start = self.layout_element_children.len();
1751 self.layout_elements[open_idx].children_start = children_start;
1752
1753 if layout_config.layout_direction == LayoutDirection::LeftToRight {
1754 self.layout_elements[open_idx].dimensions.width = left_right_padding;
1755 self.layout_elements[open_idx].min_dimensions.width = left_right_padding;
1756
1757 for i in 0..children_length {
1758 let buf_idx = self.layout_element_children_buffer.len()
1759 - children_length as usize
1760 + i as usize;
1761 let child_index = self.layout_element_children_buffer[buf_idx];
1762 let child = &self.layout_elements[child_index as usize];
1763 let child_width = child.dimensions.width;
1764 let child_height = child.dimensions.height;
1765 let child_min_width = child.min_dimensions.width;
1766 let child_min_height = child.min_dimensions.height;
1767
1768 self.layout_elements[open_idx].dimensions.width += child_width;
1769 let current_height = self.layout_elements[open_idx].dimensions.height;
1770 self.layout_elements[open_idx].dimensions.height =
1771 f32::max(current_height, child_height + top_bottom_padding);
1772
1773 if !element_has_clip_horizontal {
1774 self.layout_elements[open_idx].min_dimensions.width += child_min_width;
1775 }
1776 if !element_has_clip_vertical {
1777 let current_min_h = self.layout_elements[open_idx].min_dimensions.height;
1778 self.layout_elements[open_idx].min_dimensions.height =
1779 f32::max(current_min_h, child_min_height + top_bottom_padding);
1780 }
1781 self.layout_element_children.push(child_index);
1782 }
1783 let child_gap =
1784 (children_length.saturating_sub(1) as u32 * layout_config.child_gap as u32) as f32;
1785 self.layout_elements[open_idx].dimensions.width += child_gap;
1786 if !element_has_clip_horizontal {
1787 self.layout_elements[open_idx].min_dimensions.width += child_gap;
1788 }
1789 } else {
1790 self.layout_elements[open_idx].dimensions.height = top_bottom_padding;
1792 self.layout_elements[open_idx].min_dimensions.height = top_bottom_padding;
1793
1794 for i in 0..children_length {
1795 let buf_idx = self.layout_element_children_buffer.len()
1796 - children_length as usize
1797 + i as usize;
1798 let child_index = self.layout_element_children_buffer[buf_idx];
1799 let child = &self.layout_elements[child_index as usize];
1800 let child_width = child.dimensions.width;
1801 let child_height = child.dimensions.height;
1802 let child_min_width = child.min_dimensions.width;
1803 let child_min_height = child.min_dimensions.height;
1804
1805 self.layout_elements[open_idx].dimensions.height += child_height;
1806 let current_width = self.layout_elements[open_idx].dimensions.width;
1807 self.layout_elements[open_idx].dimensions.width =
1808 f32::max(current_width, child_width + left_right_padding);
1809
1810 if !element_has_clip_vertical {
1811 self.layout_elements[open_idx].min_dimensions.height += child_min_height;
1812 }
1813 if !element_has_clip_horizontal {
1814 let current_min_w = self.layout_elements[open_idx].min_dimensions.width;
1815 self.layout_elements[open_idx].min_dimensions.width =
1816 f32::max(current_min_w, child_min_width + left_right_padding);
1817 }
1818 self.layout_element_children.push(child_index);
1819 }
1820 let child_gap =
1821 (children_length.saturating_sub(1) as u32 * layout_config.child_gap as u32) as f32;
1822 self.layout_elements[open_idx].dimensions.height += child_gap;
1823 if !element_has_clip_vertical {
1824 self.layout_elements[open_idx].min_dimensions.height += child_gap;
1825 }
1826 }
1827
1828 let remove_count = children_length as usize;
1830 let new_len = self.layout_element_children_buffer.len().saturating_sub(remove_count);
1831 self.layout_element_children_buffer.truncate(new_len);
1832
1833 {
1835 let sizing_type = self.layout_configs[layout_config_index].sizing.width.type_;
1836 if sizing_type != SizingType::Percent {
1837 let mut max_w = self.layout_configs[layout_config_index].sizing.width.min_max.max;
1838 if max_w <= 0.0 {
1839 max_w = MAXFLOAT;
1840 self.layout_configs[layout_config_index].sizing.width.min_max.max = max_w;
1841 }
1842 let min_w = self.layout_configs[layout_config_index].sizing.width.min_max.min;
1843 self.layout_elements[open_idx].dimensions.width = f32::min(
1844 f32::max(self.layout_elements[open_idx].dimensions.width, min_w),
1845 max_w,
1846 );
1847 self.layout_elements[open_idx].min_dimensions.width = f32::min(
1848 f32::max(self.layout_elements[open_idx].min_dimensions.width, min_w),
1849 max_w,
1850 );
1851 } else {
1852 self.layout_elements[open_idx].dimensions.width = 0.0;
1853 }
1854 }
1855
1856 {
1858 let sizing_type = self.layout_configs[layout_config_index].sizing.height.type_;
1859 if sizing_type != SizingType::Percent {
1860 let mut max_h = self.layout_configs[layout_config_index].sizing.height.min_max.max;
1861 if max_h <= 0.0 {
1862 max_h = MAXFLOAT;
1863 self.layout_configs[layout_config_index].sizing.height.min_max.max = max_h;
1864 }
1865 let min_h = self.layout_configs[layout_config_index].sizing.height.min_max.min;
1866 self.layout_elements[open_idx].dimensions.height = f32::min(
1867 f32::max(self.layout_elements[open_idx].dimensions.height, min_h),
1868 max_h,
1869 );
1870 self.layout_elements[open_idx].min_dimensions.height = f32::min(
1871 f32::max(self.layout_elements[open_idx].min_dimensions.height, min_h),
1872 max_h,
1873 );
1874 } else {
1875 self.layout_elements[open_idx].dimensions.height = 0.0;
1876 }
1877 }
1878
1879 self.update_aspect_ratio_box(open_idx);
1880
1881 if let Some(shape_rot) = self.element_shape_rotations.get(open_idx).copied().flatten() {
1883 if !shape_rot.is_noop() {
1884 let orig_w = self.layout_elements[open_idx].dimensions.width;
1885 let orig_h = self.layout_elements[open_idx].dimensions.height;
1886
1887 let cr = self
1889 .find_element_config_index(open_idx, ElementConfigType::Shared)
1890 .map(|idx| self.shared_element_configs[idx].corner_radius)
1891 .unwrap_or_default();
1892
1893 let (eff_w, eff_h) = crate::math::compute_rotated_aabb(
1894 orig_w,
1895 orig_h,
1896 &cr,
1897 shape_rot.rotation_radians,
1898 );
1899
1900 while self.element_pre_rotation_dimensions.len() <= open_idx {
1902 self.element_pre_rotation_dimensions.push(None);
1903 }
1904 self.element_pre_rotation_dimensions[open_idx] =
1905 Some(Dimensions::new(orig_w, orig_h));
1906
1907 self.layout_elements[open_idx].dimensions.width = eff_w;
1909 self.layout_elements[open_idx].dimensions.height = eff_h;
1910 self.layout_elements[open_idx].min_dimensions.width = eff_w;
1911 self.layout_elements[open_idx].min_dimensions.height = eff_h;
1912 }
1913 }
1914
1915 let element_is_floating =
1916 self.element_has_config(open_idx, ElementConfigType::Floating);
1917
1918 self.open_layout_element_stack.pop();
1920
1921 if self.open_layout_element_stack.len() > 1 {
1923 if element_is_floating {
1924 let parent_idx = self.get_open_layout_element();
1925 self.layout_elements[parent_idx].floating_children_count += 1;
1926 return;
1927 }
1928 let parent_idx = self.get_open_layout_element();
1929 self.layout_elements[parent_idx].children_length += 1;
1930 self.layout_element_children_buffer.push(open_idx as i32);
1931 }
1932 }
1933
1934 pub fn open_text_element(
1935 &mut self,
1936 text: &str,
1937 text_config_index: usize,
1938 ) {
1939 if self.boolean_warnings.max_elements_exceeded {
1940 return;
1941 }
1942
1943 let parent_idx = self.get_open_layout_element();
1944 let parent_id = self.layout_elements[parent_idx].id;
1945 let parent_children_count = self.layout_elements[parent_idx].children_length;
1946
1947 let text_element = LayoutElement {
1949 text_data_index: -1,
1950 ..Default::default()
1951 };
1952 self.layout_elements.push(text_element);
1953 let text_elem_idx = (self.layout_elements.len() - 1) as i32;
1954
1955 while self.layout_element_clip_element_ids.len() < self.layout_elements.len() {
1956 self.layout_element_clip_element_ids.push(0);
1957 }
1958 if !self.open_clip_element_stack.is_empty() {
1959 let clip_id = *self.open_clip_element_stack.last().unwrap();
1960 self.layout_element_clip_element_ids[text_elem_idx as usize] = clip_id;
1961 } else {
1962 self.layout_element_clip_element_ids[text_elem_idx as usize] = 0;
1963 }
1964
1965 self.layout_element_children_buffer.push(text_elem_idx);
1966
1967 let text_config = self.text_element_configs[text_config_index].clone();
1969 let text_measured =
1970 self.measure_text_cached(text, &text_config);
1971
1972 let element_id = hash_number(parent_children_count as u32, parent_id);
1973 self.layout_elements[text_elem_idx as usize].id = element_id.id;
1974 self.add_hash_map_item(&element_id, text_elem_idx);
1975 if self.debug_mode_enabled {
1976 self.layout_element_id_strings.push(element_id.string_id);
1977 }
1978
1979 if text_config.accessible {
1983 let a11y = crate::accessibility::AccessibilityConfig {
1984 role: crate::accessibility::AccessibilityRole::StaticText,
1985 label: text.to_string(),
1986 ..Default::default()
1987 };
1988 self.accessibility_configs.insert(element_id.id, a11y);
1989 self.accessibility_element_order.push(element_id.id);
1990 }
1991
1992 let text_width = text_measured.unwrapped_dimensions.width;
1993 let text_height = if text_config.line_height > 0 {
1994 text_config.line_height as f32
1995 } else {
1996 text_measured.unwrapped_dimensions.height
1997 };
1998 let min_width = text_measured.min_width;
1999
2000 self.layout_elements[text_elem_idx as usize].dimensions =
2001 Dimensions::new(text_width, text_height);
2002 self.layout_elements[text_elem_idx as usize].min_dimensions =
2003 Dimensions::new(min_width, text_height);
2004
2005 let text_data = TextElementData {
2007 text: text.to_string(),
2008 preferred_dimensions: text_measured.unwrapped_dimensions,
2009 element_index: text_elem_idx,
2010 wrapped_lines_start: 0,
2011 wrapped_lines_length: 0,
2012 };
2013 self.text_element_data.push(text_data);
2014 let text_data_idx = (self.text_element_data.len() - 1) as i32;
2015 self.layout_elements[text_elem_idx as usize].text_data_index = text_data_idx;
2016
2017 self.layout_elements[text_elem_idx as usize].element_configs.start =
2019 self.element_configs.len();
2020 self.element_configs.push(ElementConfig {
2021 config_type: ElementConfigType::Text,
2022 config_index: text_config_index,
2023 });
2024 self.layout_elements[text_elem_idx as usize].element_configs.length = 1;
2025
2026 let default_layout_idx = self.store_layout_config(LayoutConfig::default());
2028 self.layout_elements[text_elem_idx as usize].layout_config_index = default_layout_idx;
2029
2030 self.layout_elements[parent_idx].children_length += 1;
2032 }
2033
2034 fn font_height(&mut self, font_asset: Option<&'static crate::renderer::FontAsset>, font_size: u16) -> f32 {
2037 let font_key = font_asset.map(|a| a.key()).unwrap_or("");
2038 let key = (font_key, font_size);
2039 if let Some(&h) = self.font_height_cache.get(&key) {
2040 return h;
2041 }
2042 let h = if let Some(ref measure_fn) = self.measure_text_fn {
2043 let config = TextConfig {
2044 font_asset,
2045 font_size,
2046 ..Default::default()
2047 };
2048 measure_fn("Mg", &config).height
2049 } else {
2050 font_size as f32
2051 };
2052 let font_loaded = font_asset
2053 .map_or(true, |a| crate::renderer::FontManager::is_loaded(a));
2054 if font_loaded {
2055 self.font_height_cache.insert(key, h);
2056 }
2057 h
2058 }
2059
2060 fn measure_text_cached(
2061 &mut self,
2062 text: &str,
2063 config: &TextConfig,
2064 ) -> MeasureTextCacheItem {
2065 match &self.measure_text_fn {
2066 Some(_) => {},
2067 None => {
2068 if !self.boolean_warnings.text_measurement_fn_not_set {
2069 self.boolean_warnings.text_measurement_fn_not_set = true;
2070 }
2071 return MeasureTextCacheItem::default();
2072 }
2073 };
2074
2075 let id = hash_string_contents_with_config(text, config);
2076
2077 if let Some(item) = self.measure_text_cache.get_mut(&id) {
2079 item.generation = self.generation;
2080 return *item;
2081 }
2082
2083 let text_data = text.as_bytes();
2085 let text_length = text_data.len() as i32;
2086
2087 let space_str = " ";
2088 let space_width = (self.measure_text_fn.as_ref().unwrap())(space_str, config).width;
2089
2090 let mut start: i32 = 0;
2091 let mut end: i32 = 0;
2092 let mut line_width: f32 = 0.0;
2093 let mut measured_width: f32 = 0.0;
2094 let mut measured_height: f32 = 0.0;
2095 let mut min_width: f32 = 0.0;
2096 let mut contains_newlines = false;
2097
2098 let mut temp_word_next: i32 = -1;
2099 let mut previous_word_index: i32 = -1;
2100
2101 while end < text_length {
2102 let current = text_data[end as usize];
2103 if current == b' ' || current == b'\n' {
2104 let length = end - start;
2105 let mut dimensions = Dimensions::default();
2106 if length > 0 {
2107 let substr =
2108 core::str::from_utf8(&text_data[start as usize..end as usize]).unwrap();
2109 dimensions = (self.measure_text_fn.as_ref().unwrap())(substr, config);
2110 }
2111 min_width = f32::max(dimensions.width, min_width);
2112 measured_height = f32::max(measured_height, dimensions.height);
2113
2114 if current == b' ' {
2115 dimensions.width += space_width;
2116 let word = MeasuredWord {
2117 start_offset: start,
2118 length: length + 1,
2119 width: dimensions.width,
2120 next: -1,
2121 };
2122 let word_idx = self.add_measured_word(word, previous_word_index);
2123 if previous_word_index == -1 {
2124 temp_word_next = word_idx;
2125 }
2126 previous_word_index = word_idx;
2127 line_width += dimensions.width;
2128 }
2129 if current == b'\n' {
2130 if length > 0 {
2131 let word = MeasuredWord {
2132 start_offset: start,
2133 length,
2134 width: dimensions.width,
2135 next: -1,
2136 };
2137 let word_idx = self.add_measured_word(word, previous_word_index);
2138 if previous_word_index == -1 {
2139 temp_word_next = word_idx;
2140 }
2141 previous_word_index = word_idx;
2142 }
2143 let newline_word = MeasuredWord {
2144 start_offset: end + 1,
2145 length: 0,
2146 width: 0.0,
2147 next: -1,
2148 };
2149 let word_idx = self.add_measured_word(newline_word, previous_word_index);
2150 if previous_word_index == -1 {
2151 temp_word_next = word_idx;
2152 }
2153 previous_word_index = word_idx;
2154 line_width += dimensions.width;
2155 measured_width = f32::max(line_width, measured_width);
2156 contains_newlines = true;
2157 line_width = 0.0;
2158 }
2159 start = end + 1;
2160 }
2161 end += 1;
2162 }
2163
2164 if end - start > 0 {
2165 let substr =
2166 core::str::from_utf8(&text_data[start as usize..end as usize]).unwrap();
2167 let dimensions = (self.measure_text_fn.as_ref().unwrap())(substr, config);
2168 let word = MeasuredWord {
2169 start_offset: start,
2170 length: end - start,
2171 width: dimensions.width,
2172 next: -1,
2173 };
2174 let word_idx = self.add_measured_word(word, previous_word_index);
2175 if previous_word_index == -1 {
2176 temp_word_next = word_idx;
2177 }
2178 line_width += dimensions.width;
2179 measured_height = f32::max(measured_height, dimensions.height);
2180 min_width = f32::max(dimensions.width, min_width);
2181 }
2182
2183 measured_width =
2184 f32::max(line_width, measured_width) - config.letter_spacing as f32;
2185
2186 let result = MeasureTextCacheItem {
2187 id,
2188 generation: self.generation,
2189 measured_words_start_index: temp_word_next,
2190 unwrapped_dimensions: Dimensions::new(measured_width, measured_height),
2191 min_width,
2192 contains_newlines,
2193 };
2194 self.measure_text_cache.insert(id, result);
2195 result
2196 }
2197
2198 fn add_measured_word(&mut self, word: MeasuredWord, previous_word_index: i32) -> i32 {
2199 let new_index: i32;
2200 if let Some(&free_idx) = self.measured_words_free_list.last() {
2201 self.measured_words_free_list.pop();
2202 new_index = free_idx;
2203 self.measured_words[free_idx as usize] = word;
2204 } else {
2205 self.measured_words.push(word);
2206 new_index = (self.measured_words.len() - 1) as i32;
2207 }
2208 if previous_word_index >= 0 {
2209 self.measured_words[previous_word_index as usize].next = new_index;
2210 }
2211 new_index
2212 }
2213
2214 pub fn begin_layout(&mut self) {
2215 self.initialize_ephemeral_memory();
2216 self.generation += 1;
2217 if self.pressed_this_frame_generation != self.generation {
2218 self.pressed_this_frame_ids.clear();
2219 }
2220 if self.released_this_frame_generation != self.generation {
2221 self.released_this_frame_ids.clear();
2222 }
2223 self.dynamic_element_index = 0;
2224
2225 self.evict_stale_text_cache();
2227
2228 let root_width = self.layout_dimensions.width;
2229 let root_height = self.layout_dimensions.height;
2230
2231 self.boolean_warnings = BooleanWarnings::default();
2232
2233 let root_id = hash_string("Ply__RootContainer", 0);
2234 self.open_element_with_id(&root_id);
2235
2236 let root_decl = ElementDeclaration {
2237 layout: LayoutConfig {
2238 sizing: SizingConfig {
2239 width: SizingAxis {
2240 type_: SizingType::Fixed,
2241 min_max: SizingMinMax {
2242 min: root_width,
2243 max: root_width,
2244 },
2245 percent: 0.0,
2246 grow_weight: 1.0,
2247 },
2248 height: SizingAxis {
2249 type_: SizingType::Fixed,
2250 min_max: SizingMinMax {
2251 min: root_height,
2252 max: root_height,
2253 },
2254 percent: 0.0,
2255 grow_weight: 1.0,
2256 },
2257 },
2258 ..Default::default()
2259 },
2260 ..Default::default()
2261 };
2262 self.configure_open_element(&root_decl);
2263 self.open_layout_element_stack.push(0);
2264 self.layout_element_tree_roots.push(LayoutElementTreeRoot {
2265 layout_element_index: 0,
2266 ..Default::default()
2267 });
2268 }
2269
2270 pub fn end_layout(&mut self) -> &[InternalRenderCommand<CustomElementData>] {
2271 self.close_element();
2272
2273 if self.open_layout_element_stack.len() > 1 {
2274 }
2276
2277 if self.debug_mode_enabled {
2278 self.render_debug_view();
2279 }
2280
2281 self.calculate_final_layout();
2282 &self.render_commands
2283 }
2284
2285 fn evict_stale_text_cache(&mut self) {
2288 let gen = self.generation;
2289 let measured_words = &mut self.measured_words;
2290 let free_list = &mut self.measured_words_free_list;
2291 self.measure_text_cache.retain(|_, item| {
2292 if gen.wrapping_sub(item.generation) <= 2 {
2293 true
2294 } else {
2295 let mut idx = item.measured_words_start_index;
2297 while idx != -1 {
2298 let word = measured_words[idx as usize];
2299 free_list.push(idx);
2300 idx = word.next;
2301 }
2302 false
2303 }
2304 });
2305 }
2306
2307 fn initialize_ephemeral_memory(&mut self) {
2308 self.layout_element_children_buffer.clear();
2309 self.layout_elements.clear();
2310 self.layout_configs.clear();
2311 self.element_configs.clear();
2312 self.text_element_configs.clear();
2313 self.aspect_ratio_configs.clear();
2314 self.aspect_ratio_cover_configs.clear();
2315 self.image_element_configs.clear();
2316 self.floating_element_configs.clear();
2317 self.clip_element_configs.clear();
2318 self.custom_element_configs.clear();
2319 self.border_element_configs.clear();
2320 self.shared_element_configs.clear();
2321 self.element_effects.clear();
2322 self.element_shaders.clear();
2323 self.element_visual_rotations.clear();
2324 self.element_shape_rotations.clear();
2325 self.element_pre_rotation_dimensions.clear();
2326 self.layout_element_id_strings.clear();
2327 self.wrapped_text_lines.clear();
2328 self.tree_node_array.clear();
2329 self.layout_element_tree_roots.clear();
2330 self.layout_element_children.clear();
2331 self.open_layout_element_stack.clear();
2332 self.text_element_data.clear();
2333 self.aspect_ratio_element_indexes.clear();
2334 self.render_commands.clear();
2335 self.tree_node_visited.clear();
2336 self.open_clip_element_stack.clear();
2337 self.reusable_element_index_buffer.clear();
2338 self.layout_element_clip_element_ids.clear();
2339 self.dynamic_string_data.clear();
2340 self.focusable_elements.clear();
2341 self.accessibility_configs.clear();
2342 self.accessibility_element_order.clear();
2343 self.text_input_configs.clear();
2344 self.text_input_element_ids.clear();
2345 }
2346
2347 fn child_size_on_axis(&self, child_index: usize, x_axis: bool) -> f32 {
2348 if x_axis {
2349 self.layout_elements[child_index].dimensions.width
2350 } else {
2351 self.layout_elements[child_index].dimensions.height
2352 }
2353 }
2354
2355 fn child_sizing_on_axis(&self, child_index: usize, x_axis: bool) -> SizingAxis {
2356 let child_layout_idx = self.layout_elements[child_index].layout_config_index;
2357 if x_axis {
2358 self.layout_configs[child_layout_idx].sizing.width
2359 } else {
2360 self.layout_configs[child_layout_idx].sizing.height
2361 }
2362 }
2363
2364 fn child_wrap_break_main_size(&self, child_index: usize, main_axis_x: bool) -> f32 {
2365 let child_sizing = self.child_sizing_on_axis(child_index, main_axis_x);
2366 match child_sizing.type_ {
2367 SizingType::Grow => child_sizing.min_max.min,
2368 SizingType::Percent | SizingType::Fixed | SizingType::Fit => {
2369 self.child_size_on_axis(child_index, main_axis_x)
2370 }
2371 }
2372 }
2373
2374 fn build_wrapped_line(
2375 &self,
2376 parent_index: usize,
2377 start_child_offset: usize,
2378 end_child_offset: usize,
2379 main_axis_x: bool,
2380 ) -> WrappedLayoutLine {
2381 let layout_idx = self.layout_elements[parent_index].layout_config_index;
2382 let layout = self.layout_configs[layout_idx];
2383 let children_start = self.layout_elements[parent_index].children_start;
2384
2385 let mut main_size = 0.0;
2386 let mut cross_size = 0.0;
2387
2388 for child_offset in start_child_offset..end_child_offset {
2389 let child_index = self.layout_element_children[children_start + child_offset] as usize;
2390 if child_offset > start_child_offset {
2391 main_size += layout.child_gap as f32;
2392 }
2393
2394 let child_main = self.child_size_on_axis(child_index, main_axis_x);
2395 let child_cross = self.child_size_on_axis(child_index, !main_axis_x);
2396 main_size += child_main;
2397 cross_size = f32::max(cross_size, child_cross);
2398 }
2399
2400 WrappedLayoutLine {
2401 start_child_offset,
2402 end_child_offset,
2403 main_size,
2404 cross_size,
2405 }
2406 }
2407
2408 fn compute_wrapped_lines(
2409 &self,
2410 parent_index: usize,
2411 main_axis_x: bool,
2412 ) -> Vec<WrappedLayoutLine> {
2413 let layout_idx = self.layout_elements[parent_index].layout_config_index;
2414 let layout = self.layout_configs[layout_idx];
2415
2416 let children_length = self.layout_elements[parent_index].children_length as usize;
2417 if children_length == 0 {
2418 return Vec::new();
2419 }
2420
2421 let parent_main_size = if main_axis_x {
2422 self.layout_elements[parent_index].dimensions.width
2423 } else {
2424 self.layout_elements[parent_index].dimensions.height
2425 };
2426 let main_padding = if main_axis_x {
2427 (layout.padding.left + layout.padding.right) as f32
2428 } else {
2429 (layout.padding.top + layout.padding.bottom) as f32
2430 };
2431 let available_main = (parent_main_size - main_padding).max(0.0);
2432
2433 if !layout.wrap {
2434 return vec![self.build_wrapped_line(
2435 parent_index,
2436 0,
2437 children_length,
2438 main_axis_x,
2439 )];
2440 }
2441
2442 let children_start = self.layout_elements[parent_index].children_start;
2443 let mut lines = Vec::new();
2444 let mut line_start = 0usize;
2445 let mut line_break_main = 0.0;
2446
2447 for child_offset in 0..children_length {
2448 let child_index = self.layout_element_children[children_start + child_offset] as usize;
2449 let break_size = self.child_wrap_break_main_size(child_index, main_axis_x);
2450
2451 let additional = if child_offset == line_start {
2452 break_size
2453 } else {
2454 layout.child_gap as f32 + break_size
2455 };
2456
2457 if child_offset > line_start && line_break_main + additional > available_main + EPSILON {
2458 lines.push(self.build_wrapped_line(
2459 parent_index,
2460 line_start,
2461 child_offset,
2462 main_axis_x,
2463 ));
2464 line_start = child_offset;
2465 line_break_main = break_size;
2466 } else {
2467 line_break_main += additional;
2468 }
2469 }
2470
2471 lines.push(self.build_wrapped_line(
2472 parent_index,
2473 line_start,
2474 children_length,
2475 main_axis_x,
2476 ));
2477
2478 lines
2479 }
2480
2481 fn wrapped_content_dimensions(
2482 &self,
2483 parent_index: usize,
2484 main_axis_x: bool,
2485 lines: &[WrappedLayoutLine],
2486 ) -> Dimensions {
2487 let layout_idx = self.layout_elements[parent_index].layout_config_index;
2488 let layout = self.layout_configs[layout_idx];
2489
2490 let lr_padding = (layout.padding.left + layout.padding.right) as f32;
2491 let tb_padding = (layout.padding.top + layout.padding.bottom) as f32;
2492
2493 if lines.is_empty() {
2494 return Dimensions::new(lr_padding, tb_padding);
2495 }
2496
2497 let wrap_gap_total = lines.len().saturating_sub(1) as f32 * layout.wrap_gap as f32;
2498 let max_main = lines.iter().fold(0.0_f32, |acc, line| acc.max(line.main_size));
2499 let total_cross = lines.iter().map(|line| line.cross_size).sum::<f32>() + wrap_gap_total;
2500
2501 if main_axis_x {
2502 Dimensions::new(max_main + lr_padding, total_cross + tb_padding)
2503 } else {
2504 Dimensions::new(total_cross + lr_padding, max_main + tb_padding)
2505 }
2506 }
2507
2508 fn size_containers_along_axis(&mut self, x_axis: bool) {
2509 let mut bfs_buffer: Vec<i32> = Vec::new();
2510 let mut resizable_container_buffer: Vec<i32> = Vec::new();
2511
2512 for root_index in 0..self.layout_element_tree_roots.len() {
2513 bfs_buffer.clear();
2514 let root = self.layout_element_tree_roots[root_index];
2515 let root_elem_idx = root.layout_element_index as usize;
2516 bfs_buffer.push(root.layout_element_index);
2517
2518 if self.element_has_config(root_elem_idx, ElementConfigType::Floating) {
2520 if let Some(float_cfg_idx) =
2521 self.find_element_config_index(root_elem_idx, ElementConfigType::Floating)
2522 {
2523 let parent_id = self.floating_element_configs[float_cfg_idx].parent_id;
2524 if let Some(parent_item) = self.layout_element_map.get(&parent_id) {
2525 let parent_elem_idx = parent_item.layout_element_index as usize;
2526 let parent_dims = self.layout_elements[parent_elem_idx].dimensions;
2527 let root_layout_idx =
2528 self.layout_elements[root_elem_idx].layout_config_index;
2529
2530 let w_type = self.layout_configs[root_layout_idx].sizing.width.type_;
2531 match w_type {
2532 SizingType::Grow => {
2533 self.layout_elements[root_elem_idx].dimensions.width =
2534 parent_dims.width;
2535 }
2536 SizingType::Percent => {
2537 self.layout_elements[root_elem_idx].dimensions.width =
2538 parent_dims.width
2539 * self.layout_configs[root_layout_idx]
2540 .sizing
2541 .width
2542 .percent;
2543 }
2544 _ => {}
2545 }
2546 let h_type = self.layout_configs[root_layout_idx].sizing.height.type_;
2547 match h_type {
2548 SizingType::Grow => {
2549 self.layout_elements[root_elem_idx].dimensions.height =
2550 parent_dims.height;
2551 }
2552 SizingType::Percent => {
2553 self.layout_elements[root_elem_idx].dimensions.height =
2554 parent_dims.height
2555 * self.layout_configs[root_layout_idx]
2556 .sizing
2557 .height
2558 .percent;
2559 }
2560 _ => {}
2561 }
2562 }
2563 }
2564 }
2565
2566 let root_layout_idx = self.layout_elements[root_elem_idx].layout_config_index;
2568 if self.layout_configs[root_layout_idx].sizing.width.type_ != SizingType::Percent {
2569 let min = self.layout_configs[root_layout_idx].sizing.width.min_max.min;
2570 let max = self.layout_configs[root_layout_idx].sizing.width.min_max.max;
2571 self.layout_elements[root_elem_idx].dimensions.width = f32::min(
2572 f32::max(self.layout_elements[root_elem_idx].dimensions.width, min),
2573 max,
2574 );
2575 }
2576 if self.layout_configs[root_layout_idx].sizing.height.type_ != SizingType::Percent {
2577 let min = self.layout_configs[root_layout_idx].sizing.height.min_max.min;
2578 let max = self.layout_configs[root_layout_idx].sizing.height.min_max.max;
2579 self.layout_elements[root_elem_idx].dimensions.height = f32::min(
2580 f32::max(self.layout_elements[root_elem_idx].dimensions.height, min),
2581 max,
2582 );
2583 }
2584
2585 let mut i = 0;
2586 while i < bfs_buffer.len() {
2587 let parent_index = bfs_buffer[i] as usize;
2588 i += 1;
2589
2590 let parent_layout_idx = self.layout_elements[parent_index].layout_config_index;
2591 let parent_config = self.layout_configs[parent_layout_idx];
2592 let parent_size = if x_axis {
2593 self.layout_elements[parent_index].dimensions.width
2594 } else {
2595 self.layout_elements[parent_index].dimensions.height
2596 };
2597 let parent_padding = if x_axis {
2598 (parent_config.padding.left + parent_config.padding.right) as f32
2599 } else {
2600 (parent_config.padding.top + parent_config.padding.bottom) as f32
2601 };
2602 let sizing_along_axis = (x_axis
2603 && parent_config.layout_direction == LayoutDirection::LeftToRight)
2604 || (!x_axis
2605 && parent_config.layout_direction == LayoutDirection::TopToBottom);
2606
2607 let mut inner_content_size: f32 = 0.0;
2608 let mut total_padding_and_child_gaps = parent_padding;
2609 let parent_child_gap = parent_config.child_gap as f32;
2610 let mut single_along_axis_grow_candidate: Option<Option<usize>> = None;
2612
2613 resizable_container_buffer.clear();
2614
2615 let children_start = self.layout_elements[parent_index].children_start;
2616 let children_length = self.layout_elements[parent_index].children_length as usize;
2617
2618 for child_offset in 0..children_length {
2619 let child_element_index =
2620 self.layout_element_children[children_start + child_offset] as usize;
2621 let child_layout_idx =
2622 self.layout_elements[child_element_index].layout_config_index;
2623 let child_sizing = if x_axis {
2624 self.layout_configs[child_layout_idx].sizing.width
2625 } else {
2626 self.layout_configs[child_layout_idx].sizing.height
2627 };
2628 let child_size = if x_axis {
2629 self.layout_elements[child_element_index].dimensions.width
2630 } else {
2631 self.layout_elements[child_element_index].dimensions.height
2632 };
2633
2634 let is_text_element =
2635 self.element_has_config(child_element_index, ElementConfigType::Text);
2636 let has_children = self.layout_elements[child_element_index].children_length > 0;
2637
2638 if !is_text_element && has_children {
2639 bfs_buffer.push(child_element_index as i32);
2640 }
2641
2642 let is_wrapping_text = if is_text_element {
2643 if let Some(text_cfg_idx) = self.find_element_config_index(
2644 child_element_index,
2645 ElementConfigType::Text,
2646 ) {
2647 self.text_element_configs[text_cfg_idx].wrap_mode
2648 == WrapMode::Words
2649 } else {
2650 false
2651 }
2652 } else {
2653 false
2654 };
2655
2656 if child_sizing.type_ != SizingType::Percent
2657 && child_sizing.type_ != SizingType::Fixed
2658 && (!is_text_element || is_wrapping_text)
2659 {
2660 resizable_container_buffer.push(child_element_index as i32);
2661 if sizing_along_axis
2662 && child_sizing.type_ == SizingType::Grow
2663 && child_sizing.grow_weight > 0.0
2664 {
2665 single_along_axis_grow_candidate = match single_along_axis_grow_candidate {
2666 None => Some(Some(child_element_index)),
2667 Some(Some(_)) | Some(None) => Some(None),
2668 };
2669 }
2670 }
2671
2672 if sizing_along_axis {
2673 inner_content_size += if child_sizing.type_ == SizingType::Percent {
2674 0.0
2675 } else {
2676 child_size
2677 };
2678 if child_offset > 0 {
2679 inner_content_size += parent_child_gap;
2680 total_padding_and_child_gaps += parent_child_gap;
2681 }
2682 } else {
2683 inner_content_size = f32::max(child_size, inner_content_size);
2684 }
2685 }
2686
2687 for child_offset in 0..children_length {
2689 let child_element_index =
2690 self.layout_element_children[children_start + child_offset] as usize;
2691 let child_layout_idx =
2692 self.layout_elements[child_element_index].layout_config_index;
2693 let child_sizing = if x_axis {
2694 self.layout_configs[child_layout_idx].sizing.width
2695 } else {
2696 self.layout_configs[child_layout_idx].sizing.height
2697 };
2698 if child_sizing.type_ == SizingType::Percent {
2699 let new_size =
2700 (parent_size - total_padding_and_child_gaps) * child_sizing.percent;
2701 if x_axis {
2702 self.layout_elements[child_element_index].dimensions.width = new_size;
2703 } else {
2704 self.layout_elements[child_element_index].dimensions.height = new_size;
2705 }
2706 if sizing_along_axis {
2707 inner_content_size += new_size;
2708 }
2709 self.update_aspect_ratio_box(child_element_index);
2710 }
2711 }
2712
2713 if sizing_along_axis && parent_config.wrap {
2714 let parent_clips = if let Some(clip_idx) = self
2715 .find_element_config_index(parent_index, ElementConfigType::Clip)
2716 {
2717 let clip = &self.clip_element_configs[clip_idx];
2718 (x_axis && clip.horizontal) || (!x_axis && clip.vertical)
2719 } else {
2720 false
2721 };
2722
2723 let wrapped_lines = self.compute_wrapped_lines(parent_index, x_axis);
2724
2725 for line in wrapped_lines {
2726 if line.end_child_offset <= line.start_child_offset {
2727 continue;
2728 }
2729
2730 let mut line_children: Vec<usize> = Vec::new();
2731 for child_offset in line.start_child_offset..line.end_child_offset {
2732 let child_element_index =
2733 self.layout_element_children[children_start + child_offset] as usize;
2734 line_children.push(child_element_index);
2735 }
2736
2737 let mut line_inner_content_size: f32 = 0.0;
2738 let mut line_resizable_buffer: Vec<i32> = Vec::new();
2739 let mut single_line_grow_candidate: Option<Option<usize>> = None;
2740
2741 for line_child_offset in 0..line_children.len() {
2742 let child_idx = line_children[line_child_offset];
2743 let child_layout_idx = self.layout_elements[child_idx].layout_config_index;
2744 let child_sizing = if x_axis {
2745 self.layout_configs[child_layout_idx].sizing.width
2746 } else {
2747 self.layout_configs[child_layout_idx].sizing.height
2748 };
2749 let child_size = if x_axis {
2750 self.layout_elements[child_idx].dimensions.width
2751 } else {
2752 self.layout_elements[child_idx].dimensions.height
2753 };
2754
2755 if line_child_offset > 0 {
2756 line_inner_content_size += parent_child_gap;
2757 }
2758 line_inner_content_size += child_size;
2759
2760 let is_text_element =
2761 self.element_has_config(child_idx, ElementConfigType::Text);
2762 let is_wrapping_text = if is_text_element {
2763 if let Some(text_cfg_idx) = self
2764 .find_element_config_index(child_idx, ElementConfigType::Text)
2765 {
2766 self.text_element_configs[text_cfg_idx].wrap_mode
2767 == WrapMode::Words
2768 } else {
2769 false
2770 }
2771 } else {
2772 false
2773 };
2774
2775 if child_sizing.type_ != SizingType::Percent
2776 && child_sizing.type_ != SizingType::Fixed
2777 && (!is_text_element || is_wrapping_text)
2778 {
2779 line_resizable_buffer.push(child_idx as i32);
2780
2781 if child_sizing.type_ == SizingType::Grow
2782 && child_sizing.grow_weight > 0.0
2783 {
2784 single_line_grow_candidate = match single_line_grow_candidate {
2785 None => Some(Some(child_idx)),
2786 Some(Some(_)) | Some(None) => Some(None),
2787 };
2788 }
2789 }
2790 }
2791
2792 let size_to_distribute =
2793 parent_size - parent_padding - line_inner_content_size;
2794
2795 if size_to_distribute < 0.0 {
2796 if parent_clips {
2797 continue;
2798 }
2799
2800 let mut distribute = size_to_distribute;
2801 while distribute < -EPSILON && !line_resizable_buffer.is_empty() {
2802 let mut largest: f32 = 0.0;
2803 let mut second_largest: f32 = 0.0;
2804 let mut width_to_add = distribute;
2805
2806 for &child_idx in &line_resizable_buffer {
2807 let cs = if x_axis {
2808 self.layout_elements[child_idx as usize].dimensions.width
2809 } else {
2810 self.layout_elements[child_idx as usize].dimensions.height
2811 };
2812 if float_equal(cs, largest) {
2813 continue;
2814 }
2815 if cs > largest {
2816 second_largest = largest;
2817 largest = cs;
2818 }
2819 if cs < largest {
2820 second_largest = f32::max(second_largest, cs);
2821 width_to_add = second_largest - largest;
2822 }
2823 }
2824
2825 width_to_add = f32::max(
2826 width_to_add,
2827 distribute / line_resizable_buffer.len() as f32,
2828 );
2829
2830 let mut j = 0;
2831 while j < line_resizable_buffer.len() {
2832 let child_idx = line_resizable_buffer[j] as usize;
2833 let current_size = if x_axis {
2834 self.layout_elements[child_idx].dimensions.width
2835 } else {
2836 self.layout_elements[child_idx].dimensions.height
2837 };
2838 let min_size = if x_axis {
2839 self.layout_elements[child_idx].min_dimensions.width
2840 } else {
2841 self.layout_elements[child_idx].min_dimensions.height
2842 };
2843
2844 if float_equal(current_size, largest) {
2845 let new_size = current_size + width_to_add;
2846 if new_size <= min_size {
2847 if x_axis {
2848 self.layout_elements[child_idx].dimensions.width =
2849 min_size;
2850 } else {
2851 self.layout_elements[child_idx].dimensions.height =
2852 min_size;
2853 }
2854 distribute -= min_size - current_size;
2855 line_resizable_buffer.swap_remove(j);
2856 continue;
2857 }
2858
2859 if x_axis {
2860 self.layout_elements[child_idx].dimensions.width =
2861 new_size;
2862 } else {
2863 self.layout_elements[child_idx].dimensions.height =
2864 new_size;
2865 }
2866 distribute -= new_size - current_size;
2867 }
2868
2869 j += 1;
2870 }
2871 }
2872 } else if size_to_distribute > 0.0 {
2873 if let Some(Some(single_line_grow_child_idx)) =
2874 single_line_grow_candidate
2875 {
2876 let child_layout_idx =
2877 self.layout_elements[single_line_grow_child_idx]
2878 .layout_config_index;
2879 let child_max_size = if x_axis {
2880 self.layout_configs[child_layout_idx].sizing.width.min_max.max
2881 } else {
2882 self.layout_configs[child_layout_idx]
2883 .sizing
2884 .height
2885 .min_max
2886 .max
2887 };
2888
2889 let child_size_ref = if x_axis {
2890 &mut self.layout_elements[single_line_grow_child_idx]
2891 .dimensions
2892 .width
2893 } else {
2894 &mut self.layout_elements[single_line_grow_child_idx]
2895 .dimensions
2896 .height
2897 };
2898
2899 *child_size_ref =
2900 f32::min(*child_size_ref + size_to_distribute, child_max_size);
2901 } else {
2902 let mut j = 0;
2903 while j < line_resizable_buffer.len() {
2904 let child_idx = line_resizable_buffer[j] as usize;
2905 let child_layout_idx =
2906 self.layout_elements[child_idx].layout_config_index;
2907 let child_sizing = if x_axis {
2908 self.layout_configs[child_layout_idx].sizing.width
2909 } else {
2910 self.layout_configs[child_layout_idx].sizing.height
2911 };
2912 if child_sizing.type_ != SizingType::Grow
2913 || child_sizing.grow_weight <= 0.0
2914 {
2915 line_resizable_buffer.swap_remove(j);
2916 } else {
2917 j += 1;
2918 }
2919 }
2920
2921 let mut distribute = size_to_distribute;
2922 while distribute > EPSILON && !line_resizable_buffer.is_empty() {
2923 let mut total_weight = 0.0;
2924 let mut smallest_ratio = MAXFLOAT;
2925 let mut second_smallest_ratio = MAXFLOAT;
2926
2927 for &child_idx in &line_resizable_buffer {
2928 let child_layout_idx =
2929 self.layout_elements[child_idx as usize]
2930 .layout_config_index;
2931 let child_sizing = if x_axis {
2932 self.layout_configs[child_layout_idx].sizing.width
2933 } else {
2934 self.layout_configs[child_layout_idx].sizing.height
2935 };
2936
2937 total_weight += child_sizing.grow_weight;
2938
2939 let child_size = if x_axis {
2940 self.layout_elements[child_idx as usize].dimensions.width
2941 } else {
2942 self.layout_elements[child_idx as usize].dimensions.height
2943 };
2944 let child_ratio = child_size / child_sizing.grow_weight;
2945
2946 if float_equal(child_ratio, smallest_ratio) {
2947 continue;
2948 }
2949 if child_ratio < smallest_ratio {
2950 second_smallest_ratio = smallest_ratio;
2951 smallest_ratio = child_ratio;
2952 } else if child_ratio > smallest_ratio {
2953 second_smallest_ratio =
2954 f32::min(second_smallest_ratio, child_ratio);
2955 }
2956 }
2957
2958 if total_weight <= 0.0 {
2959 break;
2960 }
2961
2962 let per_weight_growth = distribute / total_weight;
2963 let ratio_step_cap = if second_smallest_ratio == MAXFLOAT {
2964 MAXFLOAT
2965 } else {
2966 second_smallest_ratio - smallest_ratio
2967 };
2968
2969 let mut resized_any = false;
2970
2971 let mut j = 0;
2972 while j < line_resizable_buffer.len() {
2973 let child_idx = line_resizable_buffer[j] as usize;
2974 let child_layout_idx =
2975 self.layout_elements[child_idx].layout_config_index;
2976 let child_sizing = if x_axis {
2977 self.layout_configs[child_layout_idx].sizing.width
2978 } else {
2979 self.layout_configs[child_layout_idx].sizing.height
2980 };
2981
2982 let child_size_ref = if x_axis {
2983 &mut self.layout_elements[child_idx].dimensions.width
2984 } else {
2985 &mut self.layout_elements[child_idx].dimensions.height
2986 };
2987
2988 let child_ratio =
2989 *child_size_ref / child_sizing.grow_weight;
2990 if !float_equal(child_ratio, smallest_ratio) {
2991 j += 1;
2992 continue;
2993 }
2994
2995 let max_size = if x_axis {
2996 self.layout_configs[child_layout_idx]
2997 .sizing
2998 .width
2999 .min_max
3000 .max
3001 } else {
3002 self.layout_configs[child_layout_idx]
3003 .sizing
3004 .height
3005 .min_max
3006 .max
3007 };
3008
3009 let mut growth_share =
3010 per_weight_growth * child_sizing.grow_weight;
3011 if ratio_step_cap != MAXFLOAT {
3012 growth_share = f32::min(
3013 growth_share,
3014 ratio_step_cap * child_sizing.grow_weight,
3015 );
3016 }
3017
3018 let previous = *child_size_ref;
3019 let proposed = previous + growth_share;
3020 if proposed >= max_size {
3021 *child_size_ref = max_size;
3022 resized_any = true;
3023 line_resizable_buffer.swap_remove(j);
3024 continue;
3025 }
3026
3027 *child_size_ref = proposed;
3028 distribute -= *child_size_ref - previous;
3029 resized_any = true;
3030 j += 1;
3031 }
3032
3033 if !resized_any {
3034 break;
3035 }
3036 }
3037 }
3038 }
3039 }
3040 } else if sizing_along_axis {
3041 let size_to_distribute = parent_size - parent_padding - inner_content_size;
3042
3043 if size_to_distribute < 0.0 {
3044 let parent_clips = if let Some(clip_idx) = self
3046 .find_element_config_index(parent_index, ElementConfigType::Clip)
3047 {
3048 let clip = &self.clip_element_configs[clip_idx];
3049 (x_axis && clip.horizontal) || (!x_axis && clip.vertical)
3050 } else {
3051 false
3052 };
3053 if parent_clips {
3054 continue;
3055 }
3056
3057 let mut distribute = size_to_distribute;
3059 while distribute < -EPSILON && !resizable_container_buffer.is_empty() {
3060 let mut largest: f32 = 0.0;
3061 let mut second_largest: f32 = 0.0;
3062 let mut width_to_add = distribute;
3063
3064 for &child_idx in &resizable_container_buffer {
3065 let cs = if x_axis {
3066 self.layout_elements[child_idx as usize].dimensions.width
3067 } else {
3068 self.layout_elements[child_idx as usize].dimensions.height
3069 };
3070 if float_equal(cs, largest) {
3071 continue;
3072 }
3073 if cs > largest {
3074 second_largest = largest;
3075 largest = cs;
3076 }
3077 if cs < largest {
3078 second_largest = f32::max(second_largest, cs);
3079 width_to_add = second_largest - largest;
3080 }
3081 }
3082 width_to_add = f32::max(
3083 width_to_add,
3084 distribute / resizable_container_buffer.len() as f32,
3085 );
3086
3087 let mut j = 0;
3088 while j < resizable_container_buffer.len() {
3089 let child_idx = resizable_container_buffer[j] as usize;
3090 let current_size = if x_axis {
3091 self.layout_elements[child_idx].dimensions.width
3092 } else {
3093 self.layout_elements[child_idx].dimensions.height
3094 };
3095 let min_size = if x_axis {
3096 self.layout_elements[child_idx].min_dimensions.width
3097 } else {
3098 self.layout_elements[child_idx].min_dimensions.height
3099 };
3100 if float_equal(current_size, largest) {
3101 let new_size = current_size + width_to_add;
3102 if new_size <= min_size {
3103 if x_axis {
3104 self.layout_elements[child_idx].dimensions.width = min_size;
3105 } else {
3106 self.layout_elements[child_idx].dimensions.height = min_size;
3107 }
3108 distribute -= min_size - current_size;
3109 resizable_container_buffer.swap_remove(j);
3110 continue;
3111 }
3112 if x_axis {
3113 self.layout_elements[child_idx].dimensions.width = new_size;
3114 } else {
3115 self.layout_elements[child_idx].dimensions.height = new_size;
3116 }
3117 distribute -= new_size - current_size;
3118 }
3119 j += 1;
3120 }
3121 }
3122 } else if size_to_distribute > 0.0 {
3123 if let Some(Some(single_along_axis_grow_child_idx)) =
3124 single_along_axis_grow_candidate
3125 {
3126 let child_layout_idx = self
3127 .layout_elements[single_along_axis_grow_child_idx]
3128 .layout_config_index;
3129 let child_max_size = if x_axis {
3130 self.layout_configs[child_layout_idx].sizing.width.min_max.max
3131 } else {
3132 self.layout_configs[child_layout_idx].sizing.height.min_max.max
3133 };
3134 let child_size_ref = if x_axis {
3135 &mut self.layout_elements[single_along_axis_grow_child_idx]
3136 .dimensions
3137 .width
3138 } else {
3139 &mut self.layout_elements[single_along_axis_grow_child_idx]
3140 .dimensions
3141 .height
3142 };
3143 *child_size_ref = f32::min(*child_size_ref + size_to_distribute, child_max_size);
3144 } else {
3145 let mut j = 0;
3147 while j < resizable_container_buffer.len() {
3148 let child_idx = resizable_container_buffer[j] as usize;
3149 let child_layout_idx =
3150 self.layout_elements[child_idx].layout_config_index;
3151 let child_sizing = if x_axis {
3152 self.layout_configs[child_layout_idx].sizing.width
3153 } else {
3154 self.layout_configs[child_layout_idx].sizing.height
3155 };
3156 if child_sizing.type_ != SizingType::Grow
3157 || child_sizing.grow_weight <= 0.0
3158 {
3159 resizable_container_buffer.swap_remove(j);
3160 } else {
3161 j += 1;
3162 }
3163 }
3164
3165 let mut distribute = size_to_distribute;
3166 while distribute > EPSILON && !resizable_container_buffer.is_empty() {
3167 let mut total_weight = 0.0;
3168 let mut smallest_ratio = MAXFLOAT;
3169 let mut second_smallest_ratio = MAXFLOAT;
3170
3171 for &child_idx in &resizable_container_buffer {
3172 let child_layout_idx =
3173 self.layout_elements[child_idx as usize].layout_config_index;
3174 let child_sizing = if x_axis {
3175 self.layout_configs[child_layout_idx].sizing.width
3176 } else {
3177 self.layout_configs[child_layout_idx].sizing.height
3178 };
3179
3180 total_weight += child_sizing.grow_weight;
3181
3182 let child_size = if x_axis {
3183 self.layout_elements[child_idx as usize].dimensions.width
3184 } else {
3185 self.layout_elements[child_idx as usize].dimensions.height
3186 };
3187 let child_ratio = child_size / child_sizing.grow_weight;
3188
3189 if float_equal(child_ratio, smallest_ratio) {
3190 continue;
3191 }
3192 if child_ratio < smallest_ratio {
3193 second_smallest_ratio = smallest_ratio;
3194 smallest_ratio = child_ratio;
3195 } else if child_ratio > smallest_ratio {
3196 second_smallest_ratio = f32::min(second_smallest_ratio, child_ratio);
3197 }
3198 }
3199
3200 if total_weight <= 0.0 {
3201 break;
3202 }
3203
3204 let per_weight_growth = distribute / total_weight;
3205
3206 let ratio_step_cap = if second_smallest_ratio == MAXFLOAT {
3207 MAXFLOAT
3208 } else {
3209 second_smallest_ratio - smallest_ratio
3210 };
3211
3212 let mut resized_any = false;
3213
3214 let mut j = 0;
3215 while j < resizable_container_buffer.len() {
3216 let child_idx = resizable_container_buffer[j] as usize;
3217 let child_layout_idx =
3218 self.layout_elements[child_idx].layout_config_index;
3219 let child_sizing = if x_axis {
3220 self.layout_configs[child_layout_idx].sizing.width
3221 } else {
3222 self.layout_configs[child_layout_idx].sizing.height
3223 };
3224
3225 let child_size_ref = if x_axis {
3226 &mut self.layout_elements[child_idx].dimensions.width
3227 } else {
3228 &mut self.layout_elements[child_idx].dimensions.height
3229 };
3230
3231 let child_ratio = *child_size_ref / child_sizing.grow_weight;
3232 if !float_equal(child_ratio, smallest_ratio) {
3233 j += 1;
3234 continue;
3235 }
3236
3237 let max_size = if x_axis {
3238 self.layout_configs[child_layout_idx]
3239 .sizing
3240 .width
3241 .min_max
3242 .max
3243 } else {
3244 self.layout_configs[child_layout_idx]
3245 .sizing
3246 .height
3247 .min_max
3248 .max
3249 };
3250
3251 let mut growth_share = per_weight_growth * child_sizing.grow_weight;
3252 if ratio_step_cap != MAXFLOAT {
3253 growth_share =
3254 f32::min(growth_share, ratio_step_cap * child_sizing.grow_weight);
3255 }
3256
3257 let previous = *child_size_ref;
3258 let proposed = previous + growth_share;
3259 if proposed >= max_size {
3260 *child_size_ref = max_size;
3261 resized_any = true;
3262 resizable_container_buffer.swap_remove(j);
3263 continue;
3264 }
3265
3266 *child_size_ref = proposed;
3267 distribute -= *child_size_ref - previous;
3268 resized_any = true;
3269 j += 1;
3270 }
3271
3272 if !resized_any {
3273 break;
3274 }
3275 }
3276 }
3277 }
3278 } else {
3279 for &child_idx in &resizable_container_buffer {
3281 let child_idx = child_idx as usize;
3282 let child_layout_idx =
3283 self.layout_elements[child_idx].layout_config_index;
3284 let child_sizing = if x_axis {
3285 self.layout_configs[child_layout_idx].sizing.width
3286 } else {
3287 self.layout_configs[child_layout_idx].sizing.height
3288 };
3289 let min_size = if x_axis {
3290 self.layout_elements[child_idx].min_dimensions.width
3291 } else {
3292 self.layout_elements[child_idx].min_dimensions.height
3293 };
3294
3295 let mut max_size = parent_size - parent_padding;
3296 if let Some(clip_idx) =
3297 self.find_element_config_index(parent_index, ElementConfigType::Clip)
3298 {
3299 let clip = &self.clip_element_configs[clip_idx];
3300 if (x_axis && clip.horizontal) || (!x_axis && clip.vertical) {
3301 max_size = f32::max(max_size, inner_content_size);
3302 }
3303 }
3304
3305 let is_cover_aspect = self
3306 .find_element_config_index(child_idx, ElementConfigType::Aspect)
3307 .map(|cfg_idx| self.aspect_ratio_cover_configs[cfg_idx])
3308 .unwrap_or(false);
3309
3310 let child_size_ref = if x_axis {
3311 &mut self.layout_elements[child_idx].dimensions.width
3312 } else {
3313 &mut self.layout_elements[child_idx].dimensions.height
3314 };
3315
3316 if child_sizing.type_ == SizingType::Grow && child_sizing.grow_weight > 0.0 {
3317 if is_cover_aspect {
3318 *child_size_ref = f32::max(*child_size_ref, max_size);
3319 } else {
3320 *child_size_ref = f32::min(max_size, child_sizing.min_max.max);
3321 }
3322 }
3323
3324 if is_cover_aspect {
3325 *child_size_ref = f32::max(min_size, *child_size_ref);
3326 } else {
3327 *child_size_ref = f32::max(min_size, f32::min(*child_size_ref, max_size));
3328 }
3329 }
3330 }
3331 }
3332 }
3333 }
3334
3335 fn calculate_final_layout(&mut self) {
3336 self.size_containers_along_axis(true);
3338
3339 self.wrap_text();
3341
3342 for i in 0..self.aspect_ratio_element_indexes.len() {
3344 let elem_idx = self.aspect_ratio_element_indexes[i] as usize;
3345 if let Some(cfg_idx) =
3346 self.find_element_config_index(elem_idx, ElementConfigType::Aspect)
3347 {
3348 let aspect_ratio = self.aspect_ratio_configs[cfg_idx];
3349 let is_cover = self.aspect_ratio_cover_configs[cfg_idx];
3350 let new_height =
3351 (1.0 / aspect_ratio) * self.layout_elements[elem_idx].dimensions.width;
3352 self.layout_elements[elem_idx].dimensions.height = new_height;
3353 let layout_idx = self.layout_elements[elem_idx].layout_config_index;
3354 self.layout_configs[layout_idx].sizing.height.min_max.min = new_height;
3355 self.layout_configs[layout_idx].sizing.height.min_max.max = if is_cover {
3356 MAXFLOAT
3357 } else {
3358 new_height
3359 };
3360 }
3361 }
3362
3363 self.propagate_sizes_up_tree();
3365
3366 self.size_containers_along_axis(false);
3368
3369 for i in 0..self.aspect_ratio_element_indexes.len() {
3371 let elem_idx = self.aspect_ratio_element_indexes[i] as usize;
3372 if let Some(cfg_idx) =
3373 self.find_element_config_index(elem_idx, ElementConfigType::Aspect)
3374 {
3375 let aspect_ratio = self.aspect_ratio_configs[cfg_idx];
3376 let is_cover = self.aspect_ratio_cover_configs[cfg_idx];
3377 let new_width =
3378 aspect_ratio * self.layout_elements[elem_idx].dimensions.height;
3379 self.layout_elements[elem_idx].dimensions.width = new_width;
3380 let layout_idx = self.layout_elements[elem_idx].layout_config_index;
3381 self.layout_configs[layout_idx].sizing.width.min_max.min = new_width;
3382 self.layout_configs[layout_idx].sizing.width.min_max.max = if is_cover {
3383 MAXFLOAT
3384 } else {
3385 new_width
3386 };
3387 }
3388 }
3389
3390 let mut sort_max = self.layout_element_tree_roots.len().saturating_sub(1);
3392 while sort_max > 0 {
3393 for i in 0..sort_max {
3394 if self.layout_element_tree_roots[i + 1].z_index
3395 < self.layout_element_tree_roots[i].z_index
3396 {
3397 self.layout_element_tree_roots.swap(i, i + 1);
3398 }
3399 }
3400 sort_max -= 1;
3401 }
3402
3403 self.generate_render_commands();
3405 }
3406
3407 fn wrap_text(&mut self) {
3408 for text_idx in 0..self.text_element_data.len() {
3409 let elem_index = self.text_element_data[text_idx].element_index as usize;
3410 let text = self.text_element_data[text_idx].text.clone();
3411 let preferred_dims = self.text_element_data[text_idx].preferred_dimensions;
3412
3413 self.text_element_data[text_idx].wrapped_lines_start = self.wrapped_text_lines.len();
3414 self.text_element_data[text_idx].wrapped_lines_length = 0;
3415
3416 let container_width = self.layout_elements[elem_index].dimensions.width;
3417
3418 let text_config_idx = self
3420 .find_element_config_index(elem_index, ElementConfigType::Text)
3421 .unwrap_or(0);
3422 let text_config = self.text_element_configs[text_config_idx].clone();
3423
3424 let measured = self.measure_text_cached(&text, &text_config);
3425
3426 let line_height = if text_config.line_height > 0 {
3427 text_config.line_height as f32
3428 } else {
3429 preferred_dims.height
3430 };
3431
3432 if !measured.contains_newlines && preferred_dims.width <= container_width {
3433 self.wrapped_text_lines.push(WrappedTextLine {
3435 dimensions: self.layout_elements[elem_index].dimensions,
3436 start: 0,
3437 length: text.len(),
3438 });
3439 self.text_element_data[text_idx].wrapped_lines_length = 1;
3440 continue;
3441 }
3442
3443 let measure_fn = self.measure_text_fn.as_ref().unwrap();
3445 let space_width = {
3446 let space_config = text_config.clone();
3447 measure_fn(" ", &space_config).width
3448 };
3449
3450 let mut word_index = measured.measured_words_start_index;
3451 let mut line_width: f32 = 0.0;
3452 let mut line_length_chars: i32 = 0;
3453 let mut line_start_offset: i32 = 0;
3454
3455 while word_index != -1 {
3456 let measured_word = self.measured_words[word_index as usize];
3457
3458 if line_length_chars == 0 && line_width + measured_word.width > container_width {
3460 self.wrapped_text_lines.push(WrappedTextLine {
3461 dimensions: Dimensions::new(measured_word.width, line_height),
3462 start: measured_word.start_offset as usize,
3463 length: measured_word.length as usize,
3464 });
3465 self.text_element_data[text_idx].wrapped_lines_length += 1;
3466 word_index = measured_word.next;
3467 line_start_offset = measured_word.start_offset + measured_word.length;
3468 }
3469 else if measured_word.length == 0
3471 || line_width + measured_word.width > container_width
3472 {
3473 let text_bytes = text.as_bytes();
3474 let final_char_idx = (line_start_offset + line_length_chars - 1).max(0) as usize;
3475 let final_char_is_space =
3476 final_char_idx < text_bytes.len() && text_bytes[final_char_idx] == b' ';
3477 let adj_width = line_width
3478 + if final_char_is_space {
3479 -space_width
3480 } else {
3481 0.0
3482 };
3483 let adj_length = line_length_chars
3484 + if final_char_is_space { -1 } else { 0 };
3485
3486 self.wrapped_text_lines.push(WrappedTextLine {
3487 dimensions: Dimensions::new(adj_width, line_height),
3488 start: line_start_offset as usize,
3489 length: adj_length as usize,
3490 });
3491 self.text_element_data[text_idx].wrapped_lines_length += 1;
3492
3493 if line_length_chars == 0 || measured_word.length == 0 {
3494 word_index = measured_word.next;
3495 }
3496 line_width = 0.0;
3497 line_length_chars = 0;
3498 line_start_offset = measured_word.start_offset;
3499 } else {
3500 line_width += measured_word.width + text_config.letter_spacing as f32;
3501 line_length_chars += measured_word.length;
3502 word_index = measured_word.next;
3503 }
3504 }
3505
3506 if line_length_chars > 0 {
3507 self.wrapped_text_lines.push(WrappedTextLine {
3508 dimensions: Dimensions::new(
3509 line_width - text_config.letter_spacing as f32,
3510 line_height,
3511 ),
3512 start: line_start_offset as usize,
3513 length: line_length_chars as usize,
3514 });
3515 self.text_element_data[text_idx].wrapped_lines_length += 1;
3516 }
3517
3518 let num_lines = self.text_element_data[text_idx].wrapped_lines_length;
3519 self.layout_elements[elem_index].dimensions.height =
3520 line_height * num_lines as f32;
3521 }
3522 }
3523
3524 fn propagate_sizes_up_tree(&mut self) {
3525 let mut dfs_buffer: Vec<i32> = Vec::new();
3526 let mut visited: Vec<bool> = Vec::new();
3527
3528 for i in 0..self.layout_element_tree_roots.len() {
3529 let root = self.layout_element_tree_roots[i];
3530 dfs_buffer.push(root.layout_element_index);
3531 visited.push(false);
3532 }
3533
3534 while !dfs_buffer.is_empty() {
3535 let buf_idx = dfs_buffer.len() - 1;
3536 let current_elem_idx = dfs_buffer[buf_idx] as usize;
3537
3538 if !visited[buf_idx] {
3539 visited[buf_idx] = true;
3540 let is_text =
3541 self.element_has_config(current_elem_idx, ElementConfigType::Text);
3542 let children_length = self.layout_elements[current_elem_idx].children_length;
3543 if is_text || children_length == 0 {
3544 dfs_buffer.pop();
3545 visited.pop();
3546 continue;
3547 }
3548 let children_start = self.layout_elements[current_elem_idx].children_start;
3549 for j in 0..children_length as usize {
3550 let child_idx = self.layout_element_children[children_start + j];
3551 dfs_buffer.push(child_idx);
3552 visited.push(false);
3553 }
3554 continue;
3555 }
3556
3557 dfs_buffer.pop();
3558 visited.pop();
3559
3560 let layout_idx = self.layout_elements[current_elem_idx].layout_config_index;
3561 let layout_config = self.layout_configs[layout_idx];
3562 let children_start = self.layout_elements[current_elem_idx].children_start;
3563 let children_length = self.layout_elements[current_elem_idx].children_length;
3564
3565 if layout_config.layout_direction == LayoutDirection::LeftToRight {
3566 if layout_config.wrap {
3567 let lines = self.compute_wrapped_lines(current_elem_idx, true);
3568 let mut content_height =
3569 layout_config.padding.top as f32 + layout_config.padding.bottom as f32;
3570 if !lines.is_empty() {
3571 content_height +=
3572 lines.iter().map(|line| line.cross_size).sum::<f32>();
3573 content_height +=
3574 lines.len().saturating_sub(1) as f32 * layout_config.wrap_gap as f32;
3575 }
3576 self.layout_elements[current_elem_idx].dimensions.height = f32::min(
3577 f32::max(content_height, layout_config.sizing.height.min_max.min),
3578 layout_config.sizing.height.min_max.max,
3579 );
3580 } else {
3581 for j in 0..children_length as usize {
3582 let child_idx =
3583 self.layout_element_children[children_start + j] as usize;
3584 let child_height_with_padding = f32::max(
3585 self.layout_elements[child_idx].dimensions.height
3586 + layout_config.padding.top as f32
3587 + layout_config.padding.bottom as f32,
3588 self.layout_elements[current_elem_idx].dimensions.height,
3589 );
3590 self.layout_elements[current_elem_idx].dimensions.height = f32::min(
3591 f32::max(
3592 child_height_with_padding,
3593 layout_config.sizing.height.min_max.min,
3594 ),
3595 layout_config.sizing.height.min_max.max,
3596 );
3597 }
3598 }
3599 } else if layout_config.wrap {
3600 let lines = self.compute_wrapped_lines(current_elem_idx, false);
3601
3602 let max_column_height = lines
3603 .iter()
3604 .fold(0.0_f32, |acc, line| acc.max(line.main_size));
3605 let content_height = layout_config.padding.top as f32
3606 + layout_config.padding.bottom as f32
3607 + max_column_height;
3608
3609 self.layout_elements[current_elem_idx].dimensions.height = f32::min(
3610 f32::max(content_height, layout_config.sizing.height.min_max.min),
3611 layout_config.sizing.height.min_max.max,
3612 );
3613
3614 if layout_config.sizing.width.type_ != SizingType::Percent {
3615 let mut content_width =
3616 layout_config.padding.left as f32 + layout_config.padding.right as f32;
3617 if !lines.is_empty() {
3618 content_width += lines.iter().map(|line| line.cross_size).sum::<f32>();
3619 content_width +=
3620 lines.len().saturating_sub(1) as f32 * layout_config.wrap_gap as f32;
3621 }
3622
3623 self.layout_elements[current_elem_idx].dimensions.width = f32::min(
3624 f32::max(content_width, layout_config.sizing.width.min_max.min),
3625 layout_config.sizing.width.min_max.max,
3626 );
3627 }
3628 } else {
3629 let mut content_height = layout_config.padding.top as f32
3630 + layout_config.padding.bottom as f32;
3631 for j in 0..children_length as usize {
3632 let child_idx =
3633 self.layout_element_children[children_start + j] as usize;
3634 content_height += self.layout_elements[child_idx].dimensions.height;
3635 }
3636 content_height += children_length.saturating_sub(1) as f32
3637 * layout_config.child_gap as f32;
3638 self.layout_elements[current_elem_idx].dimensions.height = f32::min(
3639 f32::max(content_height, layout_config.sizing.height.min_max.min),
3640 layout_config.sizing.height.min_max.max,
3641 );
3642 }
3643 }
3644 }
3645
3646 fn element_is_offscreen(&self, bbox: &BoundingBox) -> bool {
3647 if self.culling_disabled {
3648 return false;
3649 }
3650 bbox.x > self.layout_dimensions.width
3651 || bbox.y > self.layout_dimensions.height
3652 || bbox.x + bbox.width < 0.0
3653 || bbox.y + bbox.height < 0.0
3654 }
3655
3656 fn add_render_command(&mut self, cmd: InternalRenderCommand<CustomElementData>) {
3657 self.render_commands.push(cmd);
3658 }
3659
3660 fn generate_render_commands(&mut self) {
3661 self.render_commands.clear();
3662 let mut dfs_buffer: Vec<LayoutElementTreeNode> = Vec::new();
3663 let mut visited: Vec<bool> = Vec::new();
3664
3665 for root_index in 0..self.layout_element_tree_roots.len() {
3666 dfs_buffer.clear();
3667 visited.clear();
3668 let root = self.layout_element_tree_roots[root_index];
3669 let root_elem_idx = root.layout_element_index as usize;
3670 let root_element = &self.layout_elements[root_elem_idx];
3671 let mut root_position = Vector2::default();
3672
3673 if self.element_has_config(root_elem_idx, ElementConfigType::Floating) {
3675 if let Some(parent_item) = self.layout_element_map.get(&root.parent_id) {
3676 let parent_bbox = parent_item.bounding_box;
3677 if let Some(float_cfg_idx) = self
3678 .find_element_config_index(root_elem_idx, ElementConfigType::Floating)
3679 {
3680 let config = &self.floating_element_configs[float_cfg_idx];
3681 let root_dims = root_element.dimensions;
3682 let mut target = Vector2::default();
3683
3684 match config.attach_points.parent_x {
3686 AlignX::Left => {
3687 target.x = parent_bbox.x;
3688 }
3689 AlignX::CenterX => {
3690 target.x = parent_bbox.x + parent_bbox.width / 2.0;
3691 }
3692 AlignX::Right => {
3693 target.x = parent_bbox.x + parent_bbox.width;
3694 }
3695 }
3696 match config.attach_points.element_x {
3698 AlignX::Left => {}
3699 AlignX::CenterX => {
3700 target.x -= root_dims.width / 2.0;
3701 }
3702 AlignX::Right => {
3703 target.x -= root_dims.width;
3704 }
3705 }
3706 match config.attach_points.parent_y {
3708 AlignY::Top => {
3709 target.y = parent_bbox.y;
3710 }
3711 AlignY::CenterY => {
3712 target.y = parent_bbox.y + parent_bbox.height / 2.0;
3713 }
3714 AlignY::Bottom => {
3715 target.y = parent_bbox.y + parent_bbox.height;
3716 }
3717 }
3718 match config.attach_points.element_y {
3720 AlignY::Top => {}
3721 AlignY::CenterY => {
3722 target.y -= root_dims.height / 2.0;
3723 }
3724 AlignY::Bottom => {
3725 target.y -= root_dims.height;
3726 }
3727 }
3728 target.x += config.offset.x;
3729 target.y += config.offset.y;
3730 root_position = target;
3731 }
3732 }
3733 }
3734
3735 if root.clip_element_id != 0 {
3737 if let Some(clip_item) = self.layout_element_map.get(&root.clip_element_id) {
3738 let clip_bbox = clip_item.bounding_box;
3739 self.add_render_command(InternalRenderCommand {
3740 bounding_box: clip_bbox,
3741 command_type: RenderCommandType::ScissorStart,
3742 id: hash_number(
3743 root_element.id,
3744 root_element.children_length as u32 + 10,
3745 )
3746 .id,
3747 z_index: root.z_index,
3748 ..Default::default()
3749 });
3750 }
3751 }
3752
3753 let root_layout_idx = self.layout_elements[root_elem_idx].layout_config_index;
3754 let root_padding_left = self.layout_configs[root_layout_idx].padding.left as f32;
3755 let root_padding_top = self.layout_configs[root_layout_idx].padding.top as f32;
3756
3757 dfs_buffer.push(LayoutElementTreeNode {
3758 layout_element_index: root.layout_element_index,
3759 position: root_position,
3760 next_child_offset: Vector2::new(root_padding_left, root_padding_top),
3761 });
3762 visited.push(false);
3763
3764 while !dfs_buffer.is_empty() {
3765 let buf_idx = dfs_buffer.len() - 1;
3766 let current_node = dfs_buffer[buf_idx];
3767 let current_elem_idx = current_node.layout_element_index as usize;
3768 let layout_idx = self.layout_elements[current_elem_idx].layout_config_index;
3769 let layout_config = self.layout_configs[layout_idx];
3770 let mut scroll_offset = Vector2::default();
3771
3772 if !visited[buf_idx] {
3773 visited[buf_idx] = true;
3774
3775 let current_bbox = BoundingBox::new(
3776 current_node.position.x,
3777 current_node.position.y,
3778 self.layout_elements[current_elem_idx].dimensions.width,
3779 self.layout_elements[current_elem_idx].dimensions.height,
3780 );
3781
3782 let mut _scroll_container_data_idx: Option<usize> = None;
3784 if self.element_has_config(current_elem_idx, ElementConfigType::Clip) {
3785 if let Some(clip_cfg_idx) = self
3786 .find_element_config_index(current_elem_idx, ElementConfigType::Clip)
3787 {
3788 let clip_config = self.clip_element_configs[clip_cfg_idx];
3789 for si in 0..self.scroll_container_datas.len() {
3790 if self.scroll_container_datas[si].layout_element_index
3791 == current_elem_idx as i32
3792 {
3793 _scroll_container_data_idx = Some(si);
3794 self.scroll_container_datas[si].bounding_box = current_bbox;
3795 scroll_offset = clip_config.child_offset;
3796 break;
3797 }
3798 }
3799 }
3800 }
3801
3802 let elem_id = self.layout_elements[current_elem_idx].id;
3804 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
3805 item.bounding_box = current_bbox;
3806 }
3807
3808 let shared_config = self
3810 .find_element_config_index(current_elem_idx, ElementConfigType::Shared)
3811 .map(|idx| self.shared_element_configs[idx]);
3812 let shared = shared_config.unwrap_or_default();
3813 let mut emit_rectangle = shared.background_color.a > 0.0;
3814 let offscreen = self.element_is_offscreen(¤t_bbox);
3815 let should_render_base = !offscreen;
3816
3817 let elem_effects = self.element_effects.get(current_elem_idx).cloned().unwrap_or_default();
3819
3820 let elem_visual_rotation = self.element_visual_rotations.get(current_elem_idx).cloned().flatten();
3822 let elem_visual_rotation = elem_visual_rotation.filter(|vr| !vr.is_noop());
3824
3825 let elem_shape_rotation = self.element_shape_rotations.get(current_elem_idx).cloned().flatten()
3827 .filter(|sr| !sr.is_noop());
3828 let shape_draw_bbox = if let Some(ref _sr) = elem_shape_rotation {
3831 if let Some(orig_dims) = self.element_pre_rotation_dimensions.get(current_elem_idx).copied().flatten() {
3832 let offset_x = (current_bbox.width - orig_dims.width) / 2.0;
3833 let offset_y = (current_bbox.height - orig_dims.height) / 2.0;
3834 BoundingBox::new(
3835 current_bbox.x + offset_x,
3836 current_bbox.y + offset_y,
3837 orig_dims.width,
3838 orig_dims.height,
3839 )
3840 } else {
3841 current_bbox
3842 }
3843 } else {
3844 current_bbox
3845 };
3846
3847 let elem_shaders = self.element_shaders.get(current_elem_idx).cloned().unwrap_or_default();
3851
3852 if !elem_shaders.is_empty() {
3853 for (i, shader) in elem_shaders.iter().rev().enumerate() {
3855 let vr = if i == 0 { elem_visual_rotation } else { None };
3857 self.add_render_command(InternalRenderCommand {
3858 bounding_box: current_bbox,
3859 command_type: RenderCommandType::GroupBegin,
3860 effects: vec![shader.clone()],
3861 id: elem_id,
3862 z_index: root.z_index,
3863 visual_rotation: vr,
3864 ..Default::default()
3865 });
3866 }
3867 } else if let Some(vr) = elem_visual_rotation {
3868 self.add_render_command(InternalRenderCommand {
3870 bounding_box: current_bbox,
3871 command_type: RenderCommandType::GroupBegin,
3872 effects: vec![],
3873 id: elem_id,
3874 z_index: root.z_index,
3875 visual_rotation: Some(vr),
3876 ..Default::default()
3877 });
3878 }
3879
3880 let configs_start = self.layout_elements[current_elem_idx].element_configs.start;
3882 let configs_length =
3883 self.layout_elements[current_elem_idx].element_configs.length;
3884
3885 for cfg_i in 0..configs_length {
3886 let config = self.element_configs[configs_start + cfg_i as usize];
3887 let should_render = should_render_base;
3888
3889 match config.config_type {
3890 ElementConfigType::Shared
3891 | ElementConfigType::Aspect
3892 | ElementConfigType::Floating
3893 | ElementConfigType::Border => {}
3894 ElementConfigType::Clip => {
3895 let clip = &self.clip_element_configs[config.config_index];
3896 self.add_render_command(InternalRenderCommand {
3897 bounding_box: current_bbox,
3898 command_type: RenderCommandType::ScissorStart,
3899 render_data: InternalRenderData::Clip {
3900 horizontal: clip.horizontal,
3901 vertical: clip.vertical,
3902 },
3903 user_data: 0,
3904 id: elem_id,
3905 z_index: root.z_index,
3906 visual_rotation: None,
3907 shape_rotation: None,
3908 effects: Vec::new(),
3909 });
3910 }
3911 ElementConfigType::Image => {
3912 if should_render {
3913 let image_data =
3914 self.image_element_configs[config.config_index].clone();
3915 self.add_render_command(InternalRenderCommand {
3916 bounding_box: shape_draw_bbox,
3917 command_type: RenderCommandType::Image,
3918 render_data: InternalRenderData::Image {
3919 background_color: shared.background_color,
3920 corner_radius: shared.corner_radius,
3921 image_data,
3922 },
3923 user_data: shared.user_data,
3924 id: elem_id,
3925 z_index: root.z_index,
3926 visual_rotation: None,
3927 shape_rotation: elem_shape_rotation,
3928 effects: elem_effects.clone(),
3929 });
3930 }
3931 emit_rectangle = false;
3932 }
3933 ElementConfigType::Text => {
3934 if !should_render {
3935 continue;
3936 }
3937 let text_config =
3938 self.text_element_configs[config.config_index].clone();
3939 let text_data_idx =
3940 self.layout_elements[current_elem_idx].text_data_index;
3941 if text_data_idx < 0 {
3942 continue;
3943 }
3944 let text_data = &self.text_element_data[text_data_idx as usize];
3945 let natural_line_height = text_data.preferred_dimensions.height;
3946 let final_line_height = if text_config.line_height > 0 {
3947 text_config.line_height as f32
3948 } else {
3949 natural_line_height
3950 };
3951 let line_height_offset =
3952 (final_line_height - natural_line_height) / 2.0;
3953 let mut y_position = line_height_offset;
3954
3955 let lines_start = text_data.wrapped_lines_start;
3956 let lines_length = text_data.wrapped_lines_length;
3957 let parent_text = text_data.text.clone();
3958
3959 let lines_data: Vec<_> = (0..lines_length)
3961 .map(|li| {
3962 let line = &self.wrapped_text_lines[lines_start + li as usize];
3963 (line.start, line.length, line.dimensions)
3964 })
3965 .collect();
3966
3967 for (line_index, &(start, length, line_dims)) in lines_data.iter().enumerate() {
3968 if length == 0 {
3969 y_position += final_line_height;
3970 continue;
3971 }
3972
3973 let line_text = parent_text[start..start + length].to_string();
3974
3975 let align_width = if buf_idx > 0 {
3976 let parent_node = dfs_buffer[buf_idx - 1];
3977 let parent_elem_idx =
3978 parent_node.layout_element_index as usize;
3979 let parent_layout_idx = self.layout_elements
3980 [parent_elem_idx]
3981 .layout_config_index;
3982 let pp = self.layout_configs[parent_layout_idx].padding;
3983 self.layout_elements[parent_elem_idx].dimensions.width
3984 - pp.left as f32
3985 - pp.right as f32
3986 } else {
3987 current_bbox.width
3988 };
3989
3990 let mut offset = align_width - line_dims.width;
3991 if text_config.alignment == AlignX::Left {
3992 offset = 0.0;
3993 }
3994 if text_config.alignment == AlignX::CenterX {
3995 offset /= 2.0;
3996 }
3997
3998 self.add_render_command(InternalRenderCommand {
3999 bounding_box: BoundingBox::new(
4000 current_bbox.x + offset,
4001 current_bbox.y + y_position,
4002 line_dims.width,
4003 line_dims.height,
4004 ),
4005 command_type: RenderCommandType::Text,
4006 render_data: InternalRenderData::Text {
4007 text: line_text,
4008 text_color: text_config.color,
4009 font_size: text_config.font_size,
4010 letter_spacing: text_config.letter_spacing,
4011 line_height: text_config.line_height,
4012 font_asset: text_config.font_asset,
4013 },
4014 user_data: text_config.user_data,
4015 id: hash_number(line_index as u32, elem_id).id,
4016 z_index: root.z_index,
4017 visual_rotation: None,
4018 shape_rotation: None,
4019 effects: text_config.effects.clone(),
4020 });
4021 y_position += final_line_height;
4022 }
4023 }
4024 ElementConfigType::Custom => {
4025 if should_render {
4026 let custom_data =
4027 self.custom_element_configs[config.config_index].clone();
4028 self.add_render_command(InternalRenderCommand {
4029 bounding_box: shape_draw_bbox,
4030 command_type: RenderCommandType::Custom,
4031 render_data: InternalRenderData::Custom {
4032 background_color: shared.background_color,
4033 corner_radius: shared.corner_radius,
4034 custom_data,
4035 },
4036 user_data: shared.user_data,
4037 id: elem_id,
4038 z_index: root.z_index,
4039 visual_rotation: None,
4040 shape_rotation: elem_shape_rotation,
4041 effects: elem_effects.clone(),
4042 });
4043 }
4044 emit_rectangle = false;
4045 }
4046 ElementConfigType::TextInput => {
4047 if should_render {
4048 let ti_config = self.text_input_configs[config.config_index].clone();
4049 let is_focused = self.focused_element_id == elem_id;
4050
4051 if shared.background_color.a > 0.0 || !shared.corner_radius.is_zero() {
4053 self.add_render_command(InternalRenderCommand {
4054 bounding_box: shape_draw_bbox,
4055 command_type: RenderCommandType::Rectangle,
4056 render_data: InternalRenderData::Rectangle {
4057 background_color: shared.background_color,
4058 corner_radius: shared.corner_radius,
4059 },
4060 user_data: shared.user_data,
4061 id: elem_id,
4062 z_index: root.z_index,
4063 visual_rotation: None,
4064 shape_rotation: elem_shape_rotation,
4065 effects: elem_effects.clone(),
4066 });
4067 }
4068
4069 let state = self.text_edit_states
4071 .entry(elem_id)
4072 .or_insert_with(crate::text_input::TextEditState::default)
4073 .clone();
4074
4075 let disp_text = crate::text_input::display_text(
4076 &state.text,
4077 &ti_config.placeholder,
4078 ti_config.is_password && !ti_config.is_multiline,
4079 );
4080
4081 let is_placeholder = state.text.is_empty();
4082 let text_color = if is_placeholder {
4083 ti_config.placeholder_color
4084 } else {
4085 ti_config.text_color
4086 };
4087 let mut content_width = 0.0_f32;
4088 let mut content_height = 0.0_f32;
4089 let mut scroll_pos_x = state.scroll_offset;
4090 let mut scroll_pos_y = state.scroll_offset_y;
4091
4092 let natural_font_height = self.font_height(ti_config.font_asset, ti_config.font_size);
4094 let line_step = if ti_config.line_height > 0 {
4095 ti_config.line_height as f32
4096 } else {
4097 natural_font_height
4098 };
4099 let line_y_offset = (line_step - natural_font_height) / 2.0;
4101
4102 self.add_render_command(InternalRenderCommand {
4104 bounding_box: current_bbox,
4105 command_type: RenderCommandType::ScissorStart,
4106 render_data: InternalRenderData::Clip {
4107 horizontal: true,
4108 vertical: true,
4109 },
4110 user_data: 0,
4111 id: hash_number(1000, elem_id).id,
4112 z_index: root.z_index,
4113 visual_rotation: None,
4114 shape_rotation: None,
4115 effects: Vec::new(),
4116 });
4117
4118 if ti_config.is_multiline {
4119 let scroll_offset_x = state.scroll_offset;
4121 let scroll_offset_y = state.scroll_offset_y;
4122
4123 let visual_lines = if let Some(ref measure_fn) = self.measure_text_fn {
4124 crate::text_input::wrap_lines(
4125 &disp_text,
4126 current_bbox.width,
4127 ti_config.font_asset,
4128 ti_config.font_size,
4129 measure_fn.as_ref(),
4130 )
4131 } else {
4132 vec![crate::text_input::VisualLine {
4133 text: disp_text.clone(),
4134 global_char_start: 0,
4135 char_count: disp_text.chars().count(),
4136 }]
4137 };
4138
4139 let (cursor_line, cursor_col) = if is_placeholder {
4140 (0, 0)
4141 } else {
4142 #[cfg(feature = "text-styling")]
4143 let raw_cursor = state.cursor_pos_raw();
4144 #[cfg(not(feature = "text-styling"))]
4145 let raw_cursor = state.cursor_pos;
4146 crate::text_input::cursor_to_visual_pos(&visual_lines, raw_cursor)
4147 };
4148
4149 let line_positions: Vec<Vec<f32>> = if let Some(ref measure_fn) = self.measure_text_fn {
4151 visual_lines.iter().map(|vl| {
4152 crate::text_input::compute_char_x_positions(
4153 &vl.text,
4154 ti_config.font_asset,
4155 ti_config.font_size,
4156 measure_fn.as_ref(),
4157 )
4158 }).collect()
4159 } else {
4160 visual_lines.iter().map(|_| vec![0.0]).collect()
4161 };
4162 content_width = line_positions.iter()
4163 .filter_map(|p| p.last().copied())
4164 .fold(0.0_f32, |a, b| a.max(b));
4165 content_height = visual_lines.len() as f32 * line_step;
4166 scroll_pos_x = scroll_offset_x;
4167 scroll_pos_y = scroll_offset_y;
4168
4169 if is_focused {
4171 #[cfg(feature = "text-styling")]
4172 let sel_range = state.selection_range_raw();
4173 #[cfg(not(feature = "text-styling"))]
4174 let sel_range = state.selection_range();
4175 if let Some((sel_start, sel_end)) = sel_range {
4176 let (sel_start_line, sel_start_col) = crate::text_input::cursor_to_visual_pos(&visual_lines, sel_start);
4177 let (sel_end_line, sel_end_col) = crate::text_input::cursor_to_visual_pos(&visual_lines, sel_end);
4178 for (line_idx, vl) in visual_lines.iter().enumerate() {
4179 if line_idx < sel_start_line || line_idx > sel_end_line {
4180 continue;
4181 }
4182 let positions = &line_positions[line_idx];
4183 let col_start = if line_idx == sel_start_line { sel_start_col } else { 0 };
4184 let col_end = if line_idx == sel_end_line { sel_end_col } else { vl.char_count };
4185 let x_start = positions.get(col_start).copied().unwrap_or(0.0);
4186 let x_end = positions.get(col_end).copied().unwrap_or(
4187 positions.last().copied().unwrap_or(0.0)
4188 );
4189 let sel_width = x_end - x_start;
4190 if sel_width > 0.0 {
4191 let sel_y = current_bbox.y + line_idx as f32 * line_step - scroll_offset_y;
4192 self.add_render_command(InternalRenderCommand {
4193 bounding_box: BoundingBox::new(
4194 current_bbox.x - scroll_offset_x + x_start,
4195 sel_y,
4196 sel_width,
4197 line_step,
4198 ),
4199 command_type: RenderCommandType::Rectangle,
4200 render_data: InternalRenderData::Rectangle {
4201 background_color: ti_config.selection_color,
4202 corner_radius: CornerRadius::default(),
4203 },
4204 user_data: 0,
4205 id: hash_number(1001 + line_idx as u32, elem_id).id,
4206 z_index: root.z_index,
4207 visual_rotation: None,
4208 shape_rotation: None,
4209 effects: Vec::new(),
4210 });
4211 }
4212 }
4213 }
4214 }
4215
4216 for (line_idx, vl) in visual_lines.iter().enumerate() {
4218 if !vl.text.is_empty() {
4219 let positions = &line_positions[line_idx];
4220 let text_width = positions.last().copied().unwrap_or(0.0);
4221 let line_y = current_bbox.y + line_idx as f32 * line_step + line_y_offset - scroll_offset_y;
4222 self.add_render_command(InternalRenderCommand {
4223 bounding_box: BoundingBox::new(
4224 current_bbox.x - scroll_offset_x,
4225 line_y,
4226 text_width,
4227 natural_font_height,
4228 ),
4229 command_type: RenderCommandType::Text,
4230 render_data: InternalRenderData::Text {
4231 text: vl.text.clone(),
4232 text_color,
4233 font_size: ti_config.font_size,
4234 letter_spacing: 0,
4235 line_height: 0,
4236 font_asset: ti_config.font_asset,
4237 },
4238 user_data: 0,
4239 id: hash_number(2000 + line_idx as u32, elem_id).id,
4240 z_index: root.z_index,
4241 visual_rotation: None,
4242 shape_rotation: None,
4243 effects: Vec::new(),
4244 });
4245 }
4246 }
4247
4248 if is_focused && state.cursor_visible() {
4250 let cursor_positions = &line_positions[cursor_line.min(line_positions.len() - 1)];
4251 let cursor_x_pos = cursor_positions.get(cursor_col).copied().unwrap_or(0.0);
4252 let cursor_y = current_bbox.y + cursor_line as f32 * line_step - scroll_offset_y;
4253 self.add_render_command(InternalRenderCommand {
4254 bounding_box: BoundingBox::new(
4255 current_bbox.x - scroll_offset_x + cursor_x_pos,
4256 cursor_y,
4257 2.0,
4258 line_step,
4259 ),
4260 command_type: RenderCommandType::Rectangle,
4261 render_data: InternalRenderData::Rectangle {
4262 background_color: ti_config.cursor_color,
4263 corner_radius: CornerRadius::default(),
4264 },
4265 user_data: 0,
4266 id: hash_number(1003, elem_id).id,
4267 z_index: root.z_index,
4268 visual_rotation: None,
4269 shape_rotation: None,
4270 effects: Vec::new(),
4271 });
4272 }
4273 } else {
4274 let char_x_positions = if let Some(ref measure_fn) = self.measure_text_fn {
4276 crate::text_input::compute_char_x_positions(
4277 &disp_text,
4278 ti_config.font_asset,
4279 ti_config.font_size,
4280 measure_fn.as_ref(),
4281 )
4282 } else {
4283 vec![0.0]
4284 };
4285
4286 let scroll_offset = state.scroll_offset;
4287 let text_x = current_bbox.x - scroll_offset;
4288 let font_height = natural_font_height;
4289
4290 #[cfg(feature = "text-styling")]
4292 let render_cursor_pos = if is_placeholder { 0 } else { state.cursor_pos_raw() };
4293 #[cfg(not(feature = "text-styling"))]
4294 let render_cursor_pos = if is_placeholder { 0 } else { state.cursor_pos };
4295
4296 #[cfg(feature = "text-styling")]
4297 let render_selection = if !is_placeholder { state.selection_range_raw() } else { None };
4298 #[cfg(not(feature = "text-styling"))]
4299 let render_selection = if !is_placeholder { state.selection_range() } else { None };
4300
4301 if is_focused {
4303 if let Some((sel_start, sel_end)) = render_selection {
4304 let sel_start_x = char_x_positions.get(sel_start).copied().unwrap_or(0.0);
4305 let sel_end_x = char_x_positions.get(sel_end).copied().unwrap_or(0.0);
4306 let sel_width = sel_end_x - sel_start_x;
4307 if sel_width > 0.0 {
4308 let sel_y = current_bbox.y + (current_bbox.height - font_height) / 2.0;
4309 self.add_render_command(InternalRenderCommand {
4310 bounding_box: BoundingBox::new(
4311 text_x + sel_start_x,
4312 sel_y,
4313 sel_width,
4314 font_height,
4315 ),
4316 command_type: RenderCommandType::Rectangle,
4317 render_data: InternalRenderData::Rectangle {
4318 background_color: ti_config.selection_color,
4319 corner_radius: CornerRadius::default(),
4320 },
4321 user_data: 0,
4322 id: hash_number(1001, elem_id).id,
4323 z_index: root.z_index,
4324 visual_rotation: None,
4325 shape_rotation: None,
4326 effects: Vec::new(),
4327 });
4328 }
4329 }
4330 }
4331
4332 if !disp_text.is_empty() {
4334 let text_width = char_x_positions.last().copied().unwrap_or(0.0);
4335 content_width = text_width;
4336 content_height = font_height;
4337 scroll_pos_x = scroll_offset;
4338 scroll_pos_y = 0.0;
4339 let text_y = current_bbox.y + (current_bbox.height - font_height) / 2.0;
4340 self.add_render_command(InternalRenderCommand {
4341 bounding_box: BoundingBox::new(
4342 text_x,
4343 text_y,
4344 text_width,
4345 font_height,
4346 ),
4347 command_type: RenderCommandType::Text,
4348 render_data: InternalRenderData::Text {
4349 text: disp_text,
4350 text_color,
4351 font_size: ti_config.font_size,
4352 letter_spacing: 0,
4353 line_height: 0,
4354 font_asset: ti_config.font_asset,
4355 },
4356 user_data: 0,
4357 id: hash_number(1002, elem_id).id,
4358 z_index: root.z_index,
4359 visual_rotation: None,
4360 shape_rotation: None,
4361 effects: Vec::new(),
4362 });
4363 }
4364
4365 if is_focused && state.cursor_visible() {
4367 let cursor_x_pos = char_x_positions
4368 .get(render_cursor_pos)
4369 .copied()
4370 .unwrap_or(0.0);
4371 let cursor_y = current_bbox.y + (current_bbox.height - font_height) / 2.0;
4372 self.add_render_command(InternalRenderCommand {
4373 bounding_box: BoundingBox::new(
4374 text_x + cursor_x_pos,
4375 cursor_y,
4376 2.0,
4377 font_height,
4378 ),
4379 command_type: RenderCommandType::Rectangle,
4380 render_data: InternalRenderData::Rectangle {
4381 background_color: ti_config.cursor_color,
4382 corner_radius: CornerRadius::default(),
4383 },
4384 user_data: 0,
4385 id: hash_number(1003, elem_id).id,
4386 z_index: root.z_index,
4387 visual_rotation: None,
4388 shape_rotation: None,
4389 effects: Vec::new(),
4390 });
4391 }
4392 }
4393
4394 if let Some(scrollbar_cfg) = ti_config.scrollbar {
4395 let idle_frames = self
4396 .text_input_scrollbar_idle_frames
4397 .get(&elem_id)
4398 .copied()
4399 .unwrap_or(0);
4400 let alpha = scrollbar_visibility_alpha(scrollbar_cfg, idle_frames);
4401 if alpha > 0.0 {
4402 let vertical = if ti_config.is_multiline {
4403 compute_vertical_scrollbar_geometry(
4404 current_bbox,
4405 content_height,
4406 scroll_pos_y,
4407 scrollbar_cfg,
4408 )
4409 } else {
4410 None
4411 };
4412
4413 let horizontal = compute_horizontal_scrollbar_geometry(
4414 current_bbox,
4415 content_width,
4416 scroll_pos_x,
4417 scrollbar_cfg,
4418 );
4419
4420 if vertical.is_some() || horizontal.is_some() {
4421 self.render_scrollbar_geometry(
4422 hash_number(elem_id, 8010).id,
4423 root.z_index,
4424 scrollbar_cfg,
4425 alpha,
4426 vertical,
4427 horizontal,
4428 );
4429 }
4430 }
4431 }
4432
4433 self.add_render_command(InternalRenderCommand {
4435 bounding_box: current_bbox,
4436 command_type: RenderCommandType::ScissorEnd,
4437 render_data: InternalRenderData::None,
4438 user_data: 0,
4439 id: hash_number(1004, elem_id).id,
4440 z_index: root.z_index,
4441 visual_rotation: None,
4442 shape_rotation: None,
4443 effects: Vec::new(),
4444 });
4445 }
4446 emit_rectangle = false;
4448 }
4449 }
4450 }
4451
4452 if emit_rectangle {
4453 self.add_render_command(InternalRenderCommand {
4454 bounding_box: shape_draw_bbox,
4455 command_type: RenderCommandType::Rectangle,
4456 render_data: InternalRenderData::Rectangle {
4457 background_color: shared.background_color,
4458 corner_radius: shared.corner_radius,
4459 },
4460 user_data: shared.user_data,
4461 id: elem_id,
4462 z_index: root.z_index,
4463 visual_rotation: None,
4464 shape_rotation: elem_shape_rotation,
4465 effects: elem_effects.clone(),
4466 });
4467 }
4468
4469 let is_text =
4471 self.element_has_config(current_elem_idx, ElementConfigType::Text);
4472 if !is_text {
4473 let children_start =
4474 self.layout_elements[current_elem_idx].children_start;
4475 let children_length =
4476 self.layout_elements[current_elem_idx].children_length as usize;
4477
4478 if layout_config.layout_direction == LayoutDirection::LeftToRight {
4479 if layout_config.wrap {
4480 let lines = self.compute_wrapped_lines(current_elem_idx, true);
4481 let content_height = if lines.is_empty() {
4482 0.0
4483 } else {
4484 lines.iter().map(|line| line.cross_size).sum::<f32>()
4485 + lines.len().saturating_sub(1) as f32
4486 * layout_config.wrap_gap as f32
4487 };
4488
4489 let mut extra_space = self.layout_elements[current_elem_idx]
4490 .dimensions
4491 .height
4492 - (layout_config.padding.top + layout_config.padding.bottom)
4493 as f32
4494 - content_height;
4495 if _scroll_container_data_idx.is_some() {
4496 extra_space = extra_space.max(0.0);
4497 }
4498 match layout_config.child_alignment.y {
4499 AlignY::Top => extra_space = 0.0,
4500 AlignY::CenterY => extra_space /= 2.0,
4501 AlignY::Bottom => {}
4502 }
4503 dfs_buffer[buf_idx].next_child_offset.y += extra_space;
4504 } else {
4505 let mut content_width: f32 = 0.0;
4506 for ci in 0..children_length {
4507 let child_idx =
4508 self.layout_element_children[children_start + ci] as usize;
4509 content_width +=
4510 self.layout_elements[child_idx].dimensions.width;
4511 }
4512 content_width += children_length.saturating_sub(1) as f32
4513 * layout_config.child_gap as f32;
4514 let mut extra_space = self.layout_elements[current_elem_idx]
4515 .dimensions
4516 .width
4517 - (layout_config.padding.left + layout_config.padding.right)
4518 as f32
4519 - content_width;
4520 if _scroll_container_data_idx.is_some() {
4521 extra_space = extra_space.max(0.0);
4522 }
4523 match layout_config.child_alignment.x {
4524 AlignX::Left => extra_space = 0.0,
4525 AlignX::CenterX => extra_space /= 2.0,
4526 AlignX::Right => {}
4527 }
4528 dfs_buffer[buf_idx].next_child_offset.x += extra_space;
4529 }
4530 } else if layout_config.wrap {
4531 let lines = self.compute_wrapped_lines(current_elem_idx, false);
4532 let content_width = if lines.is_empty() {
4533 0.0
4534 } else {
4535 lines.iter().map(|line| line.cross_size).sum::<f32>()
4536 + lines.len().saturating_sub(1) as f32
4537 * layout_config.wrap_gap as f32
4538 };
4539
4540 let mut extra_space = self.layout_elements[current_elem_idx]
4541 .dimensions
4542 .width
4543 - (layout_config.padding.left + layout_config.padding.right)
4544 as f32
4545 - content_width;
4546 if _scroll_container_data_idx.is_some() {
4547 extra_space = extra_space.max(0.0);
4548 }
4549 match layout_config.child_alignment.x {
4550 AlignX::Left => extra_space = 0.0,
4551 AlignX::CenterX => extra_space /= 2.0,
4552 AlignX::Right => {}
4553 }
4554 dfs_buffer[buf_idx].next_child_offset.x += extra_space;
4555 } else {
4556 let mut content_height: f32 = 0.0;
4557 for ci in 0..children_length {
4558 let child_idx =
4559 self.layout_element_children[children_start + ci] as usize;
4560 content_height +=
4561 self.layout_elements[child_idx].dimensions.height;
4562 }
4563 content_height += children_length.saturating_sub(1) as f32
4564 * layout_config.child_gap as f32;
4565 let mut extra_space = self.layout_elements[current_elem_idx]
4566 .dimensions
4567 .height
4568 - (layout_config.padding.top + layout_config.padding.bottom) as f32
4569 - content_height;
4570 if _scroll_container_data_idx.is_some() {
4571 extra_space = extra_space.max(0.0);
4572 }
4573 match layout_config.child_alignment.y {
4574 AlignY::Top => extra_space = 0.0,
4575 AlignY::CenterY => extra_space /= 2.0,
4576 AlignY::Bottom => {}
4577 }
4578 dfs_buffer[buf_idx].next_child_offset.y += extra_space;
4579 }
4580
4581 if let Some(si) = _scroll_container_data_idx {
4583 let (content_w, content_h) = if layout_config.wrap {
4584 let lines = self.compute_wrapped_lines(
4585 current_elem_idx,
4586 layout_config.layout_direction
4587 == LayoutDirection::LeftToRight,
4588 );
4589 let content_dims = self.wrapped_content_dimensions(
4590 current_elem_idx,
4591 layout_config.layout_direction
4592 == LayoutDirection::LeftToRight,
4593 &lines,
4594 );
4595 (content_dims.width, content_dims.height)
4596 } else {
4597 let child_gap_total = children_length.saturating_sub(1) as f32
4598 * layout_config.child_gap as f32;
4599 let lr_padding =
4600 (layout_config.padding.left + layout_config.padding.right)
4601 as f32;
4602 let tb_padding =
4603 (layout_config.padding.top + layout_config.padding.bottom)
4604 as f32;
4605
4606 if layout_config.layout_direction == LayoutDirection::LeftToRight {
4607 let w: f32 = (0..children_length)
4609 .map(|ci| {
4610 let idx = self.layout_element_children
4611 [children_start + ci]
4612 as usize;
4613 self.layout_elements[idx].dimensions.width
4614 })
4615 .sum::<f32>()
4616 + lr_padding
4617 + child_gap_total;
4618 let h: f32 = (0..children_length)
4619 .map(|ci| {
4620 let idx = self.layout_element_children
4621 [children_start + ci]
4622 as usize;
4623 self.layout_elements[idx].dimensions.height
4624 })
4625 .fold(0.0_f32, |a, b| a.max(b))
4626 + tb_padding;
4627 (w, h)
4628 } else {
4629 let w: f32 = (0..children_length)
4631 .map(|ci| {
4632 let idx = self.layout_element_children
4633 [children_start + ci]
4634 as usize;
4635 self.layout_elements[idx].dimensions.width
4636 })
4637 .fold(0.0_f32, |a, b| a.max(b))
4638 + lr_padding;
4639 let h: f32 = (0..children_length)
4640 .map(|ci| {
4641 let idx = self.layout_element_children
4642 [children_start + ci]
4643 as usize;
4644 self.layout_elements[idx].dimensions.height
4645 })
4646 .sum::<f32>()
4647 + tb_padding
4648 + child_gap_total;
4649 (w, h)
4650 }
4651 };
4652 self.scroll_container_datas[si].content_size =
4653 Dimensions::new(content_w, content_h);
4654 }
4655 }
4656 } else {
4657 let mut close_clip = false;
4660 let mut scroll_container_data_idx: Option<usize> = None;
4661
4662 if self.element_has_config(current_elem_idx, ElementConfigType::Clip) {
4663 close_clip = true;
4664 if let Some(clip_cfg_idx) = self
4665 .find_element_config_index(current_elem_idx, ElementConfigType::Clip)
4666 {
4667 let clip_config = self.clip_element_configs[clip_cfg_idx];
4668 for si in 0..self.scroll_container_datas.len() {
4669 if self.scroll_container_datas[si].layout_element_index
4670 == current_elem_idx as i32
4671 {
4672 scroll_offset = clip_config.child_offset;
4673 scroll_container_data_idx = Some(si);
4674 break;
4675 }
4676 }
4677 }
4678 }
4679
4680 if self.element_has_config(current_elem_idx, ElementConfigType::Border) {
4681 let border_elem_id = self.layout_elements[current_elem_idx].id;
4682 if let Some(border_bbox) = self.layout_element_map.get(&border_elem_id).map(|item| item.bounding_box) {
4683 let bbox = border_bbox;
4684 if !self.element_is_offscreen(&bbox) {
4685 let shared = self
4686 .find_element_config_index(
4687 current_elem_idx,
4688 ElementConfigType::Shared,
4689 )
4690 .map(|idx| self.shared_element_configs[idx])
4691 .unwrap_or_default();
4692 let border_cfg_idx = self
4693 .find_element_config_index(
4694 current_elem_idx,
4695 ElementConfigType::Border,
4696 )
4697 .unwrap();
4698 let border_config = self.border_element_configs[border_cfg_idx];
4699
4700 let children_count =
4701 self.layout_elements[current_elem_idx].children_length;
4702
4703 if border_config.width.between_children > 0
4705 && border_config.color.a > 0.0
4706 {
4707 let half_gap = layout_config.child_gap as f32 / 2.0;
4708 let half_divider =
4709 border_config.width.between_children as f32 / 2.0;
4710 let children_start =
4711 self.layout_elements[current_elem_idx].children_start;
4712 let children_length = self.layout_elements[current_elem_idx]
4713 .children_length
4714 as usize;
4715
4716 if layout_config.layout_direction
4717 == LayoutDirection::LeftToRight
4718 {
4719 let mut border_offset_x =
4720 layout_config.padding.left as f32 - half_gap;
4721 for ci in 0..children_length {
4722 let child_idx = self.layout_element_children
4723 [children_start + ci]
4724 as usize;
4725 if ci > 0 {
4726 self.add_render_command(InternalRenderCommand {
4727 bounding_box: BoundingBox::new(
4728 bbox.x + border_offset_x
4729 - half_divider
4730 + scroll_offset.x,
4731 bbox.y + scroll_offset.y,
4732 border_config.width.between_children as f32,
4733 self.layout_elements[current_elem_idx]
4734 .dimensions
4735 .height,
4736 ),
4737 command_type: RenderCommandType::Rectangle,
4738 render_data: InternalRenderData::Rectangle {
4739 background_color: border_config.color,
4740 corner_radius: CornerRadius::default(),
4741 },
4742 user_data: shared.user_data,
4743 id: hash_number(
4744 self.layout_elements[current_elem_idx].id,
4745 children_count as u32 + 1 + ci as u32,
4746 )
4747 .id,
4748 z_index: root.z_index,
4749 visual_rotation: None,
4750 shape_rotation: None,
4751 effects: Vec::new(),
4752 });
4753 }
4754 border_offset_x +=
4755 self.layout_elements[child_idx].dimensions.width
4756 + layout_config.child_gap as f32;
4757 }
4758 } else {
4759 let mut border_offset_y =
4760 layout_config.padding.top as f32 - half_gap;
4761 for ci in 0..children_length {
4762 let child_idx = self.layout_element_children
4763 [children_start + ci]
4764 as usize;
4765 if ci > 0 {
4766 self.add_render_command(InternalRenderCommand {
4767 bounding_box: BoundingBox::new(
4768 bbox.x + scroll_offset.x,
4769 bbox.y + border_offset_y
4770 - half_divider
4771 + scroll_offset.y,
4772 self.layout_elements[current_elem_idx]
4773 .dimensions
4774 .width,
4775 border_config.width.between_children as f32,
4776 ),
4777 command_type: RenderCommandType::Rectangle,
4778 render_data: InternalRenderData::Rectangle {
4779 background_color: border_config.color,
4780 corner_radius: CornerRadius::default(),
4781 },
4782 user_data: shared.user_data,
4783 id: hash_number(
4784 self.layout_elements[current_elem_idx].id,
4785 children_count as u32 + 1 + ci as u32,
4786 )
4787 .id,
4788 z_index: root.z_index,
4789 visual_rotation: None,
4790 shape_rotation: None,
4791 effects: Vec::new(),
4792 });
4793 }
4794 border_offset_y +=
4795 self.layout_elements[child_idx].dimensions.height
4796 + layout_config.child_gap as f32;
4797 }
4798 }
4799 }
4800 }
4801 }
4802 }
4803
4804 if let Some(si) = scroll_container_data_idx {
4805 let scd = self.scroll_container_datas[si];
4806 if let Some(scrollbar_cfg) = scd.scrollbar {
4807 let alpha = scrollbar_visibility_alpha(scrollbar_cfg, scd.scrollbar_idle_frames);
4808 if alpha > 0.0 {
4809 let vertical = if scd.scroll_y_enabled {
4810 compute_vertical_scrollbar_geometry(
4811 scd.bounding_box,
4812 scd.content_size.height,
4813 -scd.scroll_position.y,
4814 scrollbar_cfg,
4815 )
4816 } else {
4817 None
4818 };
4819
4820 let horizontal = if scd.scroll_x_enabled {
4821 compute_horizontal_scrollbar_geometry(
4822 scd.bounding_box,
4823 scd.content_size.width,
4824 -scd.scroll_position.x,
4825 scrollbar_cfg,
4826 )
4827 } else {
4828 None
4829 };
4830
4831 if vertical.is_some() || horizontal.is_some() {
4832 self.render_scrollbar_geometry(
4833 scd.element_id,
4834 root.z_index,
4835 scrollbar_cfg,
4836 alpha,
4837 vertical,
4838 horizontal,
4839 );
4840 }
4841 }
4842 }
4843 }
4844
4845 if close_clip {
4846 let root_elem = &self.layout_elements[root_elem_idx];
4847 self.add_render_command(InternalRenderCommand {
4848 command_type: RenderCommandType::ScissorEnd,
4849 id: hash_number(
4850 self.layout_elements[current_elem_idx].id,
4851 root_elem.children_length as u32 + 11,
4852 )
4853 .id,
4854 ..Default::default()
4855 });
4856 }
4857
4858 if self.element_has_config(current_elem_idx, ElementConfigType::Border) {
4859 let border_elem_id = self.layout_elements[current_elem_idx].id;
4860 if let Some(border_bbox) = self.layout_element_map.get(&border_elem_id).map(|item| item.bounding_box) {
4861 let bbox = border_bbox;
4862 if !self.element_is_offscreen(&bbox) {
4863 let shared = self
4864 .find_element_config_index(
4865 current_elem_idx,
4866 ElementConfigType::Shared,
4867 )
4868 .map(|idx| self.shared_element_configs[idx])
4869 .unwrap_or_default();
4870 let border_cfg_idx = self
4871 .find_element_config_index(
4872 current_elem_idx,
4873 ElementConfigType::Border,
4874 )
4875 .unwrap();
4876 let border_config = self.border_element_configs[border_cfg_idx];
4877
4878 let children_count =
4879 self.layout_elements[current_elem_idx].children_length;
4880 self.add_render_command(InternalRenderCommand {
4881 bounding_box: bbox,
4882 command_type: RenderCommandType::Border,
4883 render_data: InternalRenderData::Border {
4884 color: border_config.color,
4885 corner_radius: shared.corner_radius,
4886 width: border_config.width,
4887 position: border_config.position,
4888 },
4889 user_data: shared.user_data,
4890 id: hash_number(
4891 self.layout_elements[current_elem_idx].id,
4892 children_count as u32,
4893 )
4894 .id,
4895 z_index: root.z_index,
4896 visual_rotation: None,
4897 shape_rotation: None,
4898 effects: Vec::new(),
4899 });
4900 }
4901 }
4902 }
4903
4904 let elem_shaders = self.element_shaders.get(current_elem_idx).cloned().unwrap_or_default();
4906 let elem_visual_rotation = self.element_visual_rotations.get(current_elem_idx).cloned().flatten()
4907 .filter(|vr| !vr.is_noop());
4908
4909 for _shader in elem_shaders.iter() {
4911 self.add_render_command(InternalRenderCommand {
4912 command_type: RenderCommandType::GroupEnd,
4913 id: self.layout_elements[current_elem_idx].id,
4914 z_index: root.z_index,
4915 ..Default::default()
4916 });
4917 }
4918 if elem_shaders.is_empty() && elem_visual_rotation.is_some() {
4920 self.add_render_command(InternalRenderCommand {
4921 command_type: RenderCommandType::GroupEnd,
4922 id: self.layout_elements[current_elem_idx].id,
4923 z_index: root.z_index,
4924 ..Default::default()
4925 });
4926 }
4927
4928 dfs_buffer.pop();
4929 visited.pop();
4930 continue;
4931 }
4932
4933 let is_text =
4935 self.element_has_config(current_elem_idx, ElementConfigType::Text);
4936 if !is_text {
4937 let children_start = self.layout_elements[current_elem_idx].children_start;
4938 let children_length =
4939 self.layout_elements[current_elem_idx].children_length as usize;
4940
4941 let new_len = dfs_buffer.len() + children_length;
4943 dfs_buffer.resize(new_len, LayoutElementTreeNode::default());
4944 visited.resize(new_len, false);
4945
4946 let is_scroll_container = self.scroll_container_datas.iter().any(|scd| {
4947 scd.layout_element_index == current_elem_idx as i32
4948 });
4949
4950 let wrapped_lines = if layout_config.wrap {
4951 Some(self.compute_wrapped_lines(
4952 current_elem_idx,
4953 layout_config.layout_direction == LayoutDirection::LeftToRight,
4954 ))
4955 } else {
4956 None
4957 };
4958
4959 let mut wrapped_line_main_cursors: Vec<f32> = Vec::new();
4960 let mut wrapped_line_cross_starts: Vec<f32> = Vec::new();
4961 let mut wrapped_child_line_indexes: Vec<usize> =
4962 vec![0; children_length];
4963
4964 if let Some(lines) = wrapped_lines.as_ref() {
4965 if layout_config.layout_direction == LayoutDirection::LeftToRight {
4966 let mut line_y = dfs_buffer[buf_idx].next_child_offset.y;
4967 for (line_idx, line) in lines.iter().enumerate() {
4968 let mut line_extra_space =
4969 self.layout_elements[current_elem_idx].dimensions.width
4970 - (layout_config.padding.left
4971 + layout_config.padding.right)
4972 as f32
4973 - line.main_size;
4974 if is_scroll_container {
4975 line_extra_space = line_extra_space.max(0.0);
4976 }
4977 match layout_config.child_alignment.x {
4978 AlignX::Left => line_extra_space = 0.0,
4979 AlignX::CenterX => line_extra_space /= 2.0,
4980 AlignX::Right => {}
4981 }
4982
4983 wrapped_line_main_cursors
4984 .push(layout_config.padding.left as f32 + line_extra_space);
4985 wrapped_line_cross_starts.push(line_y);
4986
4987 for child_offset in
4988 line.start_child_offset..line.end_child_offset
4989 {
4990 wrapped_child_line_indexes[child_offset] = line_idx;
4991 }
4992
4993 line_y += line.cross_size + layout_config.wrap_gap as f32;
4994 }
4995 } else {
4996 let mut line_x = dfs_buffer[buf_idx].next_child_offset.x;
4997 for (line_idx, line) in lines.iter().enumerate() {
4998 let mut line_extra_space =
4999 self.layout_elements[current_elem_idx].dimensions.height
5000 - (layout_config.padding.top
5001 + layout_config.padding.bottom)
5002 as f32
5003 - line.main_size;
5004 if is_scroll_container {
5005 line_extra_space = line_extra_space.max(0.0);
5006 }
5007 match layout_config.child_alignment.y {
5008 AlignY::Top => line_extra_space = 0.0,
5009 AlignY::CenterY => line_extra_space /= 2.0,
5010 AlignY::Bottom => {}
5011 }
5012
5013 wrapped_line_main_cursors
5014 .push(layout_config.padding.top as f32 + line_extra_space);
5015 wrapped_line_cross_starts.push(line_x);
5016
5017 for child_offset in
5018 line.start_child_offset..line.end_child_offset
5019 {
5020 wrapped_child_line_indexes[child_offset] = line_idx;
5021 }
5022
5023 line_x += line.cross_size + layout_config.wrap_gap as f32;
5024 }
5025 }
5026 }
5027
5028 for ci in 0..children_length {
5029 let child_idx =
5030 self.layout_element_children[children_start + ci] as usize;
5031 let child_layout_idx =
5032 self.layout_elements[child_idx].layout_config_index;
5033
5034 let mut child_offset = dfs_buffer[buf_idx].next_child_offset;
5036 if layout_config.wrap {
5037 let Some(lines) = wrapped_lines.as_ref() else {
5038 continue;
5039 };
5040 let line_idx = wrapped_child_line_indexes[ci];
5041 let line = lines[line_idx];
5042
5043 if layout_config.layout_direction == LayoutDirection::LeftToRight {
5044 child_offset.x = wrapped_line_main_cursors[line_idx];
5045 child_offset.y = wrapped_line_cross_starts[line_idx];
5046
5047 let whitespace = line.cross_size
5048 - self.layout_elements[child_idx].dimensions.height;
5049 match layout_config.child_alignment.y {
5050 AlignY::Top => {}
5051 AlignY::CenterY => {
5052 child_offset.y += whitespace / 2.0;
5053 }
5054 AlignY::Bottom => {
5055 child_offset.y += whitespace;
5056 }
5057 }
5058
5059 wrapped_line_main_cursors[line_idx] +=
5060 self.layout_elements[child_idx].dimensions.width
5061 + layout_config.child_gap as f32;
5062 } else {
5063 child_offset.x = wrapped_line_cross_starts[line_idx];
5064 child_offset.y = wrapped_line_main_cursors[line_idx];
5065
5066 let whitespace = line.cross_size
5067 - self.layout_elements[child_idx].dimensions.width;
5068 match layout_config.child_alignment.x {
5069 AlignX::Left => {}
5070 AlignX::CenterX => {
5071 child_offset.x += whitespace / 2.0;
5072 }
5073 AlignX::Right => {
5074 child_offset.x += whitespace;
5075 }
5076 }
5077
5078 wrapped_line_main_cursors[line_idx] +=
5079 self.layout_elements[child_idx].dimensions.height
5080 + layout_config.child_gap as f32;
5081 }
5082 } else if layout_config.layout_direction == LayoutDirection::LeftToRight {
5083 child_offset.y = layout_config.padding.top as f32;
5084 let whitespace = self.layout_elements[current_elem_idx].dimensions.height
5085 - (layout_config.padding.top + layout_config.padding.bottom) as f32
5086 - self.layout_elements[child_idx].dimensions.height;
5087 match layout_config.child_alignment.y {
5088 AlignY::Top => {}
5089 AlignY::CenterY => {
5090 child_offset.y += whitespace / 2.0;
5091 }
5092 AlignY::Bottom => {
5093 child_offset.y += whitespace;
5094 }
5095 }
5096 } else {
5097 child_offset.x = layout_config.padding.left as f32;
5098 let whitespace = self.layout_elements[current_elem_idx].dimensions.width
5099 - (layout_config.padding.left + layout_config.padding.right) as f32
5100 - self.layout_elements[child_idx].dimensions.width;
5101 match layout_config.child_alignment.x {
5102 AlignX::Left => {}
5103 AlignX::CenterX => {
5104 child_offset.x += whitespace / 2.0;
5105 }
5106 AlignX::Right => {
5107 child_offset.x += whitespace;
5108 }
5109 }
5110 }
5111
5112 let child_position = Vector2::new(
5113 dfs_buffer[buf_idx].position.x + child_offset.x + scroll_offset.x,
5114 dfs_buffer[buf_idx].position.y + child_offset.y + scroll_offset.y,
5115 );
5116
5117 let new_node_index = new_len - 1 - ci;
5118 let child_padding_left =
5119 self.layout_configs[child_layout_idx].padding.left as f32;
5120 let child_padding_top =
5121 self.layout_configs[child_layout_idx].padding.top as f32;
5122 dfs_buffer[new_node_index] = LayoutElementTreeNode {
5123 layout_element_index: child_idx as i32,
5124 position: child_position,
5125 next_child_offset: Vector2::new(child_padding_left, child_padding_top),
5126 };
5127 visited[new_node_index] = false;
5128
5129 if !layout_config.wrap {
5131 if layout_config.layout_direction == LayoutDirection::LeftToRight {
5132 dfs_buffer[buf_idx].next_child_offset.x +=
5133 self.layout_elements[child_idx].dimensions.width
5134 + layout_config.child_gap as f32;
5135 } else {
5136 dfs_buffer[buf_idx].next_child_offset.y +=
5137 self.layout_elements[child_idx].dimensions.height
5138 + layout_config.child_gap as f32;
5139 }
5140 }
5141 }
5142 }
5143 }
5144
5145 if root.clip_element_id != 0 {
5147 let root_elem = &self.layout_elements[root_elem_idx];
5148 self.add_render_command(InternalRenderCommand {
5149 command_type: RenderCommandType::ScissorEnd,
5150 id: hash_number(root_elem.id, root_elem.children_length as u32 + 11).id,
5151 ..Default::default()
5152 });
5153 }
5154 }
5155
5156 if self.focused_element_id != 0 && self.focus_from_keyboard {
5158 let a11y = self.accessibility_configs.get(&self.focused_element_id);
5160 let show_ring = a11y.map_or(true, |c| c.show_ring);
5161 if show_ring {
5162 if let Some(item) = self.layout_element_map.get(&self.focused_element_id) {
5163 let bbox = item.bounding_box;
5164 if !self.element_is_offscreen(&bbox) {
5165 let elem_idx = item.layout_element_index as usize;
5166 let corner_radius = self
5167 .find_element_config_index(elem_idx, ElementConfigType::Shared)
5168 .map(|idx| self.shared_element_configs[idx].corner_radius)
5169 .unwrap_or_default();
5170 let ring_width = a11y.and_then(|c| c.ring_width).unwrap_or(2);
5171 let ring_color = a11y.and_then(|c| c.ring_color).unwrap_or(Color::rgba(255.0, 60.0, 40.0, 255.0));
5172 let expanded_bbox = BoundingBox::new(
5174 bbox.x - ring_width as f32,
5175 bbox.y - ring_width as f32,
5176 bbox.width + ring_width as f32 * 2.0,
5177 bbox.height + ring_width as f32 * 2.0,
5178 );
5179 self.add_render_command(InternalRenderCommand {
5180 bounding_box: expanded_bbox,
5181 command_type: RenderCommandType::Border,
5182 render_data: InternalRenderData::Border {
5183 color: ring_color,
5184 corner_radius: CornerRadius {
5185 top_left: corner_radius.top_left + ring_width as f32,
5186 top_right: corner_radius.top_right + ring_width as f32,
5187 bottom_left: corner_radius.bottom_left + ring_width as f32,
5188 bottom_right: corner_radius.bottom_right + ring_width as f32,
5189 },
5190 width: BorderWidth {
5191 left: ring_width,
5192 right: ring_width,
5193 top: ring_width,
5194 bottom: ring_width,
5195 between_children: 0,
5196 },
5197 position: BorderPosition::Middle,
5198 },
5199 id: hash_number(self.focused_element_id, 0xF0C5).id,
5200 z_index: 32764, ..Default::default()
5202 });
5203 }
5204 }
5205 }
5206 }
5207 }
5208
5209 pub fn set_layout_dimensions(&mut self, dimensions: Dimensions) {
5210 self.layout_dimensions = dimensions;
5211 }
5212
5213 pub fn set_pointer_state(&mut self, position: Vector2, is_down: bool) {
5214 if self.boolean_warnings.max_elements_exceeded {
5215 return;
5216 }
5217 self.pointer_info.position = position;
5218 self.pointer_over_ids.clear();
5219
5220 for root_index in (0..self.layout_element_tree_roots.len()).rev() {
5222 let root = self.layout_element_tree_roots[root_index];
5223 let mut dfs: Vec<i32> = vec![root.layout_element_index];
5224 let mut vis: Vec<bool> = vec![false];
5225 let mut found = false;
5226
5227 while !dfs.is_empty() {
5228 let idx = dfs.len() - 1;
5229 if vis[idx] {
5230 dfs.pop();
5231 vis.pop();
5232 continue;
5233 }
5234 vis[idx] = true;
5235 let current_idx = dfs[idx] as usize;
5236 let elem_id = self.layout_elements[current_idx].id;
5237
5238 let map_data = self.layout_element_map.get(&elem_id).map(|item| {
5240 (item.bounding_box, item.element_id.clone(), item.on_hover_fn.is_some())
5241 });
5242 if let Some((raw_box, elem_id_copy, has_hover)) = map_data {
5243 let mut elem_box = raw_box;
5244 elem_box.x -= root.pointer_offset.x;
5245 elem_box.y -= root.pointer_offset.y;
5246
5247 let clip_id =
5248 self.layout_element_clip_element_ids[current_idx] as u32;
5249 let clip_ok = clip_id == 0
5250 || self
5251 .layout_element_map
5252 .get(&clip_id)
5253 .map(|ci| {
5254 point_is_inside_rect(
5255 position,
5256 ci.bounding_box,
5257 )
5258 })
5259 .unwrap_or(false);
5260
5261 if point_is_inside_rect(position, elem_box) && clip_ok {
5262 if has_hover {
5264 let pointer_data = self.pointer_info;
5265 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
5266 if let Some(ref mut callback) = item.on_hover_fn {
5267 callback(elem_id_copy.clone(), pointer_data);
5268 }
5269 }
5270 }
5271 self.pointer_over_ids.push(elem_id_copy);
5272 found = true;
5273 }
5274
5275 if self.element_has_config(current_idx, ElementConfigType::Text) {
5276 dfs.pop();
5277 vis.pop();
5278 continue;
5279 }
5280 let children_start = self.layout_elements[current_idx].children_start;
5281 let children_length =
5282 self.layout_elements[current_idx].children_length as usize;
5283 for ci in (0..children_length).rev() {
5284 let child = self.layout_element_children[children_start + ci];
5285 dfs.push(child);
5286 vis.push(false);
5287 }
5288 } else {
5289 dfs.pop();
5290 vis.pop();
5291 }
5292 }
5293
5294 if found {
5295 let root_elem_idx = root.layout_element_index as usize;
5296 if self.element_has_config(root_elem_idx, ElementConfigType::Floating) {
5297 if let Some(cfg_idx) = self
5298 .find_element_config_index(root_elem_idx, ElementConfigType::Floating)
5299 {
5300 if self.floating_element_configs[cfg_idx].pointer_capture_mode
5301 == PointerCaptureMode::Capture
5302 {
5303 break;
5304 }
5305 }
5306 }
5307 }
5308 }
5309
5310 if is_down {
5312 match self.pointer_info.state {
5313 PointerDataInteractionState::PressedThisFrame => {
5314 self.pointer_info.state = PointerDataInteractionState::Pressed;
5315 }
5316 s if s != PointerDataInteractionState::Pressed => {
5317 self.pointer_info.state = PointerDataInteractionState::PressedThisFrame;
5318 }
5319 _ => {}
5320 }
5321 } else {
5322 match self.pointer_info.state {
5323 PointerDataInteractionState::ReleasedThisFrame => {
5324 self.pointer_info.state = PointerDataInteractionState::Released;
5325 }
5326 s if s != PointerDataInteractionState::Released => {
5327 self.pointer_info.state = PointerDataInteractionState::ReleasedThisFrame;
5328 }
5329 _ => {}
5330 }
5331 }
5332
5333 match self.pointer_info.state {
5335 PointerDataInteractionState::PressedThisFrame => {
5336 let clicked_text_input = self.pointer_over_ids.last()
5338 .and_then(|top| self.layout_element_map.get(&top.id))
5339 .map(|item| item.is_text_input)
5340 .unwrap_or(false);
5341
5342 if clicked_text_input {
5343 self.focus_from_keyboard = false;
5345 if let Some(top) = self.pointer_over_ids.last().cloned() {
5346 if self.focused_element_id != top.id {
5347 self.change_focus(top.id);
5348 }
5349 if let Some(item) = self.layout_element_map.get(&top.id) {
5351 let click_x = self.pointer_info.position.x - item.bounding_box.x;
5352 let click_y = self.pointer_info.position.y - item.bounding_box.y;
5353 self.pending_text_click = Some((top.id, click_x, click_y, false));
5356 }
5357 self.pressed_element_ids = self.pointer_over_ids.clone();
5358 }
5359 } else {
5360 let preserves = self.pointer_over_ids.iter().any(|eid| {
5363 self.layout_element_map.get(&eid.id)
5364 .map(|item| item.preserve_focus)
5365 .unwrap_or(false)
5366 });
5367
5368 if !preserves && self.focused_element_id != 0 {
5370 self.change_focus(0);
5371 }
5372
5373 self.pressed_element_ids = self.pointer_over_ids.clone();
5375 for eid in self.pointer_over_ids.clone().iter() {
5376 if let Some(item) = self.layout_element_map.get_mut(&eid.id) {
5377 if let Some(ref mut callback) = item.on_press_fn {
5378 callback(eid.clone(), self.pointer_info);
5379 }
5380 }
5381 }
5382 }
5383
5384 let pressed_now = self.pressed_element_ids.clone();
5385 self.track_just_pressed_ids(&pressed_now);
5386 }
5387 PointerDataInteractionState::ReleasedThisFrame => {
5388 let pressed = std::mem::take(&mut self.pressed_element_ids);
5390 self.track_just_released_ids(&pressed);
5391 for eid in pressed.iter() {
5392 if let Some(item) = self.layout_element_map.get_mut(&eid.id) {
5393 if let Some(ref mut callback) = item.on_release_fn {
5394 callback(eid.clone(), self.pointer_info);
5395 }
5396 }
5397 }
5398 }
5399 _ => {}
5400 }
5401 }
5402
5403 const SCROLL_DECEL: f32 = 5.0; const SCROLL_MIN_VELOCITY: f32 = 5.0; const SCROLL_VELOCITY_SMOOTHING: f32 = 0.4; pub fn update_scroll_containers(
5409 &mut self,
5410 enable_drag_scrolling: bool,
5411 scroll_delta: Vector2,
5412 delta_time: f32,
5413 touch_input_active: bool,
5414 ) {
5415 let pointer = self.pointer_info.position;
5416 let dt = delta_time.max(0.0001); let mut i = 0;
5420 while i < self.scroll_container_datas.len() {
5421 if !self.scroll_container_datas[i].open_this_frame {
5422 self.scroll_container_datas.swap_remove(i);
5423 continue;
5424 }
5425 self.scroll_container_datas[i].open_this_frame = false;
5426 i += 1;
5427 }
5428
5429 for scd in &mut self.scroll_container_datas {
5430 scd.scrollbar_activity_this_frame = false;
5431 }
5432
5433 if enable_drag_scrolling {
5435 let pointer_state = self.pointer_info.state;
5436
5437 match pointer_state {
5438 PointerDataInteractionState::PressedThisFrame => {
5439 let mut best: Option<usize> = None;
5441 for si in 0..self.scroll_container_datas.len() {
5442 let scd = &self.scroll_container_datas[si];
5443 let bb = scd.bounding_box;
5444 if pointer.x >= bb.x
5445 && pointer.x <= bb.x + bb.width
5446 && pointer.y >= bb.y
5447 && pointer.y <= bb.y + bb.height
5448 {
5449 best = Some(si);
5450 }
5451 }
5452 if let Some(si) = best {
5453 let scd = &mut self.scroll_container_datas[si];
5454 scd.scroll_momentum = Vector2::default();
5455 scd.previous_delta = Vector2::default();
5456
5457 scd.pointer_scroll_active = false;
5458 scd.scrollbar_thumb_drag_active_x = false;
5459 scd.scrollbar_thumb_drag_active_y = false;
5460
5461 let mut started_thumb_drag = false;
5462 if let Some(scrollbar_cfg) = scd.scrollbar {
5463 let alpha = scrollbar_visibility_alpha(scrollbar_cfg, scd.scrollbar_idle_frames);
5464 if alpha > 0.0 {
5465 if scd.scroll_y_enabled {
5466 if let Some(geo) = compute_vertical_scrollbar_geometry(
5467 scd.bounding_box,
5468 scd.content_size.height,
5469 -scd.scroll_position.y,
5470 scrollbar_cfg,
5471 ) {
5472 if point_is_inside_rect(pointer, geo.thumb_bbox) {
5473 scd.scrollbar_thumb_drag_active_y = true;
5474 started_thumb_drag = true;
5475 }
5476 }
5477 }
5478
5479 if !started_thumb_drag && scd.scroll_x_enabled {
5480 if let Some(geo) = compute_horizontal_scrollbar_geometry(
5481 scd.bounding_box,
5482 scd.content_size.width,
5483 -scd.scroll_position.x,
5484 scrollbar_cfg,
5485 ) {
5486 if point_is_inside_rect(pointer, geo.thumb_bbox) {
5487 scd.scrollbar_thumb_drag_active_x = true;
5488 started_thumb_drag = true;
5489 }
5490 }
5491 }
5492 }
5493 }
5494
5495 if started_thumb_drag {
5496 scd.scrollbar_drag_origin = pointer;
5497 scd.scrollbar_drag_scroll_origin =
5498 Vector2::new(-scd.scroll_position.x, -scd.scroll_position.y);
5499 scd.scrollbar_activity_this_frame = true;
5500 } else if !(scd.no_drag_scroll && !touch_input_active) {
5501 scd.pointer_scroll_active = true;
5502 scd.pointer_origin = pointer;
5503 scd.scroll_origin = scd.scroll_position;
5504 }
5505 }
5506 }
5507 PointerDataInteractionState::Pressed => {
5508 for si in 0..self.scroll_container_datas.len() {
5510 let scd = &mut self.scroll_container_datas[si];
5511
5512 if scd.scrollbar_thumb_drag_active_y {
5513 if let Some(scrollbar_cfg) = scd.scrollbar {
5514 if let Some(geo) = compute_vertical_scrollbar_geometry(
5515 scd.bounding_box,
5516 scd.content_size.height,
5517 scd.scrollbar_drag_scroll_origin.y,
5518 scrollbar_cfg,
5519 ) {
5520 let delta = pointer.y - scd.scrollbar_drag_origin.y;
5521 let new_scroll = if geo.thumb_travel <= 0.0 {
5522 0.0
5523 } else {
5524 scd.scrollbar_drag_scroll_origin.y
5525 + delta * (geo.max_scroll / geo.thumb_travel)
5526 };
5527 scd.scroll_position.y = -new_scroll.clamp(0.0, geo.max_scroll);
5528 }
5529 }
5530 scd.scroll_momentum.y = 0.0;
5531 scd.scrollbar_activity_this_frame = true;
5532 continue;
5533 }
5534
5535 if scd.scrollbar_thumb_drag_active_x {
5536 if let Some(scrollbar_cfg) = scd.scrollbar {
5537 if let Some(geo) = compute_horizontal_scrollbar_geometry(
5538 scd.bounding_box,
5539 scd.content_size.width,
5540 scd.scrollbar_drag_scroll_origin.x,
5541 scrollbar_cfg,
5542 ) {
5543 let delta = pointer.x - scd.scrollbar_drag_origin.x;
5544 let new_scroll = if geo.thumb_travel <= 0.0 {
5545 0.0
5546 } else {
5547 scd.scrollbar_drag_scroll_origin.x
5548 + delta * (geo.max_scroll / geo.thumb_travel)
5549 };
5550 scd.scroll_position.x = -new_scroll.clamp(0.0, geo.max_scroll);
5551 }
5552 }
5553 scd.scroll_momentum.x = 0.0;
5554 scd.scrollbar_activity_this_frame = true;
5555 continue;
5556 }
5557
5558 if !scd.pointer_scroll_active {
5559 continue;
5560 }
5561
5562 let drag_delta = Vector2::new(
5563 pointer.x - scd.pointer_origin.x,
5564 pointer.y - scd.pointer_origin.y,
5565 );
5566 scd.scroll_position = Vector2::new(
5567 scd.scroll_origin.x + drag_delta.x,
5568 scd.scroll_origin.y + drag_delta.y,
5569 );
5570
5571 let frame_delta = Vector2::new(
5573 drag_delta.x - scd.previous_delta.x,
5574 drag_delta.y - scd.previous_delta.y,
5575 );
5576 let moved = frame_delta.x.abs() > 0.5 || frame_delta.y.abs() > 0.5;
5577
5578 if moved {
5579 let instant_velocity = Vector2::new(
5581 frame_delta.x / dt,
5582 frame_delta.y / dt,
5583 );
5584 let s = Self::SCROLL_VELOCITY_SMOOTHING;
5585 scd.scroll_momentum = Vector2::new(
5586 scd.scroll_momentum.x * (1.0 - s) + instant_velocity.x * s,
5587 scd.scroll_momentum.y * (1.0 - s) + instant_velocity.y * s,
5588 );
5589 scd.scrollbar_activity_this_frame = true;
5590 }
5591 scd.previous_delta = drag_delta;
5592 }
5593 }
5594 PointerDataInteractionState::ReleasedThisFrame
5595 | PointerDataInteractionState::Released => {
5596 for si in 0..self.scroll_container_datas.len() {
5597 let scd = &mut self.scroll_container_datas[si];
5598 if scd.scrollbar_thumb_drag_active_x
5599 || scd.scrollbar_thumb_drag_active_y
5600 {
5601 scd.scrollbar_activity_this_frame = true;
5602 }
5603 scd.pointer_scroll_active = false;
5604 scd.scrollbar_thumb_drag_active_x = false;
5605 scd.scrollbar_thumb_drag_active_y = false;
5606 }
5607 }
5608 }
5609 }
5610
5611 for si in 0..self.scroll_container_datas.len() {
5613 let scd = &mut self.scroll_container_datas[si];
5614 if scd.pointer_scroll_active
5615 || scd.scrollbar_thumb_drag_active_x
5616 || scd.scrollbar_thumb_drag_active_y
5617 {
5618 } else if scd.scroll_momentum.x.abs() > Self::SCROLL_MIN_VELOCITY
5620 || scd.scroll_momentum.y.abs() > Self::SCROLL_MIN_VELOCITY
5621 {
5622 scd.scroll_position.x += scd.scroll_momentum.x * dt;
5624 scd.scroll_position.y += scd.scroll_momentum.y * dt;
5625 scd.scrollbar_activity_this_frame = true;
5626
5627 let decay = (-Self::SCROLL_DECEL * dt).exp();
5629 scd.scroll_momentum.x *= decay;
5630 scd.scroll_momentum.y *= decay;
5631
5632 if scd.scroll_momentum.x.abs() < Self::SCROLL_MIN_VELOCITY {
5634 scd.scroll_momentum.x = 0.0;
5635 }
5636 if scd.scroll_momentum.y.abs() < Self::SCROLL_MIN_VELOCITY {
5637 scd.scroll_momentum.y = 0.0;
5638 }
5639 }
5640 }
5641
5642 if scroll_delta.x != 0.0 || scroll_delta.y != 0.0 {
5644 let mut best: Option<usize> = None;
5646 for si in 0..self.scroll_container_datas.len() {
5647 let bb = self.scroll_container_datas[si].bounding_box;
5648 if pointer.x >= bb.x
5649 && pointer.x <= bb.x + bb.width
5650 && pointer.y >= bb.y
5651 && pointer.y <= bb.y + bb.height
5652 {
5653 best = Some(si);
5654 }
5655 }
5656 if let Some(si) = best {
5657 let scd = &mut self.scroll_container_datas[si];
5658 scd.scroll_position.y += scroll_delta.y;
5659 scd.scroll_position.x += scroll_delta.x;
5660 scd.scroll_momentum = Vector2::default();
5662 scd.scrollbar_activity_this_frame = true;
5663 }
5664 }
5665
5666 for si in 0..self.scroll_container_datas.len() {
5668 let scd = &mut self.scroll_container_datas[si];
5669 let max_scroll_y =
5670 -(scd.content_size.height - scd.bounding_box.height).max(0.0);
5671 let max_scroll_x =
5672 -(scd.content_size.width - scd.bounding_box.width).max(0.0);
5673 scd.scroll_position.y = scd.scroll_position.y.clamp(max_scroll_y, 0.0);
5674 scd.scroll_position.x = scd.scroll_position.x.clamp(max_scroll_x, 0.0);
5675
5676 if scd.scroll_position.y >= 0.0 || scd.scroll_position.y <= max_scroll_y {
5678 scd.scroll_momentum.y = 0.0;
5679 }
5680 if scd.scroll_position.x >= 0.0 || scd.scroll_position.x <= max_scroll_x {
5681 scd.scroll_momentum.x = 0.0;
5682 }
5683
5684 if scd.scrollbar.is_some() {
5685 if scd.scrollbar_activity_this_frame {
5686 scd.scrollbar_idle_frames = 0;
5687 } else {
5688 scd.scrollbar_idle_frames = scd.scrollbar_idle_frames.saturating_add(1);
5689 }
5690 }
5691 }
5692 }
5693
5694 pub fn hovered(&self) -> bool {
5695 let open_idx = self.get_open_layout_element();
5696 let elem_id = self.layout_elements[open_idx].id;
5697 self.pointer_over_ids.iter().any(|eid| eid.id == elem_id)
5698 }
5699
5700 fn release_query_generation(&self) -> u32 {
5701 if self.open_layout_element_stack.len() <= 1 {
5702 self.generation.wrapping_add(1)
5703 } else {
5704 self.generation
5705 }
5706 }
5707
5708 fn track_just_released_ids(&mut self, ids: &[Id]) {
5709 if ids.is_empty() {
5710 return;
5711 }
5712
5713 let target_generation = self.release_query_generation();
5714 if self.released_this_frame_generation != target_generation {
5715 self.released_this_frame_ids.clear();
5716 self.released_this_frame_generation = target_generation;
5717 }
5718
5719 for id in ids {
5720 if !self
5721 .released_this_frame_ids
5722 .iter()
5723 .any(|existing| existing.id == id.id)
5724 {
5725 self.released_this_frame_ids.push(id.clone());
5726 }
5727 }
5728 }
5729
5730 fn track_just_pressed_ids(&mut self, ids: &[Id]) {
5731 if ids.is_empty() {
5732 return;
5733 }
5734
5735 let target_generation = self.release_query_generation();
5736 if self.pressed_this_frame_generation != target_generation {
5737 self.pressed_this_frame_ids.clear();
5738 self.pressed_this_frame_generation = target_generation;
5739 }
5740
5741 for id in ids {
5742 if !self
5743 .pressed_this_frame_ids
5744 .iter()
5745 .any(|existing| existing.id == id.id)
5746 {
5747 self.pressed_this_frame_ids.push(id.clone());
5748 }
5749 }
5750 }
5751
5752 pub fn on_hover(&mut self, callback: Box<dyn FnMut(Id, PointerData)>) {
5753 let open_idx = self.get_open_layout_element();
5754 let elem_id = self.layout_elements[open_idx].id;
5755 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
5756 item.on_hover_fn = Some(callback);
5757 }
5758 }
5759
5760 pub fn pressed(&self) -> bool {
5761 let open_idx = self.get_open_layout_element();
5762 let elem_id = self.layout_elements[open_idx].id;
5763 self.pressed_element_ids.iter().any(|eid| eid.id == elem_id)
5764 }
5765
5766 pub fn just_pressed(&self) -> bool {
5767 let open_idx = self.get_open_layout_element();
5768 let elem_id = self.layout_elements[open_idx].id;
5769 if self.pressed_this_frame_generation == self.generation
5770 && self
5771 .pressed_this_frame_ids
5772 .iter()
5773 .any(|eid| eid.id == elem_id)
5774 {
5775 return true;
5776 }
5777
5778 self.pressed_element_ids.iter().any(|eid| eid.id == elem_id)
5779 && (self.pointer_info.state == PointerDataInteractionState::PressedThisFrame
5780 || self.keyboard_press_this_frame_generation == self.generation)
5781 }
5782
5783 pub fn just_released(&self) -> bool {
5784 if self.released_this_frame_generation != self.generation {
5785 return false;
5786 }
5787
5788 let open_idx = self.get_open_layout_element();
5789 let elem_id = self.layout_elements[open_idx].id;
5790 self.released_this_frame_ids
5791 .iter()
5792 .any(|eid| eid.id == elem_id)
5793 }
5794
5795 pub fn set_press_callbacks(
5796 &mut self,
5797 on_press: Option<Box<dyn FnMut(Id, PointerData)>>,
5798 on_release: Option<Box<dyn FnMut(Id, PointerData)>>,
5799 ) {
5800 let open_idx = self.get_open_layout_element();
5801 let elem_id = self.layout_elements[open_idx].id;
5802 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
5803 item.on_press_fn = on_press;
5804 item.on_release_fn = on_release;
5805 }
5806 }
5807
5808 pub fn focused(&self) -> bool {
5810 let open_idx = self.get_open_layout_element();
5811 let elem_id = self.layout_elements[open_idx].id;
5812 self.focused_element_id == elem_id && elem_id != 0
5813 }
5814
5815 pub fn focused_element(&self) -> Option<Id> {
5817 if self.focused_element_id != 0 {
5818 self.layout_element_map
5819 .get(&self.focused_element_id)
5820 .map(|item| item.element_id.clone())
5821 } else {
5822 None
5823 }
5824 }
5825
5826 pub fn set_focus(&mut self, element_id: u32) {
5828 self.change_focus(element_id);
5829 }
5830
5831 pub fn clear_focus(&mut self) {
5833 self.change_focus(0);
5834 }
5835
5836 pub(crate) fn change_focus(&mut self, new_id: u32) {
5838 let old_id = self.focused_element_id;
5839 if old_id == new_id {
5840 return;
5841 }
5842 self.focused_element_id = new_id;
5843 if new_id == 0 {
5844 self.focus_from_keyboard = false;
5845 }
5846
5847 if old_id != 0 {
5849 if let Some(item) = self.layout_element_map.get_mut(&old_id) {
5850 let id_copy = item.element_id.clone();
5851 if let Some(ref mut callback) = item.on_unfocus_fn {
5852 callback(id_copy);
5853 }
5854 }
5855 }
5856
5857 if new_id != 0 {
5859 if let Some(item) = self.layout_element_map.get_mut(&new_id) {
5860 let id_copy = item.element_id.clone();
5861 if let Some(ref mut callback) = item.on_focus_fn {
5862 callback(id_copy);
5863 }
5864 }
5865 }
5866 }
5867
5868 #[allow(dead_code)]
5871 pub(crate) fn fire_press(&mut self, element_id: u32) {
5872 if let Some(item) = self.layout_element_map.get_mut(&element_id) {
5873 let id_copy = item.element_id.clone();
5874 if let Some(ref mut callback) = item.on_press_fn {
5875 callback(id_copy, PointerData::default());
5876 }
5877 }
5878 }
5879
5880 pub fn set_focus_callbacks(
5881 &mut self,
5882 on_focus: Option<Box<dyn FnMut(Id)>>,
5883 on_unfocus: Option<Box<dyn FnMut(Id)>>,
5884 ) {
5885 let open_idx = self.get_open_layout_element();
5886 let elem_id = self.layout_elements[open_idx].id;
5887 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
5888 item.on_focus_fn = on_focus;
5889 item.on_unfocus_fn = on_unfocus;
5890 }
5891 }
5892
5893 pub fn set_text_input_callbacks(
5895 &mut self,
5896 on_changed: Option<Box<dyn FnMut(&str)>>,
5897 on_submit: Option<Box<dyn FnMut(&str)>>,
5898 ) {
5899 let open_idx = self.get_open_layout_element();
5900 let elem_id = self.layout_elements[open_idx].id;
5901 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
5902 item.on_text_changed_fn = on_changed;
5903 item.on_text_submit_fn = on_submit;
5904 }
5905 }
5906
5907 pub fn is_text_input_focused(&self) -> bool {
5909 if self.focused_element_id == 0 {
5910 return false;
5911 }
5912 self.text_edit_states.contains_key(&self.focused_element_id)
5913 }
5914
5915 pub fn is_focused_text_input_multiline(&self) -> bool {
5917 if self.focused_element_id == 0 {
5918 return false;
5919 }
5920 self.text_input_element_ids.iter()
5921 .position(|&id| id == self.focused_element_id)
5922 .and_then(|idx| self.text_input_configs.get(idx))
5923 .map_or(false, |cfg| cfg.is_multiline)
5924 }
5925
5926 pub fn get_text_value(&self, element_id: u32) -> &str {
5928 self.text_edit_states
5929 .get(&element_id)
5930 .map(|state| state.text.as_str())
5931 .unwrap_or("")
5932 }
5933
5934 pub fn set_text_value(&mut self, element_id: u32, value: &str) {
5936 let state = self.text_edit_states
5937 .entry(element_id)
5938 .or_insert_with(crate::text_input::TextEditState::default);
5939 state.text = value.to_string();
5940 #[cfg(feature = "text-styling")]
5941 let max_pos = crate::text_input::styling::cursor_len(&state.text);
5942 #[cfg(not(feature = "text-styling"))]
5943 let max_pos = state.text.chars().count();
5944 if state.cursor_pos > max_pos {
5945 state.cursor_pos = max_pos;
5946 }
5947 state.selection_anchor = None;
5948 state.reset_blink();
5949 }
5950
5951 pub fn get_cursor_pos(&self, element_id: u32) -> usize {
5954 self.text_edit_states
5955 .get(&element_id)
5956 .map(|state| state.cursor_pos)
5957 .unwrap_or(0)
5958 }
5959
5960 pub fn set_cursor_pos(&mut self, element_id: u32, pos: usize) {
5964 if let Some(state) = self.text_edit_states.get_mut(&element_id) {
5965 #[cfg(feature = "text-styling")]
5966 let max_pos = crate::text_input::styling::cursor_len(&state.text);
5967 #[cfg(not(feature = "text-styling"))]
5968 let max_pos = state.text.chars().count();
5969 state.cursor_pos = pos.min(max_pos);
5970 state.selection_anchor = None;
5971 state.reset_blink();
5972 }
5973 }
5974
5975 pub fn get_selection_range(&self, element_id: u32) -> Option<(usize, usize)> {
5978 self.text_edit_states
5979 .get(&element_id)
5980 .and_then(|state| state.selection_range())
5981 }
5982
5983 pub fn set_selection(&mut self, element_id: u32, anchor: usize, cursor: usize) {
5987 if let Some(state) = self.text_edit_states.get_mut(&element_id) {
5988 #[cfg(feature = "text-styling")]
5989 let max_pos = crate::text_input::styling::cursor_len(&state.text);
5990 #[cfg(not(feature = "text-styling"))]
5991 let max_pos = state.text.chars().count();
5992 state.selection_anchor = Some(anchor.min(max_pos));
5993 state.cursor_pos = cursor.min(max_pos);
5994 state.reset_blink();
5995 }
5996 }
5997
5998 pub fn is_element_pressed(&self, element_id: u32) -> bool {
6000 self.pressed_element_ids.iter().any(|eid| eid.id == element_id)
6001 }
6002
6003 pub fn is_element_just_pressed(&self, element_id: u32) -> bool {
6005 if self.pressed_this_frame_generation == self.generation
6006 && self
6007 .pressed_this_frame_ids
6008 .iter()
6009 .any(|eid| eid.id == element_id)
6010 {
6011 return true;
6012 }
6013
6014 self.pressed_element_ids.iter().any(|eid| eid.id == element_id)
6015 && (self.pointer_info.state == PointerDataInteractionState::PressedThisFrame
6016 || self.keyboard_press_this_frame_generation == self.generation)
6017 }
6018
6019 pub fn is_element_just_released(&self, element_id: u32) -> bool {
6021 self.released_this_frame_generation == self.generation
6022 && self
6023 .released_this_frame_ids
6024 .iter()
6025 .any(|eid| eid.id == element_id)
6026 }
6027
6028 pub fn process_text_input_char(&mut self, ch: char) -> bool {
6031 if !self.is_text_input_focused() {
6032 return false;
6033 }
6034 let elem_id = self.focused_element_id;
6035
6036 let max_length = self.text_input_element_ids.iter()
6038 .position(|&id| id == elem_id)
6039 .and_then(|idx| self.text_input_configs.get(idx))
6040 .and_then(|cfg| cfg.max_length);
6041
6042 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
6043 let old_text = state.text.clone();
6044 state.push_undo(crate::text_input::UndoActionKind::InsertChar);
6045 #[cfg(feature = "text-styling")]
6046 {
6047 state.insert_char_styled(ch, max_length);
6048 }
6049 #[cfg(not(feature = "text-styling"))]
6050 {
6051 state.insert_text(&ch.to_string(), max_length);
6052 }
6053 if state.text != old_text {
6054 let new_text = state.text.clone();
6055 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
6057 if let Some(ref mut callback) = item.on_text_changed_fn {
6058 callback(&new_text);
6059 }
6060 }
6061 }
6062 true
6063 } else {
6064 false
6065 }
6066 }
6067
6068 pub fn process_text_input_action(&mut self, action: TextInputAction) -> bool {
6072 if !self.is_text_input_focused() {
6073 return false;
6074 }
6075 let elem_id = self.focused_element_id;
6076
6077 let config_idx = self.text_input_element_ids.iter()
6079 .position(|&id| id == elem_id);
6080 let (max_length, is_multiline, font_asset, font_size) = config_idx
6081 .and_then(|idx| self.text_input_configs.get(idx))
6082 .map(|cfg| (cfg.max_length, cfg.is_multiline, cfg.font_asset, cfg.font_size))
6083 .unwrap_or((None, false, None, 16));
6084
6085 let visual_lines_opt = if is_multiline {
6087 let visible_width = self.layout_element_map
6088 .get(&elem_id)
6089 .map(|item| item.bounding_box.width)
6090 .unwrap_or(0.0);
6091 if visible_width > 0.0 {
6092 if let Some(state) = self.text_edit_states.get(&elem_id) {
6093 if let Some(ref measure_fn) = self.measure_text_fn {
6094 Some(crate::text_input::wrap_lines(
6095 &state.text,
6096 visible_width,
6097 font_asset,
6098 font_size,
6099 measure_fn.as_ref(),
6100 ))
6101 } else { None }
6102 } else { None }
6103 } else { None }
6104 } else { None };
6105
6106 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
6107 let old_text = state.text.clone();
6108
6109 match &action {
6111 TextInputAction::Backspace => state.push_undo(crate::text_input::UndoActionKind::Backspace),
6112 TextInputAction::Delete => state.push_undo(crate::text_input::UndoActionKind::Delete),
6113 TextInputAction::BackspaceWord => state.push_undo(crate::text_input::UndoActionKind::DeleteWord),
6114 TextInputAction::DeleteWord => state.push_undo(crate::text_input::UndoActionKind::DeleteWord),
6115 TextInputAction::Cut => state.push_undo(crate::text_input::UndoActionKind::Cut),
6116 TextInputAction::Paste { .. } => state.push_undo(crate::text_input::UndoActionKind::Paste),
6117 TextInputAction::Submit if is_multiline => state.push_undo(crate::text_input::UndoActionKind::InsertChar),
6118 _ => {}
6119 }
6120
6121 match action {
6122 TextInputAction::MoveLeft { shift } => {
6123 #[cfg(feature = "text-styling")]
6124 { state.move_left_styled(shift); }
6125 #[cfg(not(feature = "text-styling"))]
6126 { state.move_left(shift); }
6127 }
6128 TextInputAction::MoveRight { shift } => {
6129 #[cfg(feature = "text-styling")]
6130 { state.move_right_styled(shift); }
6131 #[cfg(not(feature = "text-styling"))]
6132 { state.move_right(shift); }
6133 }
6134 TextInputAction::MoveWordLeft { shift } => {
6135 #[cfg(feature = "text-styling")]
6136 { state.move_word_left_styled(shift); }
6137 #[cfg(not(feature = "text-styling"))]
6138 { state.move_word_left(shift); }
6139 }
6140 TextInputAction::MoveWordRight { shift } => {
6141 #[cfg(feature = "text-styling")]
6142 { state.move_word_right_styled(shift); }
6143 #[cfg(not(feature = "text-styling"))]
6144 { state.move_word_right(shift); }
6145 }
6146 TextInputAction::MoveHome { shift } => {
6147 #[cfg(not(feature = "text-styling"))]
6149 {
6150 if let Some(ref vl) = visual_lines_opt {
6151 let new_pos = crate::text_input::visual_line_home(vl, state.cursor_pos);
6152 if shift && state.selection_anchor.is_none() {
6153 state.selection_anchor = Some(state.cursor_pos);
6154 }
6155 state.cursor_pos = new_pos;
6156 if !shift { state.selection_anchor = None; }
6157 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
6158 state.reset_blink();
6159 } else {
6160 state.move_home(shift);
6161 }
6162 }
6163 #[cfg(feature = "text-styling")]
6164 {
6165 state.move_home_styled(shift);
6166 }
6167 }
6168 TextInputAction::MoveEnd { shift } => {
6169 #[cfg(not(feature = "text-styling"))]
6170 {
6171 if let Some(ref vl) = visual_lines_opt {
6172 let new_pos = crate::text_input::visual_line_end(vl, state.cursor_pos);
6173 if shift && state.selection_anchor.is_none() {
6174 state.selection_anchor = Some(state.cursor_pos);
6175 }
6176 state.cursor_pos = new_pos;
6177 if !shift { state.selection_anchor = None; }
6178 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
6179 state.reset_blink();
6180 } else {
6181 state.move_end(shift);
6182 }
6183 }
6184 #[cfg(feature = "text-styling")]
6185 {
6186 state.move_end_styled(shift);
6187 }
6188 }
6189 TextInputAction::MoveUp { shift } => {
6190 #[cfg(not(feature = "text-styling"))]
6191 {
6192 if let Some(ref vl) = visual_lines_opt {
6193 let new_pos = crate::text_input::visual_move_up(vl, state.cursor_pos);
6194 if shift && state.selection_anchor.is_none() {
6195 state.selection_anchor = Some(state.cursor_pos);
6196 }
6197 state.cursor_pos = new_pos;
6198 if !shift { state.selection_anchor = None; }
6199 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
6200 state.reset_blink();
6201 } else {
6202 state.move_up(shift);
6203 }
6204 }
6205 #[cfg(feature = "text-styling")]
6206 {
6207 state.move_up_styled(shift, visual_lines_opt.as_deref());
6208 }
6209 }
6210 TextInputAction::MoveDown { shift } => {
6211 #[cfg(not(feature = "text-styling"))]
6212 {
6213 if let Some(ref vl) = visual_lines_opt {
6214 let text_len = state.text.chars().count();
6215 let new_pos = crate::text_input::visual_move_down(vl, state.cursor_pos, text_len);
6216 if shift && state.selection_anchor.is_none() {
6217 state.selection_anchor = Some(state.cursor_pos);
6218 }
6219 state.cursor_pos = new_pos;
6220 if !shift { state.selection_anchor = None; }
6221 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
6222 state.reset_blink();
6223 } else {
6224 state.move_down(shift);
6225 }
6226 }
6227 #[cfg(feature = "text-styling")]
6228 {
6229 state.move_down_styled(shift, visual_lines_opt.as_deref());
6230 }
6231 }
6232 TextInputAction::Backspace => {
6233 #[cfg(feature = "text-styling")]
6234 { state.backspace_styled(); }
6235 #[cfg(not(feature = "text-styling"))]
6236 { state.backspace(); }
6237 }
6238 TextInputAction::Delete => {
6239 #[cfg(feature = "text-styling")]
6240 { state.delete_forward_styled(); }
6241 #[cfg(not(feature = "text-styling"))]
6242 { state.delete_forward(); }
6243 }
6244 TextInputAction::BackspaceWord => {
6245 #[cfg(feature = "text-styling")]
6246 { state.backspace_word_styled(); }
6247 #[cfg(not(feature = "text-styling"))]
6248 { state.backspace_word(); }
6249 }
6250 TextInputAction::DeleteWord => {
6251 #[cfg(feature = "text-styling")]
6252 { state.delete_word_forward_styled(); }
6253 #[cfg(not(feature = "text-styling"))]
6254 { state.delete_word_forward(); }
6255 }
6256 TextInputAction::SelectAll => {
6257 #[cfg(feature = "text-styling")]
6258 { state.select_all_styled(); }
6259 #[cfg(not(feature = "text-styling"))]
6260 { state.select_all(); }
6261 }
6262 TextInputAction::Copy => {
6263 }
6265 TextInputAction::Cut => {
6266 #[cfg(feature = "text-styling")]
6267 { state.delete_selection_styled(); }
6268 #[cfg(not(feature = "text-styling"))]
6269 { state.delete_selection(); }
6270 }
6271 TextInputAction::Paste { text } => {
6272 #[cfg(feature = "text-styling")]
6273 {
6274 let escaped = crate::text_input::styling::escape_str(&text);
6275 state.insert_text_styled(&escaped, max_length);
6276 }
6277 #[cfg(not(feature = "text-styling"))]
6278 {
6279 state.insert_text(&text, max_length);
6280 }
6281 }
6282 TextInputAction::Submit => {
6283 if is_multiline {
6284 #[cfg(feature = "text-styling")]
6285 { state.insert_text_styled("\n", max_length); }
6286 #[cfg(not(feature = "text-styling"))]
6287 { state.insert_text("\n", max_length); }
6288 } else {
6289 let text = state.text.clone();
6290 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
6292 if let Some(ref mut callback) = item.on_text_submit_fn {
6293 callback(&text);
6294 }
6295 }
6296 return true;
6297 }
6298 }
6299 TextInputAction::Undo => {
6300 state.undo();
6301 }
6302 TextInputAction::Redo => {
6303 state.redo();
6304 }
6305 }
6306 if state.text != old_text {
6307 let new_text = state.text.clone();
6308 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
6309 if let Some(ref mut callback) = item.on_text_changed_fn {
6310 callback(&new_text);
6311 }
6312 }
6313 }
6314 true
6315 } else {
6316 false
6317 }
6318 }
6319
6320 pub fn update_text_input_blink_timers(&mut self) {
6322 let dt = self.frame_delta_time as f64;
6323 for state in self.text_edit_states.values_mut() {
6324 state.cursor_blink_timer += dt;
6325 }
6326 }
6327
6328 pub fn update_text_input_scroll(&mut self) {
6330 let focused = self.focused_element_id;
6331 if focused == 0 {
6332 return;
6333 }
6334 let (visible_width, visible_height) = self.layout_element_map
6336 .get(&focused)
6337 .map(|item| (item.bounding_box.width, item.bounding_box.height))
6338 .unwrap_or((0.0, 0.0));
6339 if visible_width <= 0.0 {
6340 return;
6341 }
6342
6343 if let Some(state) = self.text_edit_states.get(&focused) {
6345 let config_idx = self.text_input_element_ids.iter()
6346 .position(|&id| id == focused);
6347 if let Some(idx) = config_idx {
6348 if let Some(cfg) = self.text_input_configs.get(idx) {
6349 if let Some(ref measure_fn) = self.measure_text_fn {
6350 let disp_text = crate::text_input::display_text(
6351 &state.text,
6352 &cfg.placeholder,
6353 cfg.is_password && !cfg.is_multiline,
6354 );
6355 if !state.text.is_empty() {
6356 if cfg.is_multiline {
6357 let visual_lines = crate::text_input::wrap_lines(
6359 &disp_text,
6360 visible_width,
6361 cfg.font_asset,
6362 cfg.font_size,
6363 measure_fn.as_ref(),
6364 );
6365 #[cfg(feature = "text-styling")]
6366 let raw_cursor = state.cursor_pos_raw();
6367 #[cfg(not(feature = "text-styling"))]
6368 let raw_cursor = state.cursor_pos;
6369 let (cursor_line, cursor_col) = crate::text_input::cursor_to_visual_pos(&visual_lines, raw_cursor);
6370 let vl_text = visual_lines.get(cursor_line).map(|vl| vl.text.as_str()).unwrap_or("");
6371 let line_positions = crate::text_input::compute_char_x_positions(
6372 vl_text,
6373 cfg.font_asset,
6374 cfg.font_size,
6375 measure_fn.as_ref(),
6376 );
6377 let cursor_x = line_positions.get(cursor_col).copied().unwrap_or(0.0);
6378 let cfg_font_asset = cfg.font_asset;
6379 let cfg_font_size = cfg.font_size;
6380 let cfg_line_height_val = cfg.line_height;
6381 let natural_height = self.font_height(cfg_font_asset, cfg_font_size);
6382 let line_height = if cfg_line_height_val > 0 { cfg_line_height_val as f32 } else { natural_height };
6383 if let Some(state_mut) = self.text_edit_states.get_mut(&focused) {
6384 state_mut.ensure_cursor_visible(cursor_x, visible_width);
6385 state_mut.ensure_cursor_visible_vertical(cursor_line, line_height, visible_height);
6386 }
6387 } else {
6388 let char_x_positions = crate::text_input::compute_char_x_positions(
6389 &disp_text,
6390 cfg.font_asset,
6391 cfg.font_size,
6392 measure_fn.as_ref(),
6393 );
6394 #[cfg(feature = "text-styling")]
6395 let raw_cursor = state.cursor_pos_raw();
6396 #[cfg(not(feature = "text-styling"))]
6397 let raw_cursor = state.cursor_pos;
6398 let cursor_x = char_x_positions
6399 .get(raw_cursor)
6400 .copied()
6401 .unwrap_or(0.0);
6402 if let Some(state_mut) = self.text_edit_states.get_mut(&focused) {
6403 state_mut.ensure_cursor_visible(cursor_x, visible_width);
6404 }
6405 }
6406 } else if let Some(state_mut) = self.text_edit_states.get_mut(&focused) {
6407 state_mut.scroll_offset = 0.0;
6408 state_mut.scroll_offset_y = 0.0;
6409 }
6410 }
6411 }
6412 }
6413 }
6414 }
6415
6416 pub fn update_text_input_pointer_scroll(&mut self, scroll_delta: Vector2, touch_input_active: bool) -> bool {
6421 for idle in self.text_input_scrollbar_idle_frames.values_mut() {
6422 *idle = idle.saturating_add(1);
6423 }
6424
6425 let mut consumed_scroll = false;
6426
6427 let focused = self.focused_element_id;
6428
6429 let has_scroll = scroll_delta.x.abs() > 0.01 || scroll_delta.y.abs() > 0.01;
6431 if has_scroll {
6432 let p = self.pointer_info.position;
6433 let hovered_ti = self.text_input_element_ids.iter().enumerate().find(|&(_, &id)| {
6435 self.layout_element_map.get(&id)
6436 .map(|item| {
6437 let bb = item.bounding_box;
6438 p.x >= bb.x && p.x <= bb.x + bb.width
6439 && p.y >= bb.y && p.y <= bb.y + bb.height
6440 })
6441 .unwrap_or(false)
6442 });
6443 if let Some((idx, &elem_id)) = hovered_ti {
6444 let is_multiline = self.text_input_configs.get(idx)
6445 .map(|cfg| cfg.is_multiline)
6446 .unwrap_or(false);
6447 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
6448 if is_multiline {
6449 if scroll_delta.y.abs() > 0.01 {
6450 state.scroll_offset_y -= scroll_delta.y;
6451 if state.scroll_offset_y < 0.0 {
6452 state.scroll_offset_y = 0.0;
6453 }
6454 }
6455 } else {
6456 let h_delta = if scroll_delta.x.abs() > scroll_delta.y.abs() {
6457 scroll_delta.x
6458 } else {
6459 scroll_delta.y
6460 };
6461 if h_delta.abs() > 0.01 {
6462 state.scroll_offset -= h_delta;
6463 if state.scroll_offset < 0.0 {
6464 state.scroll_offset = 0.0;
6465 }
6466 }
6467 }
6468 consumed_scroll = true;
6469 self.text_input_scrollbar_idle_frames.insert(elem_id, 0);
6470 }
6471 }
6472 }
6473
6474 if focused == 0 {
6476 if self.text_input_drag_active {
6477 let pointer_state = self.pointer_info.state;
6478 if matches!(pointer_state, PointerDataInteractionState::ReleasedThisFrame | PointerDataInteractionState::Released) {
6479 self.text_input_drag_active = false;
6480 self.text_input_drag_from_touch = false;
6481 }
6482 }
6483 self.text_input_scrollbar_drag_active = false;
6484 return consumed_scroll;
6485 }
6486
6487 let ti_index = self
6488 .text_input_element_ids
6489 .iter()
6490 .position(|&id| id == focused);
6491 let Some(ti_index) = ti_index else {
6492 if self.text_input_drag_active {
6493 let pointer_state = self.pointer_info.state;
6494 if matches!(pointer_state, PointerDataInteractionState::ReleasedThisFrame | PointerDataInteractionState::Released) {
6495 self.text_input_drag_active = false;
6496 self.text_input_drag_from_touch = false;
6497 }
6498 }
6499 self.text_input_scrollbar_drag_active = false;
6500 return consumed_scroll;
6501 };
6502 let Some(ti_cfg) = self.text_input_configs.get(ti_index).cloned() else {
6503 self.text_input_scrollbar_drag_active = false;
6504 return consumed_scroll;
6505 };
6506 let is_multiline = ti_cfg.is_multiline;
6507
6508 let pointer_over_focused = self.layout_element_map.get(&focused)
6509 .map(|item| {
6510 let bb = item.bounding_box;
6511 let p = self.pointer_info.position;
6512 p.x >= bb.x && p.x <= bb.x + bb.width
6513 && p.y >= bb.y && p.y <= bb.y + bb.height
6514 })
6515 .unwrap_or(false);
6516
6517 let pointer = self.pointer_info.position;
6518 let pointer_state = self.pointer_info.state;
6519
6520 match pointer_state {
6521 PointerDataInteractionState::PressedThisFrame => {
6522 if pointer_over_focused {
6523 let mut started_scrollbar_drag = false;
6524
6525 if let Some(scrollbar_cfg) = ti_cfg.scrollbar {
6526 if let Some(item) = self.layout_element_map.get(&focused) {
6527 if let Some(state) = self.text_edit_states.get(&focused).cloned() {
6528 let bbox = item.bounding_box;
6529 let (content_width, content_height) =
6530 self.text_input_content_size(&state, &ti_cfg, bbox.width);
6531
6532 let idle_frames = self
6533 .text_input_scrollbar_idle_frames
6534 .get(&focused)
6535 .copied()
6536 .unwrap_or(0);
6537 let alpha = scrollbar_visibility_alpha(scrollbar_cfg, idle_frames);
6538
6539 if alpha > 0.0 {
6540 if ti_cfg.is_multiline {
6541 if let Some(geo) = compute_vertical_scrollbar_geometry(
6542 bbox,
6543 content_height,
6544 state.scroll_offset_y,
6545 scrollbar_cfg,
6546 ) {
6547 if point_is_inside_rect(pointer, geo.thumb_bbox) {
6548 started_scrollbar_drag = true;
6549 self.text_input_scrollbar_drag_active = true;
6550 self.text_input_scrollbar_drag_vertical = true;
6551 self.text_input_scrollbar_drag_origin = pointer.y;
6552 self.text_input_scrollbar_drag_scroll_origin =
6553 state.scroll_offset_y;
6554 }
6555 }
6556 }
6557
6558 if !started_scrollbar_drag {
6559 if let Some(geo) = compute_horizontal_scrollbar_geometry(
6560 bbox,
6561 content_width,
6562 state.scroll_offset,
6563 scrollbar_cfg,
6564 ) {
6565 if point_is_inside_rect(pointer, geo.thumb_bbox) {
6566 started_scrollbar_drag = true;
6567 self.text_input_scrollbar_drag_active = true;
6568 self.text_input_scrollbar_drag_vertical = false;
6569 self.text_input_scrollbar_drag_origin = pointer.x;
6570 self.text_input_scrollbar_drag_scroll_origin =
6571 state.scroll_offset;
6572 }
6573 }
6574 }
6575 }
6576 }
6577 }
6578 }
6579
6580 if started_scrollbar_drag {
6581 self.text_input_drag_active = false;
6582 self.text_input_drag_from_touch = false;
6583 self.text_input_drag_element_id = focused;
6584 self.pending_text_click = None;
6585 self.text_input_scrollbar_idle_frames.insert(focused, 0);
6586 consumed_scroll = true;
6587 return consumed_scroll;
6588 }
6589
6590 let (scroll_x, scroll_y) = self.text_edit_states.get(&focused)
6591 .map(|s| (s.scroll_offset, s.scroll_offset_y))
6592 .unwrap_or((0.0, 0.0));
6593 self.text_input_drag_active = true;
6594 self.text_input_drag_origin = pointer;
6595 self.text_input_drag_scroll_origin = Vector2::new(scroll_x, scroll_y);
6596 self.text_input_drag_element_id = focused;
6597 self.text_input_drag_from_touch = touch_input_active;
6598 self.text_input_scrollbar_drag_active = false;
6599 }
6600 }
6601 PointerDataInteractionState::Pressed => {
6602 if self.text_input_scrollbar_drag_active {
6603 if let Some(item) = self.layout_element_map.get(&self.text_input_drag_element_id)
6604 {
6605 if let Some(state_snapshot) = self
6606 .text_edit_states
6607 .get(&self.text_input_drag_element_id)
6608 .cloned()
6609 {
6610 if let Some(scrollbar_cfg) = ti_cfg.scrollbar {
6611 let bbox = item.bounding_box;
6612 let (content_width, content_height) = self
6613 .text_input_content_size(&state_snapshot, &ti_cfg, bbox.width);
6614
6615 if let Some(state) =
6616 self.text_edit_states.get_mut(&self.text_input_drag_element_id)
6617 {
6618 if self.text_input_scrollbar_drag_vertical {
6619 if let Some(geo) = compute_vertical_scrollbar_geometry(
6620 bbox,
6621 content_height,
6622 self.text_input_scrollbar_drag_scroll_origin,
6623 scrollbar_cfg,
6624 ) {
6625 let delta = pointer.y - self.text_input_scrollbar_drag_origin;
6626 let new_scroll = if geo.thumb_travel <= 0.0 {
6627 0.0
6628 } else {
6629 self.text_input_scrollbar_drag_scroll_origin
6630 + delta * (geo.max_scroll / geo.thumb_travel)
6631 };
6632 state.scroll_offset_y = new_scroll.clamp(0.0, geo.max_scroll);
6633 }
6634 } else if let Some(geo) = compute_horizontal_scrollbar_geometry(
6635 bbox,
6636 content_width,
6637 self.text_input_scrollbar_drag_scroll_origin,
6638 scrollbar_cfg,
6639 ) {
6640 let delta = pointer.x - self.text_input_scrollbar_drag_origin;
6641 let new_scroll = if geo.thumb_travel <= 0.0 {
6642 0.0
6643 } else {
6644 self.text_input_scrollbar_drag_scroll_origin
6645 + delta * (geo.max_scroll / geo.thumb_travel)
6646 };
6647 state.scroll_offset = new_scroll.clamp(0.0, geo.max_scroll);
6648 }
6649 }
6650
6651 self.text_input_scrollbar_idle_frames
6652 .insert(self.text_input_drag_element_id, 0);
6653 consumed_scroll = true;
6654 }
6655 }
6656 }
6657 } else if self.text_input_drag_active {
6658 let drag_id = self.text_input_drag_element_id;
6659 if ti_cfg.drag_select && !self.text_input_drag_from_touch {
6660 consumed_scroll = true;
6661
6662 if let (Some(item), Some(measure_fn)) = (
6663 self.layout_element_map.get(&drag_id),
6664 self.measure_text_fn.as_ref(),
6665 ) {
6666 let bbox = item.bounding_box;
6667 let click_x = pointer.x - bbox.x;
6668 let click_y = pointer.y - bbox.y;
6669
6670 if let Some(state) = self.text_edit_states.get_mut(&drag_id) {
6671 if ti_cfg.is_multiline {
6672 if click_y < 0.0 {
6673 state.scroll_offset_y = (state.scroll_offset_y + click_y).max(0.0);
6674 } else if click_y > bbox.height {
6675 state.scroll_offset_y += click_y - bbox.height;
6676 }
6677 } else {
6678 if click_x < 0.0 {
6679 state.scroll_offset = (state.scroll_offset + click_x).max(0.0);
6680 } else if click_x > bbox.width {
6681 state.scroll_offset += click_x - bbox.width;
6682 }
6683 }
6684 }
6685
6686 if let Some(state_snapshot) = self.text_edit_states.get(&drag_id).cloned() {
6687 let clamped_x = click_x.clamp(0.0, bbox.width.max(0.0));
6688 let clamped_y = click_y.clamp(0.0, bbox.height.max(0.0));
6689 let disp_text = crate::text_input::display_text(
6690 &state_snapshot.text,
6691 &ti_cfg.placeholder,
6692 ti_cfg.is_password,
6693 );
6694
6695 if !state_snapshot.text.is_empty() {
6696 if ti_cfg.is_multiline {
6697 let visual_lines = crate::text_input::wrap_lines(
6698 &disp_text,
6699 bbox.width,
6700 ti_cfg.font_asset,
6701 ti_cfg.font_size,
6702 measure_fn.as_ref(),
6703 );
6704 if !visual_lines.is_empty() {
6705 let line_height = if ti_cfg.line_height > 0 {
6706 ti_cfg.line_height as f32
6707 } else {
6708 let config = crate::text::TextConfig {
6709 font_asset: ti_cfg.font_asset,
6710 font_size: ti_cfg.font_size,
6711 ..Default::default()
6712 };
6713 measure_fn("Mg", &config).height
6714 };
6715
6716 let adjusted_y = clamped_y + state_snapshot.scroll_offset_y;
6717 let clicked_line = (adjusted_y / line_height).floor().max(0.0) as usize;
6718 let clicked_line = clicked_line.min(visual_lines.len().saturating_sub(1));
6719
6720 let vl = &visual_lines[clicked_line];
6721 let line_char_x_positions = crate::text_input::compute_char_x_positions(
6722 &vl.text,
6723 ti_cfg.font_asset,
6724 ti_cfg.font_size,
6725 measure_fn.as_ref(),
6726 );
6727 let col = crate::text_input::find_nearest_char_boundary(
6728 clamped_x,
6729 &line_char_x_positions,
6730 );
6731 let raw_pos = vl.global_char_start + col;
6732
6733 if let Some(state) = self.text_edit_states.get_mut(&drag_id) {
6734 #[cfg(feature = "text-styling")]
6735 {
6736 let visual_pos = crate::text_input::styling::raw_to_cursor(&state.text, raw_pos);
6737 state.click_to_cursor_styled(visual_pos, true);
6738 }
6739 #[cfg(not(feature = "text-styling"))]
6740 {
6741 if state.selection_anchor.is_none() {
6742 state.selection_anchor = Some(state.cursor_pos);
6743 }
6744 state.cursor_pos = raw_pos;
6745 if state.selection_anchor == Some(state.cursor_pos) {
6746 state.selection_anchor = None;
6747 }
6748 state.reset_blink();
6749 }
6750 }
6751 }
6752 } else {
6753 let char_x_positions = crate::text_input::compute_char_x_positions(
6754 &disp_text,
6755 ti_cfg.font_asset,
6756 ti_cfg.font_size,
6757 measure_fn.as_ref(),
6758 );
6759 let adjusted_x = clamped_x + state_snapshot.scroll_offset;
6760
6761 if let Some(state) = self.text_edit_states.get_mut(&drag_id) {
6762 #[cfg(feature = "text-styling")]
6763 {
6764 let raw_pos = crate::text_input::find_nearest_char_boundary(
6765 adjusted_x,
6766 &char_x_positions,
6767 );
6768 let visual_pos = crate::text_input::styling::raw_to_cursor(&state.text, raw_pos);
6769 state.click_to_cursor_styled(visual_pos, true);
6770 }
6771 #[cfg(not(feature = "text-styling"))]
6772 {
6773 state.click_to_cursor(adjusted_x, &char_x_positions, true);
6774 }
6775 }
6776 }
6777 }
6778 }
6779
6780 self.text_input_scrollbar_idle_frames.insert(drag_id, 0);
6781 }
6782 } else if let Some(state) = self.text_edit_states.get_mut(&drag_id) {
6783 if is_multiline {
6784 let drag_delta_y = self.text_input_drag_origin.y - pointer.y;
6785 state.scroll_offset_y = (self.text_input_drag_scroll_origin.y + drag_delta_y).max(0.0);
6786 } else {
6787 let drag_delta_x = self.text_input_drag_origin.x - pointer.x;
6788 state.scroll_offset = (self.text_input_drag_scroll_origin.x + drag_delta_x).max(0.0);
6789 }
6790 self.text_input_scrollbar_idle_frames
6791 .insert(drag_id, 0);
6792 }
6793 }
6794 }
6795 PointerDataInteractionState::ReleasedThisFrame
6796 | PointerDataInteractionState::Released => {
6797 self.text_input_drag_active = false;
6798 self.text_input_drag_from_touch = false;
6799 self.text_input_scrollbar_drag_active = false;
6800 }
6801 }
6802 consumed_scroll
6803 }
6804
6805 pub fn clamp_text_input_scroll(&mut self) {
6809 for i in 0..self.text_input_element_ids.len() {
6810 let elem_id = self.text_input_element_ids[i];
6811 let cfg = match self.text_input_configs.get(i) {
6812 Some(c) => c,
6813 None => continue,
6814 };
6815
6816 let font_asset = cfg.font_asset;
6817 let font_size = cfg.font_size;
6818 let cfg_line_height = cfg.line_height;
6819 let is_multiline = cfg.is_multiline;
6820 let is_password = cfg.is_password;
6821
6822 let (visible_width, visible_height) = self.layout_element_map.get(&elem_id)
6823 .map(|item| (item.bounding_box.width, item.bounding_box.height))
6824 .unwrap_or((200.0, 0.0));
6825
6826 let text_empty = self.text_edit_states.get(&elem_id)
6827 .map(|s| s.text.is_empty())
6828 .unwrap_or(true);
6829
6830 if text_empty {
6831 if let Some(state_mut) = self.text_edit_states.get_mut(&elem_id) {
6832 state_mut.scroll_offset = 0.0;
6833 state_mut.scroll_offset_y = 0.0;
6834 }
6835 continue;
6836 }
6837
6838 if let Some(ref measure_fn) = self.measure_text_fn {
6839 let disp_text = self.text_edit_states.get(&elem_id)
6840 .map(|s| crate::text_input::display_text(&s.text, "", is_password && !is_multiline))
6841 .unwrap_or_default();
6842
6843 if is_multiline {
6844 let visual_lines = crate::text_input::wrap_lines(
6845 &disp_text,
6846 visible_width,
6847 font_asset,
6848 font_size,
6849 measure_fn.as_ref(),
6850 );
6851 let natural_height = self.font_height(font_asset, font_size);
6852 let font_height = if cfg_line_height > 0 { cfg_line_height as f32 } else { natural_height };
6853 let total_height = visual_lines.len() as f32 * font_height;
6854 let max_scroll = (total_height - visible_height).max(0.0);
6855 if let Some(state_mut) = self.text_edit_states.get_mut(&elem_id) {
6856 if state_mut.scroll_offset_y > max_scroll {
6857 state_mut.scroll_offset_y = max_scroll;
6858 }
6859 }
6860 } else {
6861 let char_x_positions = crate::text_input::compute_char_x_positions(
6863 &disp_text,
6864 font_asset,
6865 font_size,
6866 measure_fn.as_ref(),
6867 );
6868 let total_width = char_x_positions.last().copied().unwrap_or(0.0);
6869 let max_scroll = (total_width - visible_width).max(0.0);
6870 if let Some(state_mut) = self.text_edit_states.get_mut(&elem_id) {
6871 if state_mut.scroll_offset > max_scroll {
6872 state_mut.scroll_offset = max_scroll;
6873 }
6874 }
6875 }
6876 }
6877 }
6878 }
6879
6880 pub fn cycle_focus(&mut self, reverse: bool) {
6883 if self.focusable_elements.is_empty() {
6884 return;
6885 }
6886 self.focus_from_keyboard = true;
6887
6888 let mut sorted: Vec<FocusableEntry> = self.focusable_elements.clone();
6890 sorted.sort_by(|a, b| {
6891 match (a.tab_index, b.tab_index) {
6892 (Some(ai), Some(bi)) => ai.cmp(&bi).then(a.insertion_order.cmp(&b.insertion_order)),
6893 (Some(_), None) => std::cmp::Ordering::Less,
6894 (None, Some(_)) => std::cmp::Ordering::Greater,
6895 (None, None) => a.insertion_order.cmp(&b.insertion_order),
6896 }
6897 });
6898
6899 let current_pos = sorted
6901 .iter()
6902 .position(|e| e.element_id == self.focused_element_id);
6903
6904 let next_pos = match current_pos {
6905 Some(pos) => {
6906 if reverse {
6907 if pos == 0 { sorted.len() - 1 } else { pos - 1 }
6908 } else {
6909 if pos + 1 >= sorted.len() { 0 } else { pos + 1 }
6910 }
6911 }
6912 None => {
6913 if reverse { sorted.len() - 1 } else { 0 }
6915 }
6916 };
6917
6918 self.change_focus(sorted[next_pos].element_id);
6919 }
6920
6921 pub fn arrow_focus(&mut self, direction: ArrowDirection) {
6923 if self.focused_element_id == 0 {
6924 return;
6925 }
6926 self.focus_from_keyboard = true;
6927 if let Some(config) = self.accessibility_configs.get(&self.focused_element_id) {
6928 let target = match direction {
6929 ArrowDirection::Left => config.focus_left,
6930 ArrowDirection::Right => config.focus_right,
6931 ArrowDirection::Up => config.focus_up,
6932 ArrowDirection::Down => config.focus_down,
6933 };
6934 if let Some(target_id) = target {
6935 self.change_focus(target_id);
6936 }
6937 }
6938 }
6939
6940 pub fn handle_keyboard_activation(&mut self, pressed: bool, released: bool) {
6942 if self.focused_element_id == 0 {
6943 return;
6944 }
6945 if pressed {
6946 let id_copy = self
6947 .layout_element_map
6948 .get(&self.focused_element_id)
6949 .map(|item| item.element_id.clone());
6950 if let Some(id) = id_copy {
6951 self.pressed_element_ids = vec![id.clone()];
6952 let pressed_now = self.pressed_element_ids.clone();
6953 self.track_just_pressed_ids(&pressed_now);
6954 self.keyboard_press_this_frame_generation = self.release_query_generation();
6955 if let Some(item) = self.layout_element_map.get_mut(&self.focused_element_id) {
6956 if let Some(ref mut callback) = item.on_press_fn {
6957 callback(id, PointerData::default());
6958 }
6959 }
6960 }
6961 }
6962 if released {
6963 let pressed = std::mem::take(&mut self.pressed_element_ids);
6964 self.track_just_released_ids(&pressed);
6965 for eid in pressed.iter() {
6966 if let Some(item) = self.layout_element_map.get_mut(&eid.id) {
6967 if let Some(ref mut callback) = item.on_release_fn {
6968 callback(eid.clone(), PointerData::default());
6969 }
6970 }
6971 }
6972 }
6973 }
6974
6975 pub fn pointer_over(&self, element_id: Id) -> bool {
6976 self.pointer_over_ids.iter().any(|eid| eid.id == element_id.id)
6977 }
6978
6979 pub fn get_pointer_over_ids(&self) -> &[Id] {
6980 &self.pointer_over_ids
6981 }
6982
6983 pub fn get_element_data(&self, id: Id) -> Option<BoundingBox> {
6984 self.layout_element_map
6985 .get(&id.id)
6986 .map(|item| item.bounding_box)
6987 }
6988
6989 pub fn get_scroll_container_data(&self, id: Id) -> ScrollContainerData {
6990 for scd in &self.scroll_container_datas {
6991 if scd.element_id == id.id {
6992 return ScrollContainerData {
6993 scroll_position: scd.scroll_position,
6994 scroll_container_dimensions: Dimensions::new(
6995 scd.bounding_box.width,
6996 scd.bounding_box.height,
6997 ),
6998 content_dimensions: scd.content_size,
6999 horizontal: scd.scroll_x_enabled,
7000 vertical: scd.scroll_y_enabled,
7001 found: true,
7002 };
7003 }
7004 }
7005 ScrollContainerData::default()
7006 }
7007
7008 pub fn get_scroll_offset(&self) -> Vector2 {
7009 let open_idx = self.get_open_layout_element();
7010 let elem_id = self.layout_elements[open_idx].id;
7011 for scd in &self.scroll_container_datas {
7012 if scd.element_id == elem_id {
7013 return scd.scroll_position;
7014 }
7015 }
7016 Vector2::default()
7017 }
7018
7019 pub fn set_scroll_position(&mut self, id: Id, position: Vector2) {
7020 for scd in &mut self.scroll_container_datas {
7021 if scd.element_id == id.id {
7022 let max_scroll_x = (scd.content_size.width - scd.bounding_box.width).max(0.0);
7023 let max_scroll_y = (scd.content_size.height - scd.bounding_box.height).max(0.0);
7024
7025 let clamped_x = position.x.clamp(0.0, max_scroll_x);
7026 let clamped_y = position.y.clamp(0.0, max_scroll_y);
7027 scd.scroll_position.x = -clamped_x;
7028 scd.scroll_position.y = -clamped_y;
7029 if scd.scrollbar.is_some() {
7030 scd.scrollbar_idle_frames = 0;
7031 }
7032 return;
7033 }
7034 }
7035
7036 if let Some(ti_idx) = self.text_input_element_ids.iter().position(|&elem_id| elem_id == id.id) {
7037 let Some(config) = self.text_input_configs.get(ti_idx).cloned() else {
7038 return;
7039 };
7040
7041 let Some(state_snapshot) = self.text_edit_states.get(&id.id).cloned() else {
7042 return;
7043 };
7044
7045 let (visible_width, visible_height) = self
7046 .layout_element_map
7047 .get(&id.id)
7048 .map(|item| (item.bounding_box.width, item.bounding_box.height))
7049 .unwrap_or((0.0, 0.0));
7050
7051 let (content_width, content_height) =
7052 self.text_input_content_size(&state_snapshot, &config, visible_width);
7053
7054 let max_scroll_x = (content_width - visible_width).max(0.0);
7055 let max_scroll_y = (content_height - visible_height).max(0.0);
7056
7057 if let Some(state) = self.text_edit_states.get_mut(&id.id) {
7058 state.scroll_offset = position.x.clamp(0.0, max_scroll_x);
7059 state.scroll_offset_y = position.y.clamp(0.0, max_scroll_y);
7060 }
7061
7062 self.text_input_scrollbar_idle_frames.insert(id.id, 0);
7063 }
7064 }
7065
7066 fn render_scrollbar_geometry(
7067 &mut self,
7068 id: u32,
7069 z_index: i16,
7070 config: ScrollbarConfig,
7071 alpha_mul: f32,
7072 vertical: Option<ScrollbarAxisGeometry>,
7073 horizontal: Option<ScrollbarAxisGeometry>,
7074 ) {
7075 if alpha_mul <= 0.0 {
7076 return;
7077 }
7078
7079 let thumb_color = apply_alpha(config.thumb_color, alpha_mul);
7080 let track_color = config.track_color.map(|c| apply_alpha(c, alpha_mul));
7081
7082 if let Some(v) = vertical {
7083 if let Some(track) = track_color {
7084 self.add_render_command(InternalRenderCommand {
7085 bounding_box: v.track_bbox,
7086 command_type: RenderCommandType::Rectangle,
7087 render_data: InternalRenderData::Rectangle {
7088 background_color: track,
7089 corner_radius: config.corner_radius.into(),
7090 },
7091 id: hash_number(id, 8001).id,
7092 z_index,
7093 ..Default::default()
7094 });
7095 }
7096
7097 self.add_render_command(InternalRenderCommand {
7098 bounding_box: v.thumb_bbox,
7099 command_type: RenderCommandType::Rectangle,
7100 render_data: InternalRenderData::Rectangle {
7101 background_color: thumb_color,
7102 corner_radius: config.corner_radius.into(),
7103 },
7104 id: hash_number(id, 8002).id,
7105 z_index,
7106 ..Default::default()
7107 });
7108 }
7109
7110 if let Some(h) = horizontal {
7111 if let Some(track) = track_color {
7112 self.add_render_command(InternalRenderCommand {
7113 bounding_box: h.track_bbox,
7114 command_type: RenderCommandType::Rectangle,
7115 render_data: InternalRenderData::Rectangle {
7116 background_color: track,
7117 corner_radius: config.corner_radius.into(),
7118 },
7119 id: hash_number(id, 8003).id,
7120 z_index,
7121 ..Default::default()
7122 });
7123 }
7124
7125 self.add_render_command(InternalRenderCommand {
7126 bounding_box: h.thumb_bbox,
7127 command_type: RenderCommandType::Rectangle,
7128 render_data: InternalRenderData::Rectangle {
7129 background_color: thumb_color,
7130 corner_radius: config.corner_radius.into(),
7131 },
7132 id: hash_number(id, 8004).id,
7133 z_index,
7134 ..Default::default()
7135 });
7136 }
7137 }
7138
7139 fn text_input_content_size(
7140 &mut self,
7141 state: &crate::text_input::TextEditState,
7142 config: &crate::text_input::TextInputConfig,
7143 visible_width: f32,
7144 ) -> (f32, f32) {
7145 if state.text.is_empty() || visible_width <= 0.0 {
7146 return (0.0, 0.0);
7147 }
7148
7149 let Some(measure_fn) = self.measure_text_fn.as_ref() else {
7150 return (0.0, 0.0);
7151 };
7152
7153 let display_text = crate::text_input::display_text(
7154 &state.text,
7155 &config.placeholder,
7156 config.is_password && !config.is_multiline,
7157 );
7158
7159 if config.is_multiline {
7160 let visual_lines = crate::text_input::wrap_lines(
7161 &display_text,
7162 visible_width,
7163 config.font_asset,
7164 config.font_size,
7165 measure_fn.as_ref(),
7166 );
7167
7168 let content_width = visual_lines
7169 .iter()
7170 .map(|line| {
7171 crate::text_input::compute_char_x_positions(
7172 &line.text,
7173 config.font_asset,
7174 config.font_size,
7175 measure_fn.as_ref(),
7176 )
7177 .last()
7178 .copied()
7179 .unwrap_or(0.0)
7180 })
7181 .fold(0.0_f32, |a, b| a.max(b));
7182
7183 let natural_height = self.font_height(config.font_asset, config.font_size);
7184 let line_height = if config.line_height > 0 {
7185 config.line_height as f32
7186 } else {
7187 natural_height
7188 };
7189
7190 (content_width, visual_lines.len() as f32 * line_height)
7191 } else {
7192 let positions = crate::text_input::compute_char_x_positions(
7193 &display_text,
7194 config.font_asset,
7195 config.font_size,
7196 measure_fn.as_ref(),
7197 );
7198 (
7199 positions.last().copied().unwrap_or(0.0),
7200 self.font_height(config.font_asset, config.font_size),
7201 )
7202 }
7203 }
7204
7205 const DEBUG_VIEW_DEFAULT_WIDTH: f32 = 400.0;
7206 const DEBUG_VIEW_ROW_HEIGHT: f32 = 30.0;
7207 const DEBUG_VIEW_OUTER_PADDING: u16 = 10;
7208 const DEBUG_VIEW_INDENT_WIDTH: u16 = 16;
7209
7210 const DEBUG_COLOR_1: Color = Color::rgba(58.0, 56.0, 52.0, 255.0);
7211 const DEBUG_COLOR_2: Color = Color::rgba(62.0, 60.0, 58.0, 255.0);
7212 const DEBUG_COLOR_3: Color = Color::rgba(141.0, 133.0, 135.0, 255.0);
7213 const DEBUG_COLOR_4: Color = Color::rgba(238.0, 226.0, 231.0, 255.0);
7214 #[allow(dead_code)]
7215 const DEBUG_COLOR_SELECTED_ROW: Color = Color::rgba(102.0, 80.0, 78.0, 255.0);
7216 const DEBUG_HIGHLIGHT_COLOR: Color = Color::rgba(168.0, 66.0, 28.0, 100.0);
7217
7218 #[cfg(feature = "text-styling")]
7221 fn debug_escape_str(s: &str) -> String {
7222 let mut result = String::with_capacity(s.len());
7223 for c in s.chars() {
7224 match c {
7225 '{' | '}' | '|' | '\\' => {
7226 result.push('\\');
7227 result.push(c);
7228 }
7229 _ => result.push(c),
7230 }
7231 }
7232 result
7233 }
7234
7235 fn debug_text(&mut self, text: &'static str, config_index: usize) {
7239 #[cfg(feature = "text-styling")]
7240 {
7241 let escaped = Self::debug_escape_str(text);
7242 self.open_text_element(&escaped, config_index);
7243 }
7244 #[cfg(not(feature = "text-styling"))]
7245 {
7246 self.open_text_element(text, config_index);
7247 }
7248 }
7249
7250 fn debug_raw_text(&mut self, text: &str, config_index: usize) {
7254 #[cfg(feature = "text-styling")]
7255 {
7256 let escaped = Self::debug_escape_str(text);
7257 self.open_text_element(&escaped, config_index);
7258 }
7259 #[cfg(not(feature = "text-styling"))]
7260 {
7261 self.open_text_element(text, config_index);
7262 }
7263 }
7264
7265 fn debug_int_text(&mut self, value: f32, config_index: usize) {
7267 let s = format!("{}", value as i32);
7268 self.open_text_element(&s, config_index);
7269 }
7270
7271 fn debug_float_text(&mut self, value: f32, config_index: usize) {
7273 let s = format!("{:.2}", value);
7274 self.open_text_element(&s, config_index);
7275 }
7276
7277 fn debug_open(&mut self, decl: &ElementDeclaration<CustomElementData>) {
7279 self.open_element();
7280 self.configure_open_element(decl);
7281 }
7282
7283 fn debug_open_id(&mut self, name: &str, decl: &ElementDeclaration<CustomElementData>) {
7285 self.open_element_with_id(&hash_string(name, 0));
7286 self.configure_open_element(decl);
7287 }
7288
7289 fn debug_open_idi(&mut self, name: &str, offset: u32, decl: &ElementDeclaration<CustomElementData>) {
7291 self.open_element_with_id(&hash_string_with_offset(name, offset, 0));
7292 self.configure_open_element(decl);
7293 }
7294
7295 fn debug_get_config_type_label(config_type: ElementConfigType) -> (&'static str, Color) {
7296 match config_type {
7297 ElementConfigType::Shared => ("Shared", Color::rgba(243.0, 134.0, 48.0, 255.0)),
7298 ElementConfigType::Text => ("Text", Color::rgba(105.0, 210.0, 231.0, 255.0)),
7299 ElementConfigType::Aspect => ("Aspect", Color::rgba(101.0, 149.0, 194.0, 255.0)),
7300 ElementConfigType::Image => ("Image", Color::rgba(121.0, 189.0, 154.0, 255.0)),
7301 ElementConfigType::Floating => ("Floating", Color::rgba(250.0, 105.0, 0.0, 255.0)),
7302 ElementConfigType::Clip => ("Overflow", Color::rgba(242.0, 196.0, 90.0, 255.0)),
7303 ElementConfigType::Border => ("Border", Color::rgba(108.0, 91.0, 123.0, 255.0)),
7304 ElementConfigType::Custom => ("Custom", Color::rgba(11.0, 72.0, 107.0, 255.0)),
7305 ElementConfigType::TextInput => ("TextInput", Color::rgba(52.0, 152.0, 219.0, 255.0)),
7306 }
7307 }
7308
7309 fn render_debug_layout_sizing(&mut self, sizing: SizingAxis, config_index: usize) {
7311 let label = match sizing.type_ {
7312 SizingType::Fit => "FIT",
7313 SizingType::Grow => "GROW",
7314 SizingType::Percent => "PERCENT",
7315 SizingType::Fixed => "FIXED",
7316 };
7318 self.debug_text(label, config_index);
7319 if matches!(sizing.type_, SizingType::Grow | SizingType::Fit | SizingType::Fixed) {
7320 self.debug_text("(", config_index);
7321 let mut wrote_any = false;
7322
7323 if sizing.type_ == SizingType::Grow && !float_equal(sizing.grow_weight, 1.0) {
7324 self.debug_text("weight: ", config_index);
7325 self.debug_float_text(sizing.grow_weight, config_index);
7326 wrote_any = true;
7327 }
7328
7329 if sizing.min_max.min != 0.0 {
7330 if wrote_any {
7331 self.debug_text(", ", config_index);
7332 }
7333 self.debug_text("min: ", config_index);
7334 self.debug_int_text(sizing.min_max.min, config_index);
7335 wrote_any = true;
7336 }
7337 if sizing.min_max.max != MAXFLOAT {
7338 if wrote_any {
7339 self.debug_text(", ", config_index);
7340 }
7341 self.debug_text("max: ", config_index);
7342 self.debug_int_text(sizing.min_max.max, config_index);
7343 }
7344 self.debug_text(")", config_index);
7345 } else if sizing.type_ == SizingType::Percent {
7346 self.debug_text("(", config_index);
7347 self.debug_int_text(sizing.percent * 100.0, config_index);
7348 self.debug_text("%)", config_index);
7349 }
7350 }
7351
7352 fn render_debug_view_element_config_header(
7354 &mut self,
7355 element_id_string: StringId,
7356 config_type: ElementConfigType,
7357 _info_title_config: usize,
7358 ) {
7359 let (label, label_color) = Self::debug_get_config_type_label(config_type);
7360 self.render_debug_view_category_header(label, label_color, element_id_string);
7361 }
7362
7363 fn render_debug_view_category_header(
7365 &mut self,
7366 label: &str,
7367 label_color: Color,
7368 element_id_string: StringId,
7369 ) {
7370 let bg = Color::rgba(label_color.r, label_color.g, label_color.b, 90.0);
7371 self.debug_open(&ElementDeclaration {
7372 layout: LayoutConfig {
7373 sizing: SizingConfig {
7374 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
7375 ..Default::default()
7376 },
7377 padding: PaddingConfig {
7378 left: Self::DEBUG_VIEW_OUTER_PADDING,
7379 right: Self::DEBUG_VIEW_OUTER_PADDING,
7380 top: Self::DEBUG_VIEW_OUTER_PADDING,
7381 bottom: Self::DEBUG_VIEW_OUTER_PADDING,
7382 },
7383 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
7384 ..Default::default()
7385 },
7386 ..Default::default()
7387 });
7388 {
7389 self.debug_open(&ElementDeclaration {
7391 layout: LayoutConfig {
7392 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
7393 ..Default::default()
7394 },
7395 background_color: bg,
7396 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
7397 border: BorderConfig {
7398 color: label_color,
7399 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7400 ..Default::default()
7401 },
7402 ..Default::default()
7403 });
7404 {
7405 let tc = self.store_text_element_config(TextConfig {
7406 color: Self::DEBUG_COLOR_4,
7407 font_size: 16,
7408 ..Default::default()
7409 });
7410 self.debug_raw_text(label, tc);
7411 }
7412 self.close_element();
7413 self.debug_open(&ElementDeclaration {
7415 layout: LayoutConfig {
7416 sizing: SizingConfig {
7417 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
7418 ..Default::default()
7419 },
7420 ..Default::default()
7421 },
7422 ..Default::default()
7423 });
7424 self.close_element();
7425 let tc = self.store_text_element_config(TextConfig {
7427 color: Self::DEBUG_COLOR_3,
7428 font_size: 16,
7429 wrap_mode: WrapMode::None,
7430 ..Default::default()
7431 });
7432 if !element_id_string.is_empty() {
7433 self.debug_raw_text(element_id_string.as_str(), tc);
7434 }
7435 }
7436 self.close_element();
7437 }
7438
7439 fn render_debug_view_color(&mut self, color: Color, config_index: usize) {
7441 self.debug_open(&ElementDeclaration {
7442 layout: LayoutConfig {
7443 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
7444 ..Default::default()
7445 },
7446 ..Default::default()
7447 });
7448 {
7449 self.debug_text("{ r: ", config_index);
7450 self.debug_int_text(color.r, config_index);
7451 self.debug_text(", g: ", config_index);
7452 self.debug_int_text(color.g, config_index);
7453 self.debug_text(", b: ", config_index);
7454 self.debug_int_text(color.b, config_index);
7455 self.debug_text(", a: ", config_index);
7456 self.debug_int_text(color.a, config_index);
7457 self.debug_text(" }", config_index);
7458 self.debug_open(&ElementDeclaration {
7460 layout: LayoutConfig {
7461 sizing: SizingConfig {
7462 width: SizingAxis {
7463 type_: SizingType::Fixed,
7464 min_max: SizingMinMax { min: 10.0, max: 10.0 },
7465 ..Default::default()
7466 },
7467 ..Default::default()
7468 },
7469 ..Default::default()
7470 },
7471 ..Default::default()
7472 });
7473 self.close_element();
7474 let swatch_size = Self::DEBUG_VIEW_ROW_HEIGHT - 8.0;
7476 self.debug_open(&ElementDeclaration {
7477 layout: LayoutConfig {
7478 sizing: SizingConfig {
7479 width: SizingAxis {
7480 type_: SizingType::Fixed,
7481 min_max: SizingMinMax { min: swatch_size, max: swatch_size },
7482 ..Default::default()
7483 },
7484 height: SizingAxis {
7485 type_: SizingType::Fixed,
7486 min_max: SizingMinMax { min: swatch_size, max: swatch_size },
7487 ..Default::default()
7488 },
7489 },
7490 ..Default::default()
7491 },
7492 background_color: color,
7493 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
7494 border: BorderConfig {
7495 color: Self::DEBUG_COLOR_4,
7496 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7497 ..Default::default()
7498 },
7499 ..Default::default()
7500 });
7501 self.close_element();
7502 }
7503 self.close_element();
7504 }
7505
7506 fn render_debug_view_corner_radius(
7508 &mut self,
7509 cr: CornerRadius,
7510 info_text_config: usize,
7511 ) {
7512 self.debug_open(&ElementDeclaration::default());
7513 {
7514 self.debug_text("topLeft: ", info_text_config);
7515 self.debug_float_text(cr.top_left, info_text_config);
7516 }
7517 self.close_element();
7518 self.debug_open(&ElementDeclaration::default());
7519 {
7520 self.debug_text("topRight: ", info_text_config);
7521 self.debug_float_text(cr.top_right, info_text_config);
7522 }
7523 self.close_element();
7524 self.debug_open(&ElementDeclaration::default());
7525 {
7526 self.debug_text("bottomLeft: ", info_text_config);
7527 self.debug_float_text(cr.bottom_left, info_text_config);
7528 }
7529 self.close_element();
7530 self.debug_open(&ElementDeclaration::default());
7531 {
7532 self.debug_text("bottomRight: ", info_text_config);
7533 self.debug_float_text(cr.bottom_right, info_text_config);
7534 }
7535 self.close_element();
7536 }
7537
7538 fn debug_border_position_name(position: BorderPosition) -> &'static str {
7539 match position {
7540 BorderPosition::Outside => "OUTSIDE",
7541 BorderPosition::Middle => "MIDDLE",
7542 BorderPosition::Inside => "INSIDE",
7543 }
7544 }
7545
7546 fn render_debug_scrollbar_config(
7547 &mut self,
7548 scrollbar: ScrollbarConfig,
7549 info_text_config: usize,
7550 info_title_config: usize,
7551 ) {
7552 self.debug_text("Width", info_title_config);
7553 self.debug_float_text(scrollbar.width, info_text_config);
7554
7555 self.debug_text("Corner Radius", info_title_config);
7556 self.debug_float_text(scrollbar.corner_radius, info_text_config);
7557
7558 self.debug_text("Min Thumb Size", info_title_config);
7559 self.debug_float_text(scrollbar.min_thumb_size, info_text_config);
7560
7561 self.debug_text("Hide After Frames", info_title_config);
7562 if let Some(frames) = scrollbar.hide_after_frames {
7563 self.debug_int_text(frames as f32, info_text_config);
7564 } else {
7565 self.debug_text("none", info_text_config);
7566 }
7567
7568 self.debug_text("Thumb Color", info_title_config);
7569 self.render_debug_view_color(scrollbar.thumb_color, info_text_config);
7570
7571 self.debug_text("Track Color", info_title_config);
7572 if let Some(track) = scrollbar.track_color {
7573 self.render_debug_view_color(track, info_text_config);
7574 } else {
7575 self.debug_text("none", info_text_config);
7576 }
7577 }
7578
7579 fn render_debug_shader_uniform_value(&mut self, value: &crate::shaders::ShaderUniformValue, config_index: usize) {
7581 use crate::shaders::ShaderUniformValue;
7582 match value {
7583 ShaderUniformValue::Float(v) => {
7584 self.debug_float_text(*v, config_index);
7585 }
7586 ShaderUniformValue::Vec2(v) => {
7587 self.debug_text("(", config_index);
7588 self.debug_float_text(v[0], config_index);
7589 self.debug_text(", ", config_index);
7590 self.debug_float_text(v[1], config_index);
7591 self.debug_text(")", config_index);
7592 }
7593 ShaderUniformValue::Vec3(v) => {
7594 self.debug_text("(", config_index);
7595 self.debug_float_text(v[0], config_index);
7596 self.debug_text(", ", config_index);
7597 self.debug_float_text(v[1], config_index);
7598 self.debug_text(", ", config_index);
7599 self.debug_float_text(v[2], config_index);
7600 self.debug_text(")", config_index);
7601 }
7602 ShaderUniformValue::Vec4(v) => {
7603 self.debug_text("(", config_index);
7604 self.debug_float_text(v[0], config_index);
7605 self.debug_text(", ", config_index);
7606 self.debug_float_text(v[1], config_index);
7607 self.debug_text(", ", config_index);
7608 self.debug_float_text(v[2], config_index);
7609 self.debug_text(", ", config_index);
7610 self.debug_float_text(v[3], config_index);
7611 self.debug_text(")", config_index);
7612 }
7613 ShaderUniformValue::Int(v) => {
7614 self.debug_int_text(*v as f32, config_index);
7615 }
7616 ShaderUniformValue::Mat4(_) => {
7617 self.debug_text("[mat4]", config_index);
7618 }
7619 }
7620 }
7621
7622 fn render_debug_layout_elements_list(
7624 &mut self,
7625 initial_roots_length: usize,
7626 highlighted_row: i32,
7627 ) -> (i32, i32) {
7628 let row_height = Self::DEBUG_VIEW_ROW_HEIGHT;
7629 let indent_width = Self::DEBUG_VIEW_INDENT_WIDTH;
7630 let mut row_count: i32 = 0;
7631 let mut selected_element_row_index: i32 = 0;
7632 let mut highlighted_element_id: u32 = 0;
7633
7634 let scroll_item_layout = LayoutConfig {
7635 sizing: SizingConfig {
7636 height: SizingAxis {
7637 type_: SizingType::Fixed,
7638 min_max: SizingMinMax { min: row_height, max: row_height },
7639 ..Default::default()
7640 },
7641 ..Default::default()
7642 },
7643 child_gap: 6,
7644 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
7645 ..Default::default()
7646 };
7647
7648 let name_text_config = TextConfig {
7649 color: Self::DEBUG_COLOR_4,
7650 font_size: 16,
7651 wrap_mode: WrapMode::None,
7652 ..Default::default()
7653 };
7654
7655 for root_index in 0..initial_roots_length {
7656 let mut dfs_buffer: Vec<i32> = Vec::new();
7657 let root_layout_index = self.layout_element_tree_roots[root_index].layout_element_index;
7658 dfs_buffer.push(root_layout_index);
7659 let mut visited: Vec<bool> = vec![false; self.layout_elements.len()];
7660
7661 if root_index > 0 {
7663 self.debug_open_idi("Ply__DebugView_EmptyRowOuter", root_index as u32, &ElementDeclaration {
7664 layout: LayoutConfig {
7665 sizing: SizingConfig {
7666 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
7667 ..Default::default()
7668 },
7669 padding: PaddingConfig { left: indent_width / 2, right: 0, top: 0, bottom: 0 },
7670 ..Default::default()
7671 },
7672 ..Default::default()
7673 });
7674 {
7675 self.debug_open_idi("Ply__DebugView_EmptyRow", root_index as u32, &ElementDeclaration {
7676 layout: LayoutConfig {
7677 sizing: SizingConfig {
7678 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
7679 height: SizingAxis {
7680 type_: SizingType::Fixed,
7681 min_max: SizingMinMax { min: row_height, max: row_height },
7682 ..Default::default()
7683 },
7684 },
7685 ..Default::default()
7686 },
7687 border: BorderConfig {
7688 color: Self::DEBUG_COLOR_3,
7689 width: BorderWidth { top: 1, ..Default::default() },
7690 ..Default::default()
7691 },
7692 ..Default::default()
7693 });
7694 self.close_element();
7695 }
7696 self.close_element();
7697 row_count += 1;
7698 }
7699
7700 while !dfs_buffer.is_empty() {
7701 let current_element_index = *dfs_buffer.last().unwrap() as usize;
7702 let depth = dfs_buffer.len() - 1;
7703
7704 if visited[depth] {
7705 let is_text = self.element_has_config(current_element_index, ElementConfigType::Text);
7707 let children_len = self.layout_elements[current_element_index].children_length;
7708 if !is_text && children_len > 0 {
7709 self.close_element();
7710 self.close_element();
7711 self.close_element();
7712 }
7713 dfs_buffer.pop();
7714 continue;
7715 }
7716
7717 if highlighted_row == row_count {
7719 if self.pointer_info.state == PointerDataInteractionState::PressedThisFrame {
7720 let elem_id = self.layout_elements[current_element_index].id;
7721 if self.debug_selected_element_id == elem_id {
7722 self.debug_selected_element_id = 0; } else {
7724 self.debug_selected_element_id = elem_id;
7725 }
7726 }
7727 highlighted_element_id = self.layout_elements[current_element_index].id;
7728 }
7729
7730 visited[depth] = true;
7731 let current_elem_id = self.layout_elements[current_element_index].id;
7732
7733 let bounding_box = self.layout_element_map
7735 .get(¤t_elem_id)
7736 .map(|item| item.bounding_box)
7737 .unwrap_or_default();
7738 let collision = self.layout_element_map
7739 .get(¤t_elem_id)
7740 .map(|item| item.collision)
7741 .unwrap_or(false);
7742 let collapsed = self.layout_element_map
7743 .get(¤t_elem_id)
7744 .map(|item| item.collapsed)
7745 .unwrap_or(false);
7746
7747 let offscreen = self.element_is_offscreen(&bounding_box);
7748
7749 if self.debug_selected_element_id == current_elem_id {
7750 selected_element_row_index = row_count;
7751 }
7752
7753 let row_bg = if self.debug_selected_element_id == current_elem_id {
7755 Color::rgba(217.0, 91.0, 67.0, 40.0) } else {
7757 Color::rgba(0.0, 0.0, 0.0, 0.0)
7758 };
7759 self.debug_open_idi("Ply__DebugView_ElementOuter", current_elem_id, &ElementDeclaration {
7760 layout: scroll_item_layout,
7761 background_color: row_bg,
7762 ..Default::default()
7763 });
7764 {
7765 let is_text = self.element_has_config(current_element_index, ElementConfigType::Text);
7766 let children_len = self.layout_elements[current_element_index].children_length;
7767
7768 if !is_text && children_len > 0 {
7770 self.debug_open_idi("Ply__DebugView_CollapseElement", current_elem_id, &ElementDeclaration {
7772 layout: LayoutConfig {
7773 sizing: SizingConfig {
7774 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
7775 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
7776 },
7777 child_alignment: ChildAlignmentConfig { x: AlignX::CenterX, y: AlignY::CenterY },
7778 ..Default::default()
7779 },
7780 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
7781 border: BorderConfig {
7782 color: Self::DEBUG_COLOR_3,
7783 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7784 ..Default::default()
7785 },
7786 ..Default::default()
7787 });
7788 {
7789 let tc = self.store_text_element_config(TextConfig {
7790 color: Self::DEBUG_COLOR_4,
7791 font_size: 16,
7792 ..Default::default()
7793 });
7794 if collapsed {
7795 self.debug_text("+", tc);
7796 } else {
7797 self.debug_text("-", tc);
7798 }
7799 }
7800 self.close_element();
7801 } else {
7802 self.debug_open(&ElementDeclaration {
7804 layout: LayoutConfig {
7805 sizing: SizingConfig {
7806 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
7807 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
7808 },
7809 child_alignment: ChildAlignmentConfig { x: AlignX::CenterX, y: AlignY::CenterY },
7810 ..Default::default()
7811 },
7812 ..Default::default()
7813 });
7814 {
7815 self.debug_open(&ElementDeclaration {
7816 layout: LayoutConfig {
7817 sizing: SizingConfig {
7818 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 8.0, max: 8.0 }, ..Default::default() },
7819 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 8.0, max: 8.0 }, ..Default::default() },
7820 },
7821 ..Default::default()
7822 },
7823 background_color: Self::DEBUG_COLOR_3,
7824 corner_radius: CornerRadius { top_left: 2.0, top_right: 2.0, bottom_left: 2.0, bottom_right: 2.0 },
7825 ..Default::default()
7826 });
7827 self.close_element();
7828 }
7829 self.close_element();
7830 }
7831
7832 if collision {
7834 self.debug_open(&ElementDeclaration {
7835 layout: LayoutConfig {
7836 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
7837 ..Default::default()
7838 },
7839 border: BorderConfig {
7840 color: Color::rgba(177.0, 147.0, 8.0, 255.0),
7841 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7842 ..Default::default()
7843 },
7844 ..Default::default()
7845 });
7846 {
7847 let tc = self.store_text_element_config(TextConfig {
7848 color: Self::DEBUG_COLOR_3,
7849 font_size: 16,
7850 ..Default::default()
7851 });
7852 self.debug_text("Duplicate ID", tc);
7853 }
7854 self.close_element();
7855 }
7856
7857 if offscreen {
7859 self.debug_open(&ElementDeclaration {
7860 layout: LayoutConfig {
7861 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
7862 ..Default::default()
7863 },
7864 border: BorderConfig {
7865 color: Self::DEBUG_COLOR_3,
7866 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7867 ..Default::default()
7868 },
7869 ..Default::default()
7870 });
7871 {
7872 let tc = self.store_text_element_config(TextConfig {
7873 color: Self::DEBUG_COLOR_3,
7874 font_size: 16,
7875 ..Default::default()
7876 });
7877 self.debug_text("Offscreen", tc);
7878 }
7879 self.close_element();
7880 }
7881
7882 let id_string = if current_element_index < self.layout_element_id_strings.len() {
7884 self.layout_element_id_strings[current_element_index].clone()
7885 } else {
7886 StringId::empty()
7887 };
7888 if !id_string.is_empty() {
7889 let tc = if offscreen {
7890 self.store_text_element_config(TextConfig {
7891 color: Self::DEBUG_COLOR_3,
7892 font_size: 16,
7893 ..Default::default()
7894 })
7895 } else {
7896 self.store_text_element_config(name_text_config.clone())
7897 };
7898 self.debug_raw_text(id_string.as_str(), tc);
7899 }
7900
7901 let configs_start = self.layout_elements[current_element_index].element_configs.start;
7903 let configs_len = self.layout_elements[current_element_index].element_configs.length;
7904 for ci in 0..configs_len {
7905 let ec = self.element_configs[configs_start + ci as usize];
7906 if ec.config_type == ElementConfigType::Shared {
7907 let shared = self.shared_element_configs[ec.config_index];
7908 let label_color = Color::rgba(243.0, 134.0, 48.0, 90.0);
7909 if shared.background_color.a > 0.0 {
7910 self.debug_open(&ElementDeclaration {
7911 layout: LayoutConfig {
7912 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
7913 ..Default::default()
7914 },
7915 background_color: label_color,
7916 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
7917 border: BorderConfig {
7918 color: label_color,
7919 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7920 ..Default::default()
7921 },
7922 ..Default::default()
7923 });
7924 {
7925 let tc = self.store_text_element_config(TextConfig {
7926 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
7927 font_size: 16,
7928 ..Default::default()
7929 });
7930 self.debug_text("Color", tc);
7931 }
7932 self.close_element();
7933 }
7934 if !shared.corner_radius.is_zero() {
7935 let radius_color = Color::rgba(26.0, 188.0, 156.0, 90.0);
7936 self.debug_open(&ElementDeclaration {
7937 layout: LayoutConfig {
7938 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
7939 ..Default::default()
7940 },
7941 background_color: radius_color,
7942 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
7943 border: BorderConfig {
7944 color: radius_color,
7945 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7946 ..Default::default()
7947 },
7948 ..Default::default()
7949 });
7950 {
7951 let tc = self.store_text_element_config(TextConfig {
7952 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
7953 font_size: 16,
7954 ..Default::default()
7955 });
7956 self.debug_text("Radius", tc);
7957 }
7958 self.close_element();
7959 }
7960 continue;
7961 }
7962 let (label, label_color) = Self::debug_get_config_type_label(ec.config_type);
7963 let bg = Color::rgba(label_color.r, label_color.g, label_color.b, 90.0);
7964 self.debug_open(&ElementDeclaration {
7965 layout: LayoutConfig {
7966 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
7967 ..Default::default()
7968 },
7969 background_color: bg,
7970 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
7971 border: BorderConfig {
7972 color: label_color,
7973 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
7974 ..Default::default()
7975 },
7976 ..Default::default()
7977 });
7978 {
7979 let tc = self.store_text_element_config(TextConfig {
7980 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
7981 font_size: 16,
7982 ..Default::default()
7983 });
7984 self.debug_text(label, tc);
7985 }
7986 self.close_element();
7987
7988 let has_scrollbar = match ec.config_type {
7989 ElementConfigType::Clip => self
7990 .clip_element_configs
7991 .get(ec.config_index)
7992 .and_then(|cfg| cfg.scrollbar)
7993 .is_some(),
7994 ElementConfigType::TextInput => self
7995 .text_input_configs
7996 .get(ec.config_index)
7997 .and_then(|cfg| cfg.scrollbar)
7998 .is_some(),
7999 _ => false,
8000 };
8001 if has_scrollbar {
8002 let bg = Color::rgba(242.0, 196.0, 90.0, 90.0);
8003 let border_color = Color::rgba(242.0, 196.0, 90.0, 255.0);
8004 self.debug_open(&ElementDeclaration {
8005 layout: LayoutConfig {
8006 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
8007 ..Default::default()
8008 },
8009 background_color: bg,
8010 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
8011 border: BorderConfig {
8012 color: border_color,
8013 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
8014 ..Default::default()
8015 },
8016 ..Default::default()
8017 });
8018 {
8019 let tc = self.store_text_element_config(TextConfig {
8020 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
8021 font_size: 16,
8022 ..Default::default()
8023 });
8024 self.debug_text("Scrollbar", tc);
8025 }
8026 self.close_element();
8027 }
8028 }
8029
8030 let has_shaders = self.element_shaders.get(current_element_index)
8032 .map_or(false, |s| !s.is_empty());
8033 if has_shaders {
8034 let badge_color = Color::rgba(155.0, 89.0, 182.0, 90.0);
8035 self.debug_open(&ElementDeclaration {
8036 layout: LayoutConfig {
8037 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
8038 ..Default::default()
8039 },
8040 background_color: badge_color,
8041 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
8042 border: BorderConfig {
8043 color: badge_color,
8044 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
8045 ..Default::default()
8046 },
8047 ..Default::default()
8048 });
8049 {
8050 let tc = self.store_text_element_config(TextConfig {
8051 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
8052 font_size: 16,
8053 ..Default::default()
8054 });
8055 self.debug_text("Shader", tc);
8056 }
8057 self.close_element();
8058 }
8059
8060 let has_effects = self.element_effects.get(current_element_index)
8062 .map_or(false, |e| !e.is_empty());
8063 if has_effects {
8064 let badge_color = Color::rgba(155.0, 89.0, 182.0, 90.0);
8065 self.debug_open(&ElementDeclaration {
8066 layout: LayoutConfig {
8067 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
8068 ..Default::default()
8069 },
8070 background_color: badge_color,
8071 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
8072 border: BorderConfig {
8073 color: badge_color,
8074 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
8075 ..Default::default()
8076 },
8077 ..Default::default()
8078 });
8079 {
8080 let tc = self.store_text_element_config(TextConfig {
8081 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
8082 font_size: 16,
8083 ..Default::default()
8084 });
8085 self.debug_text("Effect", tc);
8086 }
8087 self.close_element();
8088 }
8089
8090 let has_visual_rot = self.element_visual_rotations.get(current_element_index)
8092 .map_or(false, |r| r.is_some());
8093 if has_visual_rot {
8094 let badge_color = Color::rgba(155.0, 89.0, 182.0, 90.0);
8095 self.debug_open(&ElementDeclaration {
8096 layout: LayoutConfig {
8097 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
8098 ..Default::default()
8099 },
8100 background_color: badge_color,
8101 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
8102 border: BorderConfig {
8103 color: badge_color,
8104 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
8105 ..Default::default()
8106 },
8107 ..Default::default()
8108 });
8109 {
8110 let tc = self.store_text_element_config(TextConfig {
8111 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
8112 font_size: 16,
8113 ..Default::default()
8114 });
8115 self.debug_text("VisualRot", tc);
8116 }
8117 self.close_element();
8118 }
8119
8120 let has_shape_rot = self.element_shape_rotations.get(current_element_index)
8122 .map_or(false, |r| r.is_some());
8123 if has_shape_rot {
8124 let badge_color = Color::rgba(26.0, 188.0, 156.0, 90.0);
8125 self.debug_open(&ElementDeclaration {
8126 layout: LayoutConfig {
8127 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
8128 ..Default::default()
8129 },
8130 background_color: badge_color,
8131 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
8132 border: BorderConfig {
8133 color: badge_color,
8134 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
8135 ..Default::default()
8136 },
8137 ..Default::default()
8138 });
8139 {
8140 let tc = self.store_text_element_config(TextConfig {
8141 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
8142 font_size: 16,
8143 ..Default::default()
8144 });
8145 self.debug_text("ShapeRot", tc);
8146 }
8147 self.close_element();
8148 }
8149 }
8150 self.close_element(); let is_text = self.element_has_config(current_element_index, ElementConfigType::Text);
8154 let children_len = self.layout_elements[current_element_index].children_length;
8155 if is_text {
8156 row_count += 1;
8157 let text_data_idx = self.layout_elements[current_element_index].text_data_index;
8158 let text_content = if text_data_idx >= 0 {
8159 self.text_element_data[text_data_idx as usize].text.clone()
8160 } else {
8161 String::new()
8162 };
8163 let raw_tc_idx = if offscreen {
8164 self.store_text_element_config(TextConfig {
8165 color: Self::DEBUG_COLOR_3,
8166 font_size: 16,
8167 ..Default::default()
8168 })
8169 } else {
8170 self.store_text_element_config(name_text_config.clone())
8171 };
8172 self.debug_open(&ElementDeclaration {
8173 layout: LayoutConfig {
8174 sizing: SizingConfig {
8175 height: SizingAxis {
8176 type_: SizingType::Fixed,
8177 min_max: SizingMinMax { min: row_height, max: row_height },
8178 ..Default::default()
8179 },
8180 ..Default::default()
8181 },
8182 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
8183 ..Default::default()
8184 },
8185 ..Default::default()
8186 });
8187 {
8188 self.debug_open(&ElementDeclaration {
8190 layout: LayoutConfig {
8191 sizing: SizingConfig {
8192 width: SizingAxis {
8193 type_: SizingType::Fixed,
8194 min_max: SizingMinMax {
8195 min: (indent_width + 16) as f32,
8196 max: (indent_width + 16) as f32,
8197 },
8198 ..Default::default()
8199 },
8200 ..Default::default()
8201 },
8202 ..Default::default()
8203 },
8204 ..Default::default()
8205 });
8206 self.close_element();
8207 self.debug_text("\"", raw_tc_idx);
8208 if text_content.len() > 40 {
8209 let mut end = 40;
8210 while !text_content.is_char_boundary(end) { end -= 1; }
8211 self.debug_raw_text(&text_content[..end], raw_tc_idx);
8212 self.debug_text("...", raw_tc_idx);
8213 } else if !text_content.is_empty() {
8214 self.debug_raw_text(&text_content, raw_tc_idx);
8215 }
8216 self.debug_text("\"", raw_tc_idx);
8217 }
8218 self.close_element();
8219 } else if children_len > 0 {
8220 self.open_element();
8222 self.configure_open_element(&ElementDeclaration {
8223 layout: LayoutConfig {
8224 padding: PaddingConfig { left: 8, ..Default::default() },
8225 ..Default::default()
8226 },
8227 ..Default::default()
8228 });
8229 self.open_element();
8230 self.configure_open_element(&ElementDeclaration {
8231 layout: LayoutConfig {
8232 padding: PaddingConfig { left: indent_width, ..Default::default() },
8233 ..Default::default()
8234 },
8235 border: BorderConfig {
8236 color: Self::DEBUG_COLOR_3,
8237 width: BorderWidth { left: 1, ..Default::default() },
8238 ..Default::default()
8239 },
8240 ..Default::default()
8241 });
8242 self.open_element();
8243 self.configure_open_element(&ElementDeclaration {
8244 layout: LayoutConfig {
8245 layout_direction: LayoutDirection::TopToBottom,
8246 ..Default::default()
8247 },
8248 ..Default::default()
8249 });
8250 }
8251
8252 row_count += 1;
8253
8254 if !is_text && !collapsed {
8256 let children_start = self.layout_elements[current_element_index].children_start;
8257 let children_length = self.layout_elements[current_element_index].children_length as usize;
8258 for i in (0..children_length).rev() {
8259 let child_idx = self.layout_element_children[children_start + i];
8260 dfs_buffer.push(child_idx);
8261 while visited.len() <= dfs_buffer.len() {
8263 visited.push(false);
8264 }
8265 visited[dfs_buffer.len() - 1] = false;
8266 }
8267 }
8268 }
8269 }
8270
8271 if self.pointer_info.state == PointerDataInteractionState::PressedThisFrame {
8273 let collapse_base_id = hash_string("Ply__DebugView_CollapseElement", 0).base_id;
8274 for i in (0..self.pointer_over_ids.len()).rev() {
8275 let element_id = self.pointer_over_ids[i].clone();
8276 if element_id.base_id == collapse_base_id {
8277 if let Some(item) = self.layout_element_map.get_mut(&element_id.offset) {
8278 item.collapsed = !item.collapsed;
8279 }
8280 break;
8281 }
8282 }
8283 }
8284
8285 let highlight_target = if self.debug_selected_element_id != 0 {
8288 self.debug_selected_element_id
8289 } else {
8290 highlighted_element_id
8291 };
8292 if highlight_target != 0 {
8293 self.debug_open_id("Ply__DebugView_ElementHighlight", &ElementDeclaration {
8294 layout: LayoutConfig {
8295 sizing: SizingConfig {
8296 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8297 height: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8298 },
8299 ..Default::default()
8300 },
8301 floating: FloatingConfig {
8302 parent_id: highlight_target,
8303 z_index: 32767,
8304 pointer_capture_mode: PointerCaptureMode::Passthrough,
8305 attach_to: FloatingAttachToElement::ElementWithId,
8306 ..Default::default()
8307 },
8308 ..Default::default()
8309 });
8310 {
8311 self.debug_open_id("Ply__DebugView_ElementHighlightRectangle", &ElementDeclaration {
8312 layout: LayoutConfig {
8313 sizing: SizingConfig {
8314 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8315 height: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8316 },
8317 ..Default::default()
8318 },
8319 background_color: Self::DEBUG_HIGHLIGHT_COLOR,
8320 ..Default::default()
8321 });
8322 self.close_element();
8323 }
8324 self.close_element();
8325 }
8326
8327 (row_count, selected_element_row_index)
8328 }
8329
8330 fn render_debug_view(&mut self) {
8332 let initial_roots_length = self.layout_element_tree_roots.len();
8333 let initial_elements_length = self.layout_elements.len();
8334 let row_height = Self::DEBUG_VIEW_ROW_HEIGHT;
8335 let outer_padding = Self::DEBUG_VIEW_OUTER_PADDING;
8336 let debug_width = self.debug_view_width;
8337
8338 let info_text_config = self.store_text_element_config(TextConfig {
8339 color: Self::DEBUG_COLOR_4,
8340 font_size: 16,
8341 wrap_mode: WrapMode::None,
8342 ..Default::default()
8343 });
8344 let info_title_config = self.store_text_element_config(TextConfig {
8345 color: Self::DEBUG_COLOR_3,
8346 font_size: 16,
8347 wrap_mode: WrapMode::None,
8348 ..Default::default()
8349 });
8350
8351 let scroll_id = hash_string("Ply__DebugViewOuterScrollPane", 0);
8353 let mut scroll_y_offset: f32 = 0.0;
8354 let detail_panel_height = if self.debug_selected_element_id != 0 { 300.0 } else { 0.0 };
8356 let mut pointer_in_debug_view = self.pointer_info.position.y < self.layout_dimensions.height - detail_panel_height;
8357 for scd in &self.scroll_container_datas {
8358 if scd.element_id == scroll_id.id {
8359 if !self.external_scroll_handling_enabled {
8360 scroll_y_offset = scd.scroll_position.y;
8361 } else {
8362 pointer_in_debug_view = self.pointer_info.position.y + scd.scroll_position.y
8363 < self.layout_dimensions.height - detail_panel_height;
8364 }
8365 break;
8366 }
8367 }
8368
8369 let highlighted_row = if pointer_in_debug_view {
8370 ((self.pointer_info.position.y - scroll_y_offset) / row_height) as i32 - 1
8371 } else {
8372 -1
8373 };
8374 let highlighted_row = if self.pointer_info.position.x < self.layout_dimensions.width - debug_width {
8375 -1
8376 } else {
8377 highlighted_row
8378 };
8379
8380 self.debug_open_id("Ply__DebugView", &ElementDeclaration {
8382 layout: LayoutConfig {
8383 sizing: SizingConfig {
8384 width: SizingAxis {
8385 type_: SizingType::Fixed,
8386 min_max: SizingMinMax { min: debug_width, max: debug_width },
8387 ..Default::default()
8388 },
8389 height: SizingAxis {
8390 type_: SizingType::Fixed,
8391 min_max: SizingMinMax { min: self.layout_dimensions.height, max: self.layout_dimensions.height },
8392 ..Default::default()
8393 },
8394 },
8395 layout_direction: LayoutDirection::TopToBottom,
8396 ..Default::default()
8397 },
8398 floating: FloatingConfig {
8399 z_index: 32765,
8400 attach_points: FloatingAttachPoints {
8401 element_x: AlignX::Right,
8402 element_y: AlignY::CenterY,
8403 parent_x: AlignX::Right,
8404 parent_y: AlignY::CenterY,
8405 },
8406 attach_to: FloatingAttachToElement::Root,
8407 clip_to: FloatingClipToElement::AttachedParent,
8408 ..Default::default()
8409 },
8410 border: BorderConfig {
8411 color: Self::DEBUG_COLOR_3,
8412 width: BorderWidth { bottom: 1, ..Default::default() },
8413 ..Default::default()
8414 },
8415 ..Default::default()
8416 });
8417 {
8418 self.debug_open(&ElementDeclaration {
8420 layout: LayoutConfig {
8421 sizing: SizingConfig {
8422 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8423 height: SizingAxis {
8424 type_: SizingType::Fixed,
8425 min_max: SizingMinMax { min: row_height, max: row_height },
8426 ..Default::default()
8427 },
8428 },
8429 padding: PaddingConfig { left: outer_padding, right: outer_padding, top: 0, bottom: 0 },
8430 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
8431 ..Default::default()
8432 },
8433 background_color: Self::DEBUG_COLOR_2,
8434 ..Default::default()
8435 });
8436 {
8437 self.debug_text("Ply Debug Tools", info_text_config);
8438 self.debug_open(&ElementDeclaration {
8440 layout: LayoutConfig {
8441 sizing: SizingConfig {
8442 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8443 ..Default::default()
8444 },
8445 ..Default::default()
8446 },
8447 ..Default::default()
8448 });
8449 self.close_element();
8450 let close_size = row_height - 10.0;
8452 self.debug_open_id("Ply__DebugView_CloseButton", &ElementDeclaration {
8453 layout: LayoutConfig {
8454 sizing: SizingConfig {
8455 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: close_size, max: close_size }, ..Default::default() },
8456 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: close_size, max: close_size }, ..Default::default() },
8457 },
8458 child_alignment: ChildAlignmentConfig { x: AlignX::CenterX, y: AlignY::CenterY },
8459 ..Default::default()
8460 },
8461 background_color: Color::rgba(217.0, 91.0, 67.0, 80.0),
8462 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
8463 border: BorderConfig {
8464 color: Color::rgba(217.0, 91.0, 67.0, 255.0),
8465 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
8466 ..Default::default()
8467 },
8468 ..Default::default()
8469 });
8470 {
8471 let tc = self.store_text_element_config(TextConfig {
8472 color: Self::DEBUG_COLOR_4,
8473 font_size: 16,
8474 ..Default::default()
8475 });
8476 self.debug_text("x", tc);
8477 }
8478 self.close_element();
8479 }
8480 self.close_element();
8481
8482 self.debug_open(&ElementDeclaration {
8484 layout: LayoutConfig {
8485 sizing: SizingConfig {
8486 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8487 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 1.0, max: 1.0 }, ..Default::default() },
8488 },
8489 ..Default::default()
8490 },
8491 background_color: Self::DEBUG_COLOR_3,
8492 ..Default::default()
8493 });
8494 self.close_element();
8495
8496 self.open_element_with_id(&scroll_id);
8498 self.configure_open_element(&ElementDeclaration {
8499 layout: LayoutConfig {
8500 sizing: SizingConfig {
8501 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8502 height: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8503 },
8504 ..Default::default()
8505 },
8506 clip: ClipConfig {
8507 horizontal: true,
8508 vertical: true,
8509 scroll_x: true,
8510 scroll_y: true,
8511 child_offset: self.get_scroll_offset(),
8512 ..Default::default()
8513 },
8514 ..Default::default()
8515 });
8516 {
8517 let alt_bg = if (initial_elements_length + initial_roots_length) & 1 == 0 {
8518 Self::DEBUG_COLOR_2
8519 } else {
8520 Self::DEBUG_COLOR_1
8521 };
8522 self.debug_open(&ElementDeclaration {
8524 layout: LayoutConfig {
8525 sizing: SizingConfig {
8526 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8527 ..Default::default() },
8529 padding: PaddingConfig {
8530 left: outer_padding,
8531 right: outer_padding,
8532 top: 0,
8533 bottom: 0,
8534 },
8535 layout_direction: LayoutDirection::TopToBottom,
8536 ..Default::default()
8537 },
8538 background_color: alt_bg,
8539 ..Default::default()
8540 });
8541 {
8542 let _layout_data = self.render_debug_layout_elements_list(
8543 initial_roots_length,
8544 highlighted_row,
8545 );
8546 }
8547 self.close_element(); }
8549 self.close_element(); self.debug_open(&ElementDeclaration {
8553 layout: LayoutConfig {
8554 sizing: SizingConfig {
8555 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8556 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 1.0, max: 1.0 }, ..Default::default() },
8557 },
8558 ..Default::default()
8559 },
8560 background_color: Self::DEBUG_COLOR_3,
8561 ..Default::default()
8562 });
8563 self.close_element();
8564
8565 if self.debug_selected_element_id != 0 {
8567 self.render_debug_selected_element_panel(info_text_config, info_title_config);
8568 }
8569 }
8570 self.close_element(); if self.pointer_info.state == PointerDataInteractionState::PressedThisFrame {
8574 let close_base_id = hash_string("Ply__DebugView_CloseButton", 0).id;
8575 let header_base_id = hash_string("Ply__DebugView_LayoutConfigHeader", 0).id;
8576 for i in (0..self.pointer_over_ids.len()).rev() {
8577 let id = self.pointer_over_ids[i].id;
8578 if id == close_base_id {
8579 self.debug_mode_enabled = false;
8580 break;
8581 }
8582 if id == header_base_id {
8583 self.debug_selected_element_id = 0;
8584 break;
8585 }
8586 }
8587 }
8588 }
8589
8590 fn render_debug_selected_element_panel(
8592 &mut self,
8593 info_text_config: usize,
8594 info_title_config: usize,
8595 ) {
8596 let row_height = Self::DEBUG_VIEW_ROW_HEIGHT;
8597 let outer_padding = Self::DEBUG_VIEW_OUTER_PADDING;
8598 let attr_padding = PaddingConfig {
8599 left: outer_padding,
8600 right: outer_padding,
8601 top: 8,
8602 bottom: 8,
8603 };
8604
8605 let selected_id = self.debug_selected_element_id;
8606 let selected_item = match self.layout_element_map.get(&selected_id) {
8607 Some(item) => item.clone(),
8608 None => return,
8609 };
8610 let layout_elem_idx = selected_item.layout_element_index as usize;
8611 if layout_elem_idx >= self.layout_elements.len() {
8612 return;
8613 }
8614
8615 let layout_config_index = self.layout_elements[layout_elem_idx].layout_config_index;
8616 let layout_config = self.layout_configs[layout_config_index];
8617
8618 self.debug_open(&ElementDeclaration {
8619 layout: LayoutConfig {
8620 sizing: SizingConfig {
8621 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8622 height: SizingAxis {
8623 type_: SizingType::Fixed,
8624 min_max: SizingMinMax { min: 316.0, max: 316.0 },
8625 ..Default::default()
8626 },
8627 },
8628 layout_direction: LayoutDirection::TopToBottom,
8629 ..Default::default()
8630 },
8631 background_color: Self::DEBUG_COLOR_2,
8632 clip: ClipConfig {
8633 vertical: true,
8634 scroll_y: true,
8635 child_offset: self.get_scroll_offset(),
8636 ..Default::default()
8637 },
8638 border: BorderConfig {
8639 color: Self::DEBUG_COLOR_3,
8640 width: BorderWidth { between_children: 1, ..Default::default() },
8641 ..Default::default()
8642 },
8643 ..Default::default()
8644 });
8645 {
8646 self.debug_open_id("Ply__DebugView_LayoutConfigHeader", &ElementDeclaration {
8648 layout: LayoutConfig {
8649 sizing: SizingConfig {
8650 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8651 height: SizingAxis {
8652 type_: SizingType::Fixed,
8653 min_max: SizingMinMax { min: row_height + 8.0, max: row_height + 8.0 },
8654 ..Default::default()
8655 },
8656 },
8657 padding: PaddingConfig { left: outer_padding, right: outer_padding, top: 0, bottom: 0 },
8658 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
8659 ..Default::default()
8660 },
8661 ..Default::default()
8662 });
8663 {
8664 self.debug_text("Layout Config", info_text_config);
8665 self.debug_open(&ElementDeclaration {
8667 layout: LayoutConfig {
8668 sizing: SizingConfig {
8669 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
8670 ..Default::default()
8671 },
8672 ..Default::default()
8673 },
8674 ..Default::default()
8675 });
8676 self.close_element();
8677 let sid = selected_item.element_id.string_id.clone();
8679 if !sid.is_empty() {
8680 self.debug_raw_text(sid.as_str(), info_title_config);
8681 if selected_item.element_id.offset != 0 {
8682 self.debug_text(" (", info_title_config);
8683 self.debug_int_text(selected_item.element_id.offset as f32, info_title_config);
8684 self.debug_text(")", info_title_config);
8685 }
8686 }
8687 }
8688 self.close_element();
8689
8690 self.debug_open(&ElementDeclaration {
8692 layout: LayoutConfig {
8693 padding: attr_padding,
8694 child_gap: 8,
8695 layout_direction: LayoutDirection::TopToBottom,
8696 ..Default::default()
8697 },
8698 ..Default::default()
8699 });
8700 {
8701 self.debug_text("Bounding Box", info_title_config);
8703 self.debug_open(&ElementDeclaration::default());
8704 {
8705 self.debug_text("{ x: ", info_text_config);
8706 self.debug_int_text(selected_item.bounding_box.x, info_text_config);
8707 self.debug_text(", y: ", info_text_config);
8708 self.debug_int_text(selected_item.bounding_box.y, info_text_config);
8709 self.debug_text(", width: ", info_text_config);
8710 self.debug_int_text(selected_item.bounding_box.width, info_text_config);
8711 self.debug_text(", height: ", info_text_config);
8712 self.debug_int_text(selected_item.bounding_box.height, info_text_config);
8713 self.debug_text(" }", info_text_config);
8714 }
8715 self.close_element();
8716
8717 self.debug_text("Layout Direction", info_title_config);
8719 if layout_config.layout_direction == LayoutDirection::TopToBottom {
8720 self.debug_text("TOP_TO_BOTTOM", info_text_config);
8721 } else {
8722 self.debug_text("LEFT_TO_RIGHT", info_text_config);
8723 }
8724
8725 self.debug_text("Sizing", info_title_config);
8727 self.debug_open(&ElementDeclaration::default());
8728 {
8729 self.debug_text("width: ", info_text_config);
8730 self.render_debug_layout_sizing(layout_config.sizing.width, info_text_config);
8731 }
8732 self.close_element();
8733 self.debug_open(&ElementDeclaration::default());
8734 {
8735 self.debug_text("height: ", info_text_config);
8736 self.render_debug_layout_sizing(layout_config.sizing.height, info_text_config);
8737 }
8738 self.close_element();
8739
8740 self.debug_text("Padding", info_title_config);
8742 self.debug_open_id("Ply__DebugViewElementInfoPadding", &ElementDeclaration::default());
8743 {
8744 self.debug_text("{ left: ", info_text_config);
8745 self.debug_int_text(layout_config.padding.left as f32, info_text_config);
8746 self.debug_text(", right: ", info_text_config);
8747 self.debug_int_text(layout_config.padding.right as f32, info_text_config);
8748 self.debug_text(", top: ", info_text_config);
8749 self.debug_int_text(layout_config.padding.top as f32, info_text_config);
8750 self.debug_text(", bottom: ", info_text_config);
8751 self.debug_int_text(layout_config.padding.bottom as f32, info_text_config);
8752 self.debug_text(" }", info_text_config);
8753 }
8754 self.close_element();
8755
8756 self.debug_text("Child Gap", info_title_config);
8758 self.debug_int_text(layout_config.child_gap as f32, info_text_config);
8759
8760 self.debug_text("Wrap", info_title_config);
8762 self.debug_text(
8763 if layout_config.wrap { "true" } else { "false" },
8764 info_text_config,
8765 );
8766
8767 self.debug_text("Wrap Gap", info_title_config);
8769 self.debug_int_text(layout_config.wrap_gap as f32, info_text_config);
8770
8771 self.debug_text("Child Alignment", info_title_config);
8773 self.debug_open(&ElementDeclaration::default());
8774 {
8775 self.debug_text("{ x: ", info_text_config);
8776 let align_x = Self::align_x_name(layout_config.child_alignment.x);
8777 self.debug_text(align_x, info_text_config);
8778 self.debug_text(", y: ", info_text_config);
8779 let align_y = Self::align_y_name(layout_config.child_alignment.y);
8780 self.debug_text(align_y, info_text_config);
8781 self.debug_text(" }", info_text_config);
8782 }
8783 self.close_element();
8784 }
8785 self.close_element(); let configs_start = self.layout_elements[layout_elem_idx].element_configs.start;
8789 let configs_len = self.layout_elements[layout_elem_idx].element_configs.length;
8790 let elem_id_string = selected_item.element_id.string_id.clone();
8791
8792 let mut shared_bg_color: Option<Color> = None;
8794 let mut shared_corner_radius: Option<CornerRadius> = None;
8795 for ci in 0..configs_len {
8796 let ec = self.element_configs[configs_start + ci as usize];
8797 if ec.config_type == ElementConfigType::Shared {
8798 let shared = self.shared_element_configs[ec.config_index];
8799 shared_bg_color = Some(shared.background_color);
8800 shared_corner_radius = Some(shared.corner_radius);
8801 }
8802 }
8803
8804 let shape_rot = self.element_shape_rotations.get(layout_elem_idx).copied().flatten();
8806 let visual_rot = self.element_visual_rotations.get(layout_elem_idx).cloned().flatten();
8807 let effects = self.element_effects.get(layout_elem_idx).cloned().unwrap_or_default();
8808 let shaders = self.element_shaders.get(layout_elem_idx).cloned().unwrap_or_default();
8809
8810 let has_color = shared_bg_color.map_or(false, |c| c.a > 0.0);
8812 if has_color {
8813 let color_label_color = Color::rgba(243.0, 134.0, 48.0, 255.0);
8814 self.render_debug_view_category_header("Color", color_label_color, elem_id_string.clone());
8815 self.debug_open(&ElementDeclaration {
8816 layout: LayoutConfig {
8817 padding: attr_padding,
8818 child_gap: 8,
8819 layout_direction: LayoutDirection::TopToBottom,
8820 ..Default::default()
8821 },
8822 ..Default::default()
8823 });
8824 {
8825 self.debug_text("Background Color", info_title_config);
8826 self.render_debug_view_color(shared_bg_color.unwrap(), info_text_config);
8827 }
8828 self.close_element();
8829 }
8830
8831 let has_corner_radius = shared_corner_radius.map_or(false, |cr| !cr.is_zero());
8833 let has_shape_rot = shape_rot.is_some();
8834 if has_corner_radius || has_shape_rot {
8835 let shape_label_color = Color::rgba(26.0, 188.0, 156.0, 255.0);
8836 self.render_debug_view_category_header("Shape", shape_label_color, elem_id_string.clone());
8837 self.debug_open(&ElementDeclaration {
8838 layout: LayoutConfig {
8839 padding: attr_padding,
8840 child_gap: 8,
8841 layout_direction: LayoutDirection::TopToBottom,
8842 ..Default::default()
8843 },
8844 ..Default::default()
8845 });
8846 {
8847 if let Some(cr) = shared_corner_radius {
8848 if !cr.is_zero() {
8849 self.debug_text("Corner Radius", info_title_config);
8850 self.render_debug_view_corner_radius(cr, info_text_config);
8851 }
8852 }
8853 if let Some(sr) = shape_rot {
8854 self.debug_text("Shape Rotation", info_title_config);
8855 self.debug_open(&ElementDeclaration::default());
8856 {
8857 self.debug_text("angle: ", info_text_config);
8858 self.debug_float_text(sr.rotation_radians, info_text_config);
8859 self.debug_text(" rad", info_text_config);
8860 }
8861 self.close_element();
8862 self.debug_open(&ElementDeclaration::default());
8863 {
8864 self.debug_text("flip_x: ", info_text_config);
8865 self.debug_text(if sr.flip_x { "true" } else { "false" }, info_text_config);
8866 self.debug_text(", flip_y: ", info_text_config);
8867 self.debug_text(if sr.flip_y { "true" } else { "false" }, info_text_config);
8868 }
8869 self.close_element();
8870 }
8871 }
8872 self.close_element();
8873 }
8874
8875 for ci in 0..configs_len {
8877 let ec = self.element_configs[configs_start + ci as usize];
8878 match ec.config_type {
8879 ElementConfigType::Shared => {} ElementConfigType::Text => {
8881 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
8882 let text_config = self.text_element_configs[ec.config_index].clone();
8883 self.debug_open(&ElementDeclaration {
8884 layout: LayoutConfig {
8885 padding: attr_padding,
8886 child_gap: 8,
8887 layout_direction: LayoutDirection::TopToBottom,
8888 ..Default::default()
8889 },
8890 ..Default::default()
8891 });
8892 {
8893 self.debug_text("Font Size", info_title_config);
8894 self.debug_int_text(text_config.font_size as f32, info_text_config);
8895 self.debug_text("Font", info_title_config);
8896 {
8897 let label = if let Some(asset) = text_config.font_asset {
8898 asset.key().to_string()
8899 } else {
8900 format!("default ({})", self.default_font_key)
8901 };
8902 self.open_text_element(&label, info_text_config);
8903 }
8904 self.debug_text("Line Height", info_title_config);
8905 if text_config.line_height == 0 {
8906 self.debug_text("auto", info_text_config);
8907 } else {
8908 self.debug_int_text(text_config.line_height as f32, info_text_config);
8909 }
8910 self.debug_text("Letter Spacing", info_title_config);
8911 self.debug_int_text(text_config.letter_spacing as f32, info_text_config);
8912 self.debug_text("Wrap Mode", info_title_config);
8913 let wrap = match text_config.wrap_mode {
8914 WrapMode::None => "NONE",
8915 WrapMode::Newline => "NEWLINES",
8916 _ => "WORDS",
8917 };
8918 self.debug_text(wrap, info_text_config);
8919 self.debug_text("Text Alignment", info_title_config);
8920 let align = match text_config.alignment {
8921 AlignX::CenterX => "CENTER",
8922 AlignX::Right => "RIGHT",
8923 _ => "LEFT",
8924 };
8925 self.debug_text(align, info_text_config);
8926 self.debug_text("Text Color", info_title_config);
8927 self.render_debug_view_color(text_config.color, info_text_config);
8928 }
8929 self.close_element();
8930 }
8931 ElementConfigType::Image => {
8932 let image_label_color = Color::rgba(121.0, 189.0, 154.0, 255.0);
8933 self.render_debug_view_category_header("Image", image_label_color, elem_id_string.clone());
8934 let image_data = self.image_element_configs[ec.config_index].clone();
8935 self.debug_open(&ElementDeclaration {
8936 layout: LayoutConfig {
8937 padding: attr_padding,
8938 child_gap: 8,
8939 layout_direction: LayoutDirection::TopToBottom,
8940 ..Default::default()
8941 },
8942 ..Default::default()
8943 });
8944 {
8945 self.debug_text("Source", info_title_config);
8946 let name = image_data.get_name();
8947 self.debug_raw_text(name, info_text_config);
8948 }
8949 self.close_element();
8950 }
8951 ElementConfigType::Aspect => {
8952 self.render_debug_view_element_config_header(
8953 elem_id_string.clone(),
8954 ec.config_type,
8955 info_title_config,
8956 );
8957 let aspect_ratio = self.aspect_ratio_configs[ec.config_index];
8958 let is_cover = self
8959 .aspect_ratio_cover_configs
8960 .get(ec.config_index)
8961 .copied()
8962 .unwrap_or(false);
8963 self.debug_open(&ElementDeclaration {
8964 layout: LayoutConfig {
8965 padding: attr_padding,
8966 child_gap: 8,
8967 layout_direction: LayoutDirection::TopToBottom,
8968 ..Default::default()
8969 },
8970 ..Default::default()
8971 });
8972 {
8973 self.debug_text("Aspect Ratio", info_title_config);
8974 self.debug_float_text(aspect_ratio, info_text_config);
8975 self.debug_text("Mode", info_title_config);
8976 self.debug_text(
8977 if is_cover { "COVER" } else { "CONTAIN" },
8978 info_text_config,
8979 );
8980 }
8981 self.close_element();
8982 }
8983 ElementConfigType::Clip => {
8984 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
8985 let clip_config = self.clip_element_configs[ec.config_index];
8986 self.debug_open(&ElementDeclaration {
8987 layout: LayoutConfig {
8988 padding: attr_padding,
8989 child_gap: 8,
8990 layout_direction: LayoutDirection::TopToBottom,
8991 ..Default::default()
8992 },
8993 ..Default::default()
8994 });
8995 {
8996 self.debug_text("Overflow", info_title_config);
8997 self.debug_open(&ElementDeclaration::default());
8998 {
8999 let x_label = if clip_config.scroll_x {
9000 "SCROLL"
9001 } else if clip_config.horizontal {
9002 "CLIP"
9003 } else {
9004 "OVERFLOW"
9005 };
9006 let y_label = if clip_config.scroll_y {
9007 "SCROLL"
9008 } else if clip_config.vertical {
9009 "CLIP"
9010 } else {
9011 "OVERFLOW"
9012 };
9013 self.debug_text("{ x: ", info_text_config);
9014 self.debug_text(x_label, info_text_config);
9015 self.debug_text(", y: ", info_text_config);
9016 self.debug_text(y_label, info_text_config);
9017 self.debug_text(" }", info_text_config);
9018 }
9019 self.close_element();
9020
9021 self.debug_text("No Drag Scroll", info_title_config);
9022 self.debug_text(
9023 if clip_config.no_drag_scroll {
9024 "true"
9025 } else {
9026 "false"
9027 },
9028 info_text_config,
9029 );
9030 }
9031 self.close_element();
9032
9033 if let Some(scrollbar) = clip_config.scrollbar {
9034 let scrollbar_label_color = Color::rgba(242.0, 196.0, 90.0, 255.0);
9035 self.render_debug_view_category_header(
9036 "Scrollbar",
9037 scrollbar_label_color,
9038 elem_id_string.clone(),
9039 );
9040 self.debug_open(&ElementDeclaration {
9041 layout: LayoutConfig {
9042 padding: attr_padding,
9043 child_gap: 8,
9044 layout_direction: LayoutDirection::TopToBottom,
9045 ..Default::default()
9046 },
9047 ..Default::default()
9048 });
9049 {
9050 self.render_debug_scrollbar_config(
9051 scrollbar,
9052 info_text_config,
9053 info_title_config,
9054 );
9055 }
9056 self.close_element();
9057 }
9058 }
9059 ElementConfigType::Floating => {
9060 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
9061 let float_config = self.floating_element_configs[ec.config_index];
9062 self.debug_open(&ElementDeclaration {
9063 layout: LayoutConfig {
9064 padding: attr_padding,
9065 child_gap: 8,
9066 layout_direction: LayoutDirection::TopToBottom,
9067 ..Default::default()
9068 },
9069 ..Default::default()
9070 });
9071 {
9072 self.debug_text("Offset", info_title_config);
9073 self.debug_open(&ElementDeclaration::default());
9074 {
9075 self.debug_text("{ x: ", info_text_config);
9076 self.debug_int_text(float_config.offset.x, info_text_config);
9077 self.debug_text(", y: ", info_text_config);
9078 self.debug_int_text(float_config.offset.y, info_text_config);
9079 self.debug_text(" }", info_text_config);
9080 }
9081 self.close_element();
9082
9083 self.debug_text("z-index", info_title_config);
9084 self.debug_int_text(float_config.z_index as f32, info_text_config);
9085
9086 self.debug_text("Parent", info_title_config);
9087 let parent_name = self.layout_element_map
9088 .get(&float_config.parent_id)
9089 .map(|item| item.element_id.string_id.clone())
9090 .unwrap_or(StringId::empty());
9091 if !parent_name.is_empty() {
9092 self.debug_raw_text(parent_name.as_str(), info_text_config);
9093 }
9094
9095 self.debug_text("Attach Points", info_title_config);
9096 self.debug_open(&ElementDeclaration::default());
9097 {
9098 self.debug_text("{ element: (", info_text_config);
9099 self.debug_text(Self::align_x_name(float_config.attach_points.element_x), info_text_config);
9100 self.debug_text(", ", info_text_config);
9101 self.debug_text(Self::align_y_name(float_config.attach_points.element_y), info_text_config);
9102 self.debug_text("), parent: (", info_text_config);
9103 self.debug_text(Self::align_x_name(float_config.attach_points.parent_x), info_text_config);
9104 self.debug_text(", ", info_text_config);
9105 self.debug_text(Self::align_y_name(float_config.attach_points.parent_y), info_text_config);
9106 self.debug_text(") }", info_text_config);
9107 }
9108 self.close_element();
9109
9110 self.debug_text("Pointer Capture Mode", info_title_config);
9111 let pcm = if float_config.pointer_capture_mode == PointerCaptureMode::Passthrough {
9112 "PASSTHROUGH"
9113 } else {
9114 "NONE"
9115 };
9116 self.debug_text(pcm, info_text_config);
9117
9118 self.debug_text("Attach To", info_title_config);
9119 let at = match float_config.attach_to {
9120 FloatingAttachToElement::Parent => "PARENT",
9121 FloatingAttachToElement::ElementWithId => "ELEMENT_WITH_ID",
9122 FloatingAttachToElement::Root => "ROOT",
9123 _ => "NONE",
9124 };
9125 self.debug_text(at, info_text_config);
9126
9127 self.debug_text("Clip To", info_title_config);
9128 let ct = if float_config.clip_to == FloatingClipToElement::None {
9129 "NONE"
9130 } else {
9131 "ATTACHED_PARENT"
9132 };
9133 self.debug_text(ct, info_text_config);
9134 }
9135 self.close_element();
9136 }
9137 ElementConfigType::Border => {
9138 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
9139 let border_config = self.border_element_configs[ec.config_index];
9140 self.debug_open_id("Ply__DebugViewElementInfoBorderBody", &ElementDeclaration {
9141 layout: LayoutConfig {
9142 padding: attr_padding,
9143 child_gap: 8,
9144 layout_direction: LayoutDirection::TopToBottom,
9145 ..Default::default()
9146 },
9147 ..Default::default()
9148 });
9149 {
9150 self.debug_text("Border Widths", info_title_config);
9151 self.debug_open(&ElementDeclaration::default());
9152 {
9153 self.debug_text("left: ", info_text_config);
9154 self.debug_int_text(border_config.width.left as f32, info_text_config);
9155 }
9156 self.close_element();
9157 self.debug_open(&ElementDeclaration::default());
9158 {
9159 self.debug_text("right: ", info_text_config);
9160 self.debug_int_text(border_config.width.right as f32, info_text_config);
9161 }
9162 self.close_element();
9163 self.debug_open(&ElementDeclaration::default());
9164 {
9165 self.debug_text("top: ", info_text_config);
9166 self.debug_int_text(border_config.width.top as f32, info_text_config);
9167 }
9168 self.close_element();
9169 self.debug_open(&ElementDeclaration::default());
9170 {
9171 self.debug_text("bottom: ", info_text_config);
9172 self.debug_int_text(border_config.width.bottom as f32, info_text_config);
9173 }
9174 self.close_element();
9175 self.debug_open(&ElementDeclaration::default());
9176 {
9177 self.debug_text("betweenChildren: ", info_text_config);
9178 self.debug_int_text(
9179 border_config.width.between_children as f32,
9180 info_text_config,
9181 );
9182 }
9183 self.close_element();
9184
9185 self.debug_text("Border Position", info_title_config);
9186 self.debug_text(
9187 Self::debug_border_position_name(border_config.position),
9188 info_text_config,
9189 );
9190
9191 self.debug_text("Border Color", info_title_config);
9192 self.render_debug_view_color(border_config.color, info_text_config);
9193 }
9194 self.close_element();
9195 }
9196 ElementConfigType::TextInput => {
9197 let input_label_color = Color::rgba(52.0, 152.0, 219.0, 255.0);
9199 self.render_debug_view_category_header("Input", input_label_color, elem_id_string.clone());
9200 let ti_cfg = self.text_input_configs[ec.config_index].clone();
9201 self.debug_open(&ElementDeclaration {
9202 layout: LayoutConfig {
9203 padding: attr_padding,
9204 child_gap: 8,
9205 layout_direction: LayoutDirection::TopToBottom,
9206 ..Default::default()
9207 },
9208 ..Default::default()
9209 });
9210 {
9211 if !ti_cfg.placeholder.is_empty() {
9212 self.debug_text("Placeholder", info_title_config);
9213 self.debug_raw_text(&ti_cfg.placeholder, info_text_config);
9214 }
9215 self.debug_text("Placeholder Color", info_title_config);
9216 self.render_debug_view_color(ti_cfg.placeholder_color, info_text_config);
9217 self.debug_text("Max Length", info_title_config);
9218 if let Some(max_len) = ti_cfg.max_length {
9219 self.debug_int_text(max_len as f32, info_text_config);
9220 } else {
9221 self.debug_text("unlimited", info_text_config);
9222 }
9223 self.debug_text("Password", info_title_config);
9224 self.debug_text(if ti_cfg.is_password { "true" } else { "false" }, info_text_config);
9225 self.debug_text("Multiline", info_title_config);
9226 self.debug_text(if ti_cfg.is_multiline { "true" } else { "false" }, info_text_config);
9227 self.debug_text("Drag Select", info_title_config);
9228 self.debug_text(if ti_cfg.drag_select { "true" } else { "false" }, info_text_config);
9229 self.debug_text("Line Height", info_title_config);
9230 if ti_cfg.line_height == 0 {
9231 self.debug_text("auto", info_text_config);
9232 } else {
9233 self.debug_int_text(ti_cfg.line_height as f32, info_text_config);
9234 }
9235 self.debug_text("No Styles Movement", info_title_config);
9236 self.debug_text(
9237 if ti_cfg.no_styles_movement {
9238 "true"
9239 } else {
9240 "false"
9241 },
9242 info_text_config,
9243 );
9244 self.debug_text("Font", info_title_config);
9245 self.debug_open(&ElementDeclaration::default());
9246 {
9247 let label = if let Some(asset) = ti_cfg.font_asset {
9248 asset.key().to_string()
9249 } else {
9250 format!("default ({})", self.default_font_key)
9251 };
9252 self.open_text_element(&label, info_text_config);
9253 self.debug_text(", size: ", info_text_config);
9254 self.debug_int_text(ti_cfg.font_size as f32, info_text_config);
9255 }
9256 self.close_element();
9257 self.debug_text("Text Color", info_title_config);
9258 self.render_debug_view_color(ti_cfg.text_color, info_text_config);
9259 self.debug_text("Cursor Color", info_title_config);
9260 self.render_debug_view_color(ti_cfg.cursor_color, info_text_config);
9261 self.debug_text("Selection Color", info_title_config);
9262 self.render_debug_view_color(ti_cfg.selection_color, info_text_config);
9263 self.debug_text("Scrollbar", info_title_config);
9264 if let Some(scrollbar) = ti_cfg.scrollbar {
9265 self.debug_text("configured", info_text_config);
9266 self.render_debug_scrollbar_config(
9267 scrollbar,
9268 info_text_config,
9269 info_title_config,
9270 );
9271 } else {
9272 self.debug_text("none", info_text_config);
9273 }
9274 let state_data = self.text_edit_states.get(&selected_id)
9276 .map(|s| (s.text.clone(), s.cursor_pos));
9277 if let Some((text_val, cursor_pos)) = state_data {
9278 self.debug_text("Value", info_title_config);
9279 let preview = if text_val.len() > 40 {
9280 let mut end = 40;
9281 while !text_val.is_char_boundary(end) { end -= 1; }
9282 format!("\"{}...\"", &text_val[..end])
9283 } else {
9284 format!("\"{}\"", &text_val)
9285 };
9286 self.debug_raw_text(&preview, info_text_config);
9287 self.debug_text("Cursor Position", info_title_config);
9288 self.debug_int_text(cursor_pos as f32, info_text_config);
9289 }
9290 }
9291 self.close_element();
9292 }
9293 _ => {}
9294 }
9295 }
9296
9297 let has_visual_rot = visual_rot.is_some();
9299 let has_effects = !effects.is_empty();
9300 let has_shaders = !shaders.is_empty();
9301 if has_visual_rot || has_effects || has_shaders {
9302 let effects_label_color = Color::rgba(155.0, 89.0, 182.0, 255.0);
9303 self.render_debug_view_category_header("Effects", effects_label_color, elem_id_string.clone());
9304 self.debug_open(&ElementDeclaration {
9305 layout: LayoutConfig {
9306 padding: attr_padding,
9307 child_gap: 8,
9308 layout_direction: LayoutDirection::TopToBottom,
9309 ..Default::default()
9310 },
9311 ..Default::default()
9312 });
9313 {
9314 if let Some(vr) = visual_rot {
9315 self.debug_text("Visual Rotation", info_title_config);
9316 self.debug_open(&ElementDeclaration::default());
9317 {
9318 self.debug_text("angle: ", info_text_config);
9319 self.debug_float_text(vr.rotation_radians, info_text_config);
9320 self.debug_text(" rad", info_text_config);
9321 }
9322 self.close_element();
9323 self.debug_open(&ElementDeclaration::default());
9324 {
9325 self.debug_text("pivot: (", info_text_config);
9326 self.debug_float_text(vr.pivot_x, info_text_config);
9327 self.debug_text(", ", info_text_config);
9328 self.debug_float_text(vr.pivot_y, info_text_config);
9329 self.debug_text(")", info_text_config);
9330 }
9331 self.close_element();
9332 self.debug_open(&ElementDeclaration::default());
9333 {
9334 self.debug_text("flip_x: ", info_text_config);
9335 self.debug_text(if vr.flip_x { "true" } else { "false" }, info_text_config);
9336 self.debug_text(", flip_y: ", info_text_config);
9337 self.debug_text(if vr.flip_y { "true" } else { "false" }, info_text_config);
9338 }
9339 self.close_element();
9340 }
9341 for (i, effect) in effects.iter().enumerate() {
9342 let label = format!("Effect {}", i + 1);
9343 self.debug_text("Effect", info_title_config);
9344 self.debug_open(&ElementDeclaration::default());
9345 {
9346 self.debug_raw_text(&label, info_text_config);
9347 self.debug_text(": ", info_text_config);
9348 self.debug_raw_text(&effect.name, info_text_config);
9349 }
9350 self.close_element();
9351 for uniform in &effect.uniforms {
9352 self.debug_open(&ElementDeclaration::default());
9353 {
9354 self.debug_text(" ", info_text_config);
9355 self.debug_raw_text(&uniform.name, info_text_config);
9356 self.debug_text(": ", info_text_config);
9357 self.render_debug_shader_uniform_value(&uniform.value, info_text_config);
9358 }
9359 self.close_element();
9360 }
9361 }
9362 for (i, shader) in shaders.iter().enumerate() {
9363 let label = format!("Shader {}", i + 1);
9364 self.debug_text("Shader", info_title_config);
9365 self.debug_open(&ElementDeclaration::default());
9366 {
9367 self.debug_raw_text(&label, info_text_config);
9368 self.debug_text(": ", info_text_config);
9369 self.debug_raw_text(&shader.name, info_text_config);
9370 }
9371 self.close_element();
9372 for uniform in &shader.uniforms {
9373 self.debug_open(&ElementDeclaration::default());
9374 {
9375 self.debug_text(" ", info_text_config);
9376 self.debug_raw_text(&uniform.name, info_text_config);
9377 self.debug_text(": ", info_text_config);
9378 self.render_debug_shader_uniform_value(&uniform.value, info_text_config);
9379 }
9380 self.close_element();
9381 }
9382 }
9383 }
9384 self.close_element();
9385 }
9386 }
9387 self.close_element(); }
9389
9390 fn align_x_name(value: AlignX) -> &'static str {
9391 match value {
9392 AlignX::Left => "LEFT",
9393 AlignX::CenterX => "CENTER",
9394 AlignX::Right => "RIGHT",
9395 }
9396 }
9397
9398 fn align_y_name(value: AlignY) -> &'static str {
9399 match value {
9400 AlignY::Top => "TOP",
9401 AlignY::CenterY => "CENTER",
9402 AlignY::Bottom => "BOTTOM",
9403 }
9404 }
9405
9406 pub fn set_max_element_count(&mut self, count: i32) {
9407 self.max_element_count = count;
9408 }
9409
9410 pub fn set_max_measure_text_cache_word_count(&mut self, count: i32) {
9411 self.max_measure_text_cache_word_count = count;
9412 }
9413
9414 pub fn set_debug_mode_enabled(&mut self, enabled: bool) {
9415 self.debug_mode_enabled = enabled;
9416 }
9417
9418 pub fn set_debug_view_width(&mut self, width: f32) {
9419 self.debug_view_width = width.max(0.0);
9420 }
9421
9422 pub fn is_debug_mode_enabled(&self) -> bool {
9423 self.debug_mode_enabled
9424 }
9425
9426 pub fn set_culling_enabled(&mut self, enabled: bool) {
9427 self.culling_disabled = !enabled;
9428 }
9429
9430 pub fn set_measure_text_function(
9431 &mut self,
9432 f: Box<dyn Fn(&str, &TextConfig) -> Dimensions>,
9433 ) {
9434 self.measure_text_fn = Some(f);
9435 self.font_height_cache.clear();
9437 }
9438}