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