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, Default)]
112pub struct SizingAxis {
113 pub type_: SizingType,
114 pub min_max: SizingMinMax,
115 pub percent: f32,
116}
117
118#[derive(Debug, Clone, Copy, Default)]
119pub struct SizingConfig {
120 pub width: SizingAxis,
121 pub height: SizingAxis,
122}
123
124#[derive(Debug, Clone, Copy, Default)]
125pub struct PaddingConfig {
126 pub left: u16,
127 pub right: u16,
128 pub top: u16,
129 pub bottom: u16,
130}
131
132#[derive(Debug, Clone, Copy, Default)]
133pub struct ChildAlignmentConfig {
134 pub x: AlignX,
135 pub y: AlignY,
136}
137
138#[derive(Debug, Clone, Copy, Default)]
139pub struct LayoutConfig {
140 pub sizing: SizingConfig,
141 pub padding: PaddingConfig,
142 pub child_gap: u16,
143 pub child_alignment: ChildAlignmentConfig,
144 pub layout_direction: LayoutDirection,
145}
146
147
148#[derive(Debug, Clone, Copy)]
149pub struct VisualRotationConfig {
150 pub rotation_radians: f32,
152 pub pivot_x: f32,
154 pub pivot_y: f32,
156 pub flip_x: bool,
158 pub flip_y: bool,
160}
161
162impl Default for VisualRotationConfig {
163 fn default() -> Self {
164 Self {
165 rotation_radians: 0.0,
166 pivot_x: 0.5,
167 pivot_y: 0.5,
168 flip_x: false,
169 flip_y: false,
170 }
171 }
172}
173
174impl VisualRotationConfig {
175 pub fn is_noop(&self) -> bool {
177 self.rotation_radians == 0.0 && !self.flip_x && !self.flip_y
178 }
179}
180
181#[derive(Debug, Clone, Copy)]
182pub struct ShapeRotationConfig {
183 pub rotation_radians: f32,
185 pub flip_x: bool,
187 pub flip_y: bool,
189}
190
191impl Default for ShapeRotationConfig {
192 fn default() -> Self {
193 Self {
194 rotation_radians: 0.0,
195 flip_x: false,
196 flip_y: false,
197 }
198 }
199}
200
201impl ShapeRotationConfig {
202 pub fn is_noop(&self) -> bool {
204 self.rotation_radians == 0.0 && !self.flip_x && !self.flip_y
205 }
206}
207
208#[derive(Debug, Clone, Copy, Default)]
209pub struct FloatingAttachPoints {
210 pub element_x: AlignX,
211 pub element_y: AlignY,
212 pub parent_x: AlignX,
213 pub parent_y: AlignY,
214}
215
216#[derive(Debug, Clone, Copy, Default)]
217pub struct FloatingConfig {
218 pub offset: Vector2,
219 pub parent_id: u32,
220 pub z_index: i16,
221 pub attach_points: FloatingAttachPoints,
222 pub pointer_capture_mode: PointerCaptureMode,
223 pub attach_to: FloatingAttachToElement,
224 pub clip_to: FloatingClipToElement,
225}
226
227#[derive(Debug, Clone, Copy, Default)]
228pub struct ClipConfig {
229 pub horizontal: bool,
230 pub vertical: bool,
231 pub scroll_x: bool,
232 pub scroll_y: bool,
233 pub child_offset: Vector2,
234}
235
236#[derive(Debug, Clone, Copy, Default)]
237pub struct BorderWidth {
238 pub left: u16,
239 pub right: u16,
240 pub top: u16,
241 pub bottom: u16,
242 pub between_children: u16,
243}
244
245impl BorderWidth {
246 pub fn is_zero(&self) -> bool {
247 self.left == 0
248 && self.right == 0
249 && self.top == 0
250 && self.bottom == 0
251 && self.between_children == 0
252 }
253}
254
255#[derive(Debug, Clone, Copy, Default)]
256pub struct BorderConfig {
257 pub color: Color,
258 pub width: BorderWidth,
259 pub position: BorderPosition,
260}
261
262#[derive(Debug, Clone)]
264pub struct ElementDeclaration<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
265 pub layout: LayoutConfig,
266 pub background_color: Color,
267 pub corner_radius: CornerRadius,
268 pub aspect_ratio: f32,
269 pub image_data: Option<ImageSource>,
270 pub floating: FloatingConfig,
271 pub custom_data: Option<CustomElementData>,
272 pub clip: ClipConfig,
273 pub border: BorderConfig,
274 pub user_data: usize,
275 pub effects: Vec<ShaderConfig>,
276 pub shaders: Vec<ShaderConfig>,
277 pub visual_rotation: Option<VisualRotationConfig>,
278 pub shape_rotation: Option<ShapeRotationConfig>,
279 pub accessibility: Option<crate::accessibility::AccessibilityConfig>,
280 pub text_input: Option<crate::text_input::TextInputConfig>,
281 pub preserve_focus: bool,
282}
283
284impl<CustomElementData: Clone + Default + std::fmt::Debug> Default for ElementDeclaration<CustomElementData> {
285 fn default() -> Self {
286 Self {
287 layout: LayoutConfig::default(),
288 background_color: Color::rgba(0.0, 0.0, 0.0, 0.0),
289 corner_radius: CornerRadius::default(),
290 aspect_ratio: 0.0,
291 image_data: None,
292 floating: FloatingConfig::default(),
293 custom_data: None,
294 clip: ClipConfig::default(),
295 border: BorderConfig::default(),
296 user_data: 0,
297 effects: Vec::new(),
298 shaders: Vec::new(),
299 visual_rotation: None,
300 shape_rotation: None,
301 accessibility: None,
302 text_input: None,
303 preserve_focus: false,
304 }
305 }
306}
307
308use crate::id::{Id, StringId};
309
310#[derive(Debug, Clone, Copy, Default)]
311struct SharedElementConfig {
312 background_color: Color,
313 corner_radius: CornerRadius,
314 user_data: usize,
315}
316
317#[derive(Debug, Clone, Copy)]
318struct ElementConfig {
319 config_type: ElementConfigType,
320 config_index: usize,
321}
322
323#[derive(Debug, Clone, Copy, Default)]
324struct ElementConfigSlice {
325 start: usize,
326 length: i32,
327}
328
329#[derive(Debug, Clone, Copy, Default)]
330struct WrappedTextLine {
331 dimensions: Dimensions,
332 start: usize,
333 length: usize,
334}
335
336#[derive(Debug, Clone)]
337struct TextElementData {
338 text: String,
339 preferred_dimensions: Dimensions,
340 element_index: i32,
341 wrapped_lines_start: usize,
342 wrapped_lines_length: i32,
343}
344
345#[derive(Debug, Clone, Copy, Default)]
346struct LayoutElement {
347 children_start: usize,
349 children_length: u16,
350 text_data_index: i32, dimensions: Dimensions,
353 min_dimensions: Dimensions,
354 layout_config_index: usize,
355 element_configs: ElementConfigSlice,
356 id: u32,
357 floating_children_count: u16,
358}
359
360#[derive(Default)]
361struct LayoutElementHashMapItem {
362 bounding_box: BoundingBox,
363 element_id: Id,
364 layout_element_index: i32,
365 on_hover_fn: Option<Box<dyn FnMut(Id, PointerData)>>,
366 on_press_fn: Option<Box<dyn FnMut(Id, PointerData)>>,
367 on_release_fn: Option<Box<dyn FnMut(Id, PointerData)>>,
368 on_focus_fn: Option<Box<dyn FnMut(Id)>>,
369 on_unfocus_fn: Option<Box<dyn FnMut(Id)>>,
370 on_text_changed_fn: Option<Box<dyn FnMut(&str)>>,
371 on_text_submit_fn: Option<Box<dyn FnMut(&str)>>,
372 is_text_input: bool,
373 preserve_focus: bool,
374 generation: u32,
375 collision: bool,
376 collapsed: bool,
377}
378
379impl Clone for LayoutElementHashMapItem {
380 fn clone(&self) -> Self {
381 Self {
382 bounding_box: self.bounding_box,
383 element_id: self.element_id.clone(),
384 layout_element_index: self.layout_element_index,
385 on_hover_fn: None, on_press_fn: None,
387 on_release_fn: None,
388 on_focus_fn: None,
389 on_unfocus_fn: None,
390 on_text_changed_fn: None,
391 on_text_submit_fn: None,
392 is_text_input: self.is_text_input,
393 preserve_focus: self.preserve_focus,
394 generation: self.generation,
395 collision: self.collision,
396 collapsed: self.collapsed,
397 }
398 }
399}
400
401#[derive(Debug, Clone, Copy, Default)]
402struct MeasuredWord {
403 start_offset: i32,
404 length: i32,
405 width: f32,
406 next: i32,
407}
408
409#[derive(Debug, Clone, Copy, Default)]
410#[allow(dead_code)]
411struct MeasureTextCacheItem {
412 unwrapped_dimensions: Dimensions,
413 measured_words_start_index: i32,
414 min_width: f32,
415 contains_newlines: bool,
416 id: u32,
417 generation: u32,
418}
419
420#[derive(Debug, Clone, Copy, Default)]
421#[allow(dead_code)]
422struct ScrollContainerDataInternal {
423 bounding_box: BoundingBox,
424 content_size: Dimensions,
425 scroll_origin: Vector2,
426 pointer_origin: Vector2,
427 scroll_momentum: Vector2,
428 scroll_position: Vector2,
429 previous_delta: Vector2,
430 element_id: u32,
431 layout_element_index: i32,
432 open_this_frame: bool,
433 pointer_scroll_active: bool,
434}
435
436#[derive(Debug, Clone, Copy, Default)]
437struct LayoutElementTreeNode {
438 layout_element_index: i32,
439 position: Vector2,
440 next_child_offset: Vector2,
441}
442
443#[derive(Debug, Clone, Copy, Default)]
444struct LayoutElementTreeRoot {
445 layout_element_index: i32,
446 parent_id: u32,
447 clip_element_id: u32,
448 z_index: i16,
449 pointer_offset: Vector2,
450}
451
452#[derive(Debug, Clone, Copy)]
453struct FocusableEntry {
454 element_id: u32,
455 tab_index: Option<i32>,
456 insertion_order: u32,
457}
458
459#[derive(Debug, Clone, Copy, Default)]
460pub struct PointerData {
461 pub position: Vector2,
462 pub state: PointerDataInteractionState,
463}
464
465#[derive(Debug, Clone, Copy, Default)]
466#[allow(dead_code)]
467struct BooleanWarnings {
468 max_elements_exceeded: bool,
469 text_measurement_fn_not_set: bool,
470 max_text_measure_cache_exceeded: bool,
471 max_render_commands_exceeded: bool,
472}
473
474#[derive(Debug, Clone)]
475pub struct InternalRenderCommand<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
476 pub bounding_box: BoundingBox,
477 pub command_type: RenderCommandType,
478 pub render_data: InternalRenderData<CustomElementData>,
479 pub user_data: usize,
480 pub id: u32,
481 pub z_index: i16,
482 pub effects: Vec<ShaderConfig>,
483 pub visual_rotation: Option<VisualRotationConfig>,
484 pub shape_rotation: Option<ShapeRotationConfig>,
485}
486
487#[derive(Debug, Clone)]
488pub enum InternalRenderData<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
489 None,
490 Rectangle {
491 background_color: Color,
492 corner_radius: CornerRadius,
493 },
494 Text {
495 text: String,
496 text_color: Color,
497 font_size: u16,
498 letter_spacing: u16,
499 line_height: u16,
500 font_asset: Option<&'static crate::renderer::FontAsset>,
501 },
502 Image {
503 background_color: Color,
504 corner_radius: CornerRadius,
505 image_data: ImageSource,
506 },
507 Custom {
508 background_color: Color,
509 corner_radius: CornerRadius,
510 custom_data: CustomElementData,
511 },
512 Border {
513 color: Color,
514 corner_radius: CornerRadius,
515 width: BorderWidth,
516 position: BorderPosition,
517 },
518 Clip {
519 horizontal: bool,
520 vertical: bool,
521 },
522}
523
524impl<CustomElementData: Clone + Default + std::fmt::Debug> Default for InternalRenderData<CustomElementData> {
525 fn default() -> Self {
526 Self::None
527 }
528}
529
530impl<CustomElementData: Clone + Default + std::fmt::Debug> Default for InternalRenderCommand<CustomElementData> {
531 fn default() -> Self {
532 Self {
533 bounding_box: BoundingBox::default(),
534 command_type: RenderCommandType::None,
535 render_data: InternalRenderData::None,
536 user_data: 0,
537 id: 0,
538 z_index: 0,
539 effects: Vec::new(),
540 visual_rotation: None,
541 shape_rotation: None,
542 }
543 }
544}
545
546#[derive(Debug, Clone, Copy)]
547pub struct ScrollContainerData {
548 pub scroll_position: Vector2,
549 pub scroll_container_dimensions: Dimensions,
550 pub content_dimensions: Dimensions,
551 pub horizontal: bool,
552 pub vertical: bool,
553 pub found: bool,
554}
555
556impl Default for ScrollContainerData {
557 fn default() -> Self {
558 Self {
559 scroll_position: Vector2::default(),
560 scroll_container_dimensions: Dimensions::default(),
561 content_dimensions: Dimensions::default(),
562 horizontal: false,
563 vertical: false,
564 found: false,
565 }
566 }
567}
568
569pub struct PlyContext<CustomElementData: Clone + Default + std::fmt::Debug = ()> {
570 pub max_element_count: i32,
572 pub max_measure_text_cache_word_count: i32,
573 pub debug_mode_enabled: bool,
574 pub culling_disabled: bool,
575 pub external_scroll_handling_enabled: bool,
576 pub debug_selected_element_id: u32,
577 pub generation: u32,
578
579 boolean_warnings: BooleanWarnings,
581
582 pointer_info: PointerData,
584 pub layout_dimensions: Dimensions,
585
586 dynamic_element_index: u32,
588
589 measure_text_fn: Option<Box<dyn Fn(&str, &TextConfig) -> Dimensions>>,
591
592 layout_elements: Vec<LayoutElement>,
594 render_commands: Vec<InternalRenderCommand<CustomElementData>>,
595 open_layout_element_stack: Vec<i32>,
596 layout_element_children: Vec<i32>,
597 layout_element_children_buffer: Vec<i32>,
598 text_element_data: Vec<TextElementData>,
599 aspect_ratio_element_indexes: Vec<i32>,
600 reusable_element_index_buffer: Vec<i32>,
601 layout_element_clip_element_ids: Vec<i32>,
602
603 layout_configs: Vec<LayoutConfig>,
605 element_configs: Vec<ElementConfig>,
606 text_element_configs: Vec<TextConfig>,
607 aspect_ratio_configs: Vec<f32>,
608 image_element_configs: Vec<ImageSource>,
609 floating_element_configs: Vec<FloatingConfig>,
610 clip_element_configs: Vec<ClipConfig>,
611 custom_element_configs: Vec<CustomElementData>,
612 border_element_configs: Vec<BorderConfig>,
613 shared_element_configs: Vec<SharedElementConfig>,
614
615 element_effects: Vec<Vec<ShaderConfig>>,
617 element_shaders: Vec<Vec<ShaderConfig>>,
619
620 element_visual_rotations: Vec<Option<VisualRotationConfig>>,
622
623 element_shape_rotations: Vec<Option<ShapeRotationConfig>>,
625 element_pre_rotation_dimensions: Vec<Option<Dimensions>>,
627
628 layout_element_id_strings: Vec<StringId>,
630
631 wrapped_text_lines: Vec<WrappedTextLine>,
633
634 tree_node_array: Vec<LayoutElementTreeNode>,
636 layout_element_tree_roots: Vec<LayoutElementTreeRoot>,
637
638 layout_element_map: FxHashMap<u32, LayoutElementHashMapItem>,
640
641 measure_text_cache: FxHashMap<u32, MeasureTextCacheItem>,
643 measured_words: Vec<MeasuredWord>,
644 measured_words_free_list: Vec<i32>,
645
646 open_clip_element_stack: Vec<i32>,
648 pointer_over_ids: Vec<Id>,
649 pressed_element_ids: Vec<Id>,
650 scroll_container_datas: Vec<ScrollContainerDataInternal>,
651
652 pub focused_element_id: u32, pub(crate) focus_from_keyboard: bool,
656 focusable_elements: Vec<FocusableEntry>,
657 pub(crate) accessibility_configs: FxHashMap<u32, crate::accessibility::AccessibilityConfig>,
658 pub(crate) accessibility_element_order: Vec<u32>,
659
660 pub(crate) text_edit_states: FxHashMap<u32, crate::text_input::TextEditState>,
662 text_input_configs: Vec<crate::text_input::TextInputConfig>,
663 pub(crate) text_input_element_ids: Vec<u32>,
665 pub(crate) pending_text_click: Option<(u32, f32, f32, bool)>,
667 pub(crate) text_input_drag_active: bool,
669 pub(crate) text_input_drag_origin: crate::math::Vector2,
670 pub(crate) text_input_drag_scroll_origin: crate::math::Vector2,
671 pub(crate) text_input_drag_element_id: u32,
672 pub(crate) current_time: f64,
674 pub(crate) frame_delta_time: f32,
676
677 tree_node_visited: Vec<bool>,
679
680 dynamic_string_data: Vec<u8>,
682
683 font_height_cache: FxHashMap<(&'static str, u16), f32>,
686
687 pub(crate) default_font_key: &'static str,
689
690 }
692
693fn hash_data_scalar(data: &[u8]) -> u64 {
694 let mut hash: u64 = 0;
695 for &b in data {
696 hash = hash.wrapping_add(b as u64);
697 hash = hash.wrapping_add(hash << 10);
698 hash ^= hash >> 6;
699 }
700 hash
701}
702
703pub fn hash_string(key: &str, seed: u32) -> Id {
704 let mut hash: u32 = seed;
705 for b in key.bytes() {
706 hash = hash.wrapping_add(b as u32);
707 hash = hash.wrapping_add(hash << 10);
708 hash ^= hash >> 6;
709 }
710 hash = hash.wrapping_add(hash << 3);
711 hash ^= hash >> 11;
712 hash = hash.wrapping_add(hash << 15);
713 Id {
714 id: hash.wrapping_add(1),
715 offset: 0,
716 base_id: hash.wrapping_add(1),
717 string_id: StringId::from_str(key),
718 }
719}
720
721pub fn hash_string_with_offset(key: &str, offset: u32, seed: u32) -> Id {
722 let mut base: u32 = seed;
723 for b in key.bytes() {
724 base = base.wrapping_add(b as u32);
725 base = base.wrapping_add(base << 10);
726 base ^= base >> 6;
727 }
728 let mut hash = base;
729 hash = hash.wrapping_add(offset);
730 hash = hash.wrapping_add(hash << 10);
731 hash ^= hash >> 6;
732
733 hash = hash.wrapping_add(hash << 3);
734 base = base.wrapping_add(base << 3);
735 hash ^= hash >> 11;
736 base ^= base >> 11;
737 hash = hash.wrapping_add(hash << 15);
738 base = base.wrapping_add(base << 15);
739 Id {
740 id: hash.wrapping_add(1),
741 offset,
742 base_id: base.wrapping_add(1),
743 string_id: StringId::from_str(key),
744 }
745}
746
747fn hash_number(offset: u32, seed: u32) -> Id {
748 let mut hash = seed;
749 hash = hash.wrapping_add(offset.wrapping_add(48));
750 hash = hash.wrapping_add(hash << 10);
751 hash ^= hash >> 6;
752 hash = hash.wrapping_add(hash << 3);
753 hash ^= hash >> 11;
754 hash = hash.wrapping_add(hash << 15);
755 Id {
756 id: hash.wrapping_add(1),
757 offset,
758 base_id: seed,
759 string_id: StringId::empty(),
760 }
761}
762
763fn hash_string_contents_with_config(
764 text: &str,
765 config: &TextConfig,
766) -> u32 {
767 let mut hash: u32 = (hash_data_scalar(text.as_bytes()) % u32::MAX as u64) as u32;
768 for &b in config.font_asset.map(|a| a.key()).unwrap_or("").as_bytes() {
770 hash = hash.wrapping_add(b as u32);
771 hash = hash.wrapping_add(hash << 10);
772 hash ^= hash >> 6;
773 }
774 hash = hash.wrapping_add(config.font_size as u32);
775 hash = hash.wrapping_add(hash << 10);
776 hash ^= hash >> 6;
777 hash = hash.wrapping_add(config.letter_spacing as u32);
778 hash = hash.wrapping_add(hash << 10);
779 hash ^= hash >> 6;
780 hash = hash.wrapping_add(hash << 3);
781 hash ^= hash >> 11;
782 hash = hash.wrapping_add(hash << 15);
783 hash.wrapping_add(1)
784}
785
786fn float_equal(left: f32, right: f32) -> bool {
787 let diff = left - right;
788 diff < EPSILON && diff > -EPSILON
789}
790
791fn point_is_inside_rect(point: Vector2, rect: BoundingBox) -> bool {
792 point.x >= rect.x
793 && point.x <= rect.x + rect.width
794 && point.y >= rect.y
795 && point.y <= rect.y + rect.height
796}
797
798impl<CustomElementData: Clone + Default + std::fmt::Debug> PlyContext<CustomElementData> {
799 pub fn new(dimensions: Dimensions) -> Self {
800 let max_element_count = DEFAULT_MAX_ELEMENT_COUNT;
801 let max_measure_text_cache_word_count = DEFAULT_MAX_MEASURE_TEXT_WORD_CACHE_COUNT;
802
803 let ctx = Self {
804 max_element_count,
805 max_measure_text_cache_word_count,
806 debug_mode_enabled: false,
807 culling_disabled: false,
808 external_scroll_handling_enabled: false,
809 debug_selected_element_id: 0,
810 generation: 0,
811 boolean_warnings: BooleanWarnings::default(),
812 pointer_info: PointerData::default(),
813 layout_dimensions: dimensions,
814 dynamic_element_index: 0,
815 measure_text_fn: None,
816 layout_elements: Vec::new(),
817 render_commands: Vec::new(),
818 open_layout_element_stack: Vec::new(),
819 layout_element_children: Vec::new(),
820 layout_element_children_buffer: Vec::new(),
821 text_element_data: Vec::new(),
822 aspect_ratio_element_indexes: Vec::new(),
823 reusable_element_index_buffer: Vec::new(),
824 layout_element_clip_element_ids: Vec::new(),
825 layout_configs: Vec::new(),
826 element_configs: Vec::new(),
827 text_element_configs: Vec::new(),
828 aspect_ratio_configs: Vec::new(),
829 image_element_configs: Vec::new(),
830 floating_element_configs: Vec::new(),
831 clip_element_configs: Vec::new(),
832 custom_element_configs: Vec::new(),
833 border_element_configs: Vec::new(),
834 shared_element_configs: Vec::new(),
835 element_effects: Vec::new(),
836 element_shaders: Vec::new(),
837 element_visual_rotations: Vec::new(),
838 element_shape_rotations: Vec::new(),
839 element_pre_rotation_dimensions: Vec::new(),
840 layout_element_id_strings: Vec::new(),
841 wrapped_text_lines: Vec::new(),
842 tree_node_array: Vec::new(),
843 layout_element_tree_roots: Vec::new(),
844 layout_element_map: FxHashMap::default(),
845 measure_text_cache: FxHashMap::default(),
846 measured_words: Vec::new(),
847 measured_words_free_list: Vec::new(),
848 open_clip_element_stack: Vec::new(),
849 pointer_over_ids: Vec::new(),
850 pressed_element_ids: Vec::new(),
851 scroll_container_datas: Vec::new(),
852 focused_element_id: 0,
853 focus_from_keyboard: false,
854 focusable_elements: Vec::new(),
855 accessibility_configs: FxHashMap::default(),
856 accessibility_element_order: Vec::new(),
857 text_edit_states: FxHashMap::default(),
858 text_input_configs: Vec::new(),
859 text_input_element_ids: Vec::new(),
860 pending_text_click: None,
861 text_input_drag_active: false,
862 text_input_drag_origin: Vector2::default(),
863 text_input_drag_scroll_origin: Vector2::default(),
864 text_input_drag_element_id: 0,
865 current_time: 0.0,
866 frame_delta_time: 0.0,
867 tree_node_visited: Vec::new(),
868 dynamic_string_data: Vec::new(),
869 font_height_cache: FxHashMap::default(),
870 default_font_key: "",
871 };
872 ctx
873 }
874
875 fn get_open_layout_element(&self) -> usize {
876 let idx = *self.open_layout_element_stack.last().unwrap();
877 idx as usize
878 }
879
880 pub fn get_open_element_id(&self) -> u32 {
882 let open_idx = self.get_open_layout_element();
883 self.layout_elements[open_idx].id
884 }
885
886 pub fn get_parent_element_id(&self) -> u32 {
887 let stack_len = self.open_layout_element_stack.len();
888 let parent_idx = self.open_layout_element_stack[stack_len - 2] as usize;
889 self.layout_elements[parent_idx].id
890 }
891
892 fn add_hash_map_item(
893 &mut self,
894 element_id: &Id,
895 layout_element_index: i32,
896 ) {
897 let gen = self.generation;
898 match self.layout_element_map.entry(element_id.id) {
899 std::collections::hash_map::Entry::Occupied(mut entry) => {
900 let item = entry.get_mut();
901 if item.generation <= gen {
902 item.element_id = element_id.clone();
903 item.generation = gen + 1;
904 item.layout_element_index = layout_element_index;
905 item.collision = false;
906 item.on_hover_fn = None;
907 item.on_press_fn = None;
908 item.on_release_fn = None;
909 item.on_focus_fn = None;
910 item.on_unfocus_fn = None;
911 item.on_text_changed_fn = None;
912 item.on_text_submit_fn = None;
913 item.is_text_input = false;
914 item.preserve_focus = false;
915 } else {
916 item.collision = true;
918 }
919 }
920 std::collections::hash_map::Entry::Vacant(entry) => {
921 entry.insert(LayoutElementHashMapItem {
922 element_id: element_id.clone(),
923 layout_element_index,
924 generation: gen + 1,
925 bounding_box: BoundingBox::default(),
926 on_hover_fn: None,
927 on_press_fn: None,
928 on_release_fn: None,
929 on_focus_fn: None,
930 on_unfocus_fn: None,
931 on_text_changed_fn: None,
932 on_text_submit_fn: None,
933 is_text_input: false,
934 preserve_focus: false,
935 collision: false,
936 collapsed: false,
937 });
938 }
939 }
940 }
941
942 fn generate_id_for_anonymous_element(&mut self, open_element_index: usize) -> Id {
943 let stack_len = self.open_layout_element_stack.len();
944 let parent_idx = self.open_layout_element_stack[stack_len - 2] as usize;
945 let parent = &self.layout_elements[parent_idx];
946 let offset =
947 parent.children_length as u32 + parent.floating_children_count as u32;
948 let parent_id = parent.id;
949 let element_id = hash_number(offset, parent_id);
950 self.layout_elements[open_element_index].id = element_id.id;
951 self.add_hash_map_item(&element_id, open_element_index as i32);
952 if self.debug_mode_enabled {
953 self.layout_element_id_strings.push(element_id.string_id.clone());
954 }
955 element_id
956 }
957
958 fn element_has_config(
959 &self,
960 element_index: usize,
961 config_type: ElementConfigType,
962 ) -> bool {
963 let element = &self.layout_elements[element_index];
964 let start = element.element_configs.start;
965 let length = element.element_configs.length;
966 for i in 0..length {
967 let config = &self.element_configs[start + i as usize];
968 if config.config_type == config_type {
969 return true;
970 }
971 }
972 false
973 }
974
975 fn find_element_config_index(
976 &self,
977 element_index: usize,
978 config_type: ElementConfigType,
979 ) -> Option<usize> {
980 let element = &self.layout_elements[element_index];
981 let start = element.element_configs.start;
982 let length = element.element_configs.length;
983 for i in 0..length {
984 let config = &self.element_configs[start + i as usize];
985 if config.config_type == config_type {
986 return Some(config.config_index);
987 }
988 }
989 None
990 }
991
992 fn update_aspect_ratio_box(&mut self, element_index: usize) {
993 if let Some(config_idx) =
994 self.find_element_config_index(element_index, ElementConfigType::Aspect)
995 {
996 let aspect_ratio = self.aspect_ratio_configs[config_idx];
997 if aspect_ratio == 0.0 {
998 return;
999 }
1000 let elem = &mut self.layout_elements[element_index];
1001 if elem.dimensions.width == 0.0 && elem.dimensions.height != 0.0 {
1002 elem.dimensions.width = elem.dimensions.height * aspect_ratio;
1003 } else if elem.dimensions.width != 0.0 && elem.dimensions.height == 0.0 {
1004 elem.dimensions.height = elem.dimensions.width * (1.0 / aspect_ratio);
1005 }
1006 }
1007 }
1008
1009 pub fn store_text_element_config(
1010 &mut self,
1011 config: TextConfig,
1012 ) -> usize {
1013 self.text_element_configs.push(config);
1014 self.text_element_configs.len() - 1
1015 }
1016
1017 fn store_layout_config(&mut self, config: LayoutConfig) -> usize {
1018 self.layout_configs.push(config);
1019 self.layout_configs.len() - 1
1020 }
1021
1022 fn store_shared_config(&mut self, config: SharedElementConfig) -> usize {
1023 self.shared_element_configs.push(config);
1024 self.shared_element_configs.len() - 1
1025 }
1026
1027 fn attach_element_config(&mut self, config_type: ElementConfigType, config_index: usize) {
1028 if self.boolean_warnings.max_elements_exceeded {
1029 return;
1030 }
1031 let open_idx = self.get_open_layout_element();
1032 self.layout_elements[open_idx].element_configs.length += 1;
1033 self.element_configs.push(ElementConfig {
1034 config_type,
1035 config_index,
1036 });
1037 }
1038
1039 pub fn open_element(&mut self) {
1040 if self.boolean_warnings.max_elements_exceeded {
1041 return;
1042 }
1043 let elem = LayoutElement {
1044 text_data_index: -1,
1045 ..Default::default()
1046 };
1047 self.layout_elements.push(elem);
1048 let idx = (self.layout_elements.len() - 1) as i32;
1049 self.open_layout_element_stack.push(idx);
1050
1051 while self.layout_element_clip_element_ids.len() < self.layout_elements.len() {
1053 self.layout_element_clip_element_ids.push(0);
1054 }
1055
1056 self.generate_id_for_anonymous_element(idx as usize);
1057
1058 if !self.open_clip_element_stack.is_empty() {
1059 let clip_id = *self.open_clip_element_stack.last().unwrap();
1060 self.layout_element_clip_element_ids[idx as usize] = clip_id;
1061 } else {
1062 self.layout_element_clip_element_ids[idx as usize] = 0;
1063 }
1064 }
1065
1066 pub fn open_element_with_id(&mut self, element_id: &Id) {
1067 if self.boolean_warnings.max_elements_exceeded {
1068 return;
1069 }
1070 let mut elem = LayoutElement {
1071 text_data_index: -1,
1072 ..Default::default()
1073 };
1074 elem.id = element_id.id;
1075 self.layout_elements.push(elem);
1076 let idx = (self.layout_elements.len() - 1) as i32;
1077 self.open_layout_element_stack.push(idx);
1078
1079 while self.layout_element_clip_element_ids.len() < self.layout_elements.len() {
1080 self.layout_element_clip_element_ids.push(0);
1081 }
1082
1083 self.add_hash_map_item(element_id, idx);
1084 if self.debug_mode_enabled {
1085 self.layout_element_id_strings.push(element_id.string_id.clone());
1086 }
1087
1088 if !self.open_clip_element_stack.is_empty() {
1089 let clip_id = *self.open_clip_element_stack.last().unwrap();
1090 self.layout_element_clip_element_ids[idx as usize] = clip_id;
1091 } else {
1092 self.layout_element_clip_element_ids[idx as usize] = 0;
1093 }
1094 }
1095
1096 pub fn configure_open_element(&mut self, declaration: &ElementDeclaration<CustomElementData>) {
1097 if self.boolean_warnings.max_elements_exceeded {
1098 return;
1099 }
1100 let open_idx = self.get_open_layout_element();
1101 let layout_config_index = self.store_layout_config(declaration.layout);
1102 self.layout_elements[open_idx].layout_config_index = layout_config_index;
1103
1104 self.layout_elements[open_idx].element_configs.start = self.element_configs.len();
1106
1107 let mut shared_config_index: Option<usize> = None;
1109 if declaration.background_color.a > 0.0 {
1110 let idx = self.store_shared_config(SharedElementConfig {
1111 background_color: declaration.background_color,
1112 corner_radius: CornerRadius::default(),
1113 user_data: 0,
1114 });
1115 shared_config_index = Some(idx);
1116 self.attach_element_config(ElementConfigType::Shared, idx);
1117 }
1118 if !declaration.corner_radius.is_zero() {
1119 if let Some(idx) = shared_config_index {
1120 self.shared_element_configs[idx].corner_radius = declaration.corner_radius;
1121 } else {
1122 let idx = self.store_shared_config(SharedElementConfig {
1123 background_color: Color::rgba(0.0, 0.0, 0.0, 0.0),
1124 corner_radius: declaration.corner_radius,
1125 user_data: 0,
1126 });
1127 shared_config_index = Some(idx);
1128 self.attach_element_config(ElementConfigType::Shared, idx);
1129 }
1130 }
1131 if declaration.user_data != 0 {
1132 if let Some(idx) = shared_config_index {
1133 self.shared_element_configs[idx].user_data = declaration.user_data;
1134 } else {
1135 let idx = self.store_shared_config(SharedElementConfig {
1136 background_color: Color::rgba(0.0, 0.0, 0.0, 0.0),
1137 corner_radius: CornerRadius::default(),
1138 user_data: declaration.user_data,
1139 });
1140 self.attach_element_config(ElementConfigType::Shared, idx);
1141 }
1142 }
1143
1144 if let Some(image_data) = declaration.image_data.clone() {
1146 self.image_element_configs.push(image_data);
1147 let idx = self.image_element_configs.len() - 1;
1148 self.attach_element_config(ElementConfigType::Image, idx);
1149 }
1150
1151 if declaration.aspect_ratio > 0.0 {
1153 self.aspect_ratio_configs.push(declaration.aspect_ratio);
1154 let idx = self.aspect_ratio_configs.len() - 1;
1155 self.attach_element_config(ElementConfigType::Aspect, idx);
1156 self.aspect_ratio_element_indexes
1157 .push((self.layout_elements.len() - 1) as i32);
1158 }
1159
1160 if declaration.floating.attach_to != FloatingAttachToElement::None {
1162 let mut floating_config = declaration.floating;
1163 let stack_len = self.open_layout_element_stack.len();
1164
1165 if stack_len >= 2 {
1166 let hierarchical_parent_idx =
1167 self.open_layout_element_stack[stack_len - 2] as usize;
1168 let hierarchical_parent_id = self.layout_elements[hierarchical_parent_idx].id;
1169
1170 let mut clip_element_id: u32 = 0;
1171
1172 if declaration.floating.attach_to == FloatingAttachToElement::Parent {
1173 floating_config.parent_id = hierarchical_parent_id;
1174 if !self.open_clip_element_stack.is_empty() {
1175 clip_element_id =
1176 *self.open_clip_element_stack.last().unwrap() as u32;
1177 }
1178 } else if declaration.floating.attach_to
1179 == FloatingAttachToElement::ElementWithId
1180 {
1181 if let Some(parent_item) =
1182 self.layout_element_map.get(&floating_config.parent_id)
1183 {
1184 let parent_elem_idx = parent_item.layout_element_index as usize;
1185 clip_element_id =
1186 self.layout_element_clip_element_ids[parent_elem_idx] as u32;
1187 }
1188 } else if declaration.floating.attach_to
1189 == FloatingAttachToElement::Root
1190 {
1191 floating_config.parent_id =
1192 hash_string("Ply__RootContainer", 0).id;
1193 }
1194
1195 if declaration.floating.clip_to == FloatingClipToElement::None {
1196 clip_element_id = 0;
1197 }
1198
1199 let current_element_index =
1200 *self.open_layout_element_stack.last().unwrap();
1201 self.layout_element_clip_element_ids[current_element_index as usize] =
1202 clip_element_id as i32;
1203 self.open_clip_element_stack.push(clip_element_id as i32);
1204
1205 self.layout_element_tree_roots
1206 .push(LayoutElementTreeRoot {
1207 layout_element_index: current_element_index,
1208 parent_id: floating_config.parent_id,
1209 clip_element_id,
1210 z_index: floating_config.z_index,
1211 pointer_offset: Vector2::default(),
1212 });
1213
1214 self.floating_element_configs.push(floating_config);
1215 let idx = self.floating_element_configs.len() - 1;
1216 self.attach_element_config(ElementConfigType::Floating, idx);
1217 }
1218 }
1219
1220 if let Some(ref custom_data) = declaration.custom_data {
1222 self.custom_element_configs.push(custom_data.clone());
1223 let idx = self.custom_element_configs.len() - 1;
1224 self.attach_element_config(ElementConfigType::Custom, idx);
1225 }
1226
1227 if declaration.clip.horizontal || declaration.clip.vertical {
1229 let mut clip = declaration.clip;
1230
1231 let elem_id = self.layout_elements[open_idx].id;
1232
1233 if clip.scroll_x || clip.scroll_y {
1235 for scd in &self.scroll_container_datas {
1236 if scd.element_id == elem_id {
1237 clip.child_offset = scd.scroll_position;
1238 break;
1239 }
1240 }
1241 }
1242
1243 self.clip_element_configs.push(clip);
1244 let idx = self.clip_element_configs.len() - 1;
1245 self.attach_element_config(ElementConfigType::Clip, idx);
1246
1247 self.open_clip_element_stack.push(elem_id as i32);
1248
1249 if clip.scroll_x || clip.scroll_y {
1251 let mut found_existing = false;
1252 for scd in &mut self.scroll_container_datas {
1253 if elem_id == scd.element_id {
1254 scd.layout_element_index = open_idx as i32;
1255 scd.open_this_frame = true;
1256 found_existing = true;
1257 break;
1258 }
1259 }
1260 if !found_existing {
1261 self.scroll_container_datas.push(ScrollContainerDataInternal {
1262 layout_element_index: open_idx as i32,
1263 scroll_origin: Vector2::new(-1.0, -1.0),
1264 element_id: elem_id,
1265 open_this_frame: true,
1266 ..Default::default()
1267 });
1268 }
1269 }
1270 }
1271
1272 if !declaration.border.width.is_zero() {
1274 self.border_element_configs.push(declaration.border);
1275 let idx = self.border_element_configs.len() - 1;
1276 self.attach_element_config(ElementConfigType::Border, idx);
1277 }
1278
1279 while self.element_effects.len() <= open_idx {
1282 self.element_effects.push(Vec::new());
1283 }
1284 self.element_effects[open_idx] = declaration.effects.clone();
1285
1286 while self.element_shaders.len() <= open_idx {
1288 self.element_shaders.push(Vec::new());
1289 }
1290 self.element_shaders[open_idx] = declaration.shaders.clone();
1291
1292 while self.element_visual_rotations.len() <= open_idx {
1294 self.element_visual_rotations.push(None);
1295 }
1296 self.element_visual_rotations[open_idx] = declaration.visual_rotation;
1297
1298 while self.element_shape_rotations.len() <= open_idx {
1300 self.element_shape_rotations.push(None);
1301 }
1302 self.element_shape_rotations[open_idx] = declaration.shape_rotation;
1303
1304 if let Some(ref a11y) = declaration.accessibility {
1306 let elem_id = self.layout_elements[open_idx].id;
1307 if a11y.focusable {
1308 self.focusable_elements.push(FocusableEntry {
1309 element_id: elem_id,
1310 tab_index: a11y.tab_index,
1311 insertion_order: self.focusable_elements.len() as u32,
1312 });
1313 }
1314 self.accessibility_configs.insert(elem_id, a11y.clone());
1315 self.accessibility_element_order.push(elem_id);
1316 }
1317
1318 if let Some(ref ti_config) = declaration.text_input {
1320 let elem_id = self.layout_elements[open_idx].id;
1321 self.text_input_configs.push(ti_config.clone());
1322 let idx = self.text_input_configs.len() - 1;
1323 self.attach_element_config(ElementConfigType::TextInput, idx);
1324 self.text_input_element_ids.push(elem_id);
1325
1326 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
1328 item.is_text_input = true;
1329 }
1330
1331 self.text_edit_states.entry(elem_id)
1333 .or_insert_with(crate::text_input::TextEditState::default);
1334
1335 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1337 state.no_styles_movement = ti_config.no_styles_movement;
1338 }
1339
1340 if let Some((click_elem, click_x, click_y, click_shift)) = self.pending_text_click.take() {
1342 if click_elem == elem_id {
1343 if let Some(ref measure_fn) = self.measure_text_fn {
1344 let state = self.text_edit_states.get(&elem_id).cloned()
1345 .unwrap_or_default();
1346 let disp_text = crate::text_input::display_text(
1347 &state.text,
1348 &ti_config.placeholder,
1349 ti_config.is_password,
1350 );
1351 if !state.text.is_empty() {
1353 let is_double_click = state.last_click_element == elem_id
1355 && (self.current_time - state.last_click_time) < 0.4;
1356
1357 if ti_config.is_multiline {
1358 let elem_width = self.layout_element_map.get(&elem_id)
1360 .map(|item| item.bounding_box.width)
1361 .unwrap_or(200.0);
1362 let visual_lines = crate::text_input::wrap_lines(
1363 &disp_text,
1364 elem_width,
1365 ti_config.font_asset,
1366 ti_config.font_size,
1367 measure_fn.as_ref(),
1368 );
1369 let font_height = if ti_config.line_height > 0 {
1370 ti_config.line_height as f32
1371 } else {
1372 let config = crate::text::TextConfig {
1373 font_asset: ti_config.font_asset,
1374 font_size: ti_config.font_size,
1375 ..Default::default()
1376 };
1377 measure_fn(&"Mg", &config).height
1378 };
1379 let adjusted_y = click_y + state.scroll_offset_y;
1380 let clicked_line = (adjusted_y / font_height).floor().max(0.0) as usize;
1381 let clicked_line = clicked_line.min(visual_lines.len().saturating_sub(1));
1382
1383 let vl = &visual_lines[clicked_line];
1384 let line_char_x_positions = crate::text_input::compute_char_x_positions(
1385 &vl.text,
1386 ti_config.font_asset,
1387 ti_config.font_size,
1388 measure_fn.as_ref(),
1389 );
1390 let col = crate::text_input::find_nearest_char_boundary(
1391 click_x, &line_char_x_positions,
1392 );
1393 let global_pos = vl.global_char_start + col;
1394
1395 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1396 #[cfg(feature = "text-styling")]
1397 {
1398 let visual_pos = crate::text_input::styling::raw_to_cursor(&state.text, global_pos);
1399 if is_double_click {
1400 state.select_word_at_styled(visual_pos);
1401 } else {
1402 state.click_to_cursor_styled(visual_pos, click_shift);
1403 }
1404 }
1405 #[cfg(not(feature = "text-styling"))]
1406 {
1407 if is_double_click {
1408 state.select_word_at(global_pos);
1409 } else {
1410 if click_shift {
1411 if state.selection_anchor.is_none() {
1412 state.selection_anchor = Some(state.cursor_pos);
1413 }
1414 } else {
1415 state.selection_anchor = None;
1416 }
1417 state.cursor_pos = global_pos;
1418 state.reset_blink();
1419 }
1420 }
1421 state.last_click_time = self.current_time;
1422 state.last_click_element = elem_id;
1423 }
1424 } else {
1425 let char_x_positions = crate::text_input::compute_char_x_positions(
1427 &disp_text,
1428 ti_config.font_asset,
1429 ti_config.font_size,
1430 measure_fn.as_ref(),
1431 );
1432 let adjusted_x = click_x + state.scroll_offset;
1433
1434 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1435 let raw_click_pos = crate::text_input::find_nearest_char_boundary(
1436 adjusted_x, &char_x_positions,
1437 );
1438 #[cfg(feature = "text-styling")]
1439 {
1440 let visual_pos = crate::text_input::styling::raw_to_cursor(&state.text, raw_click_pos);
1441 if is_double_click {
1442 state.select_word_at_styled(visual_pos);
1443 } else {
1444 state.click_to_cursor_styled(visual_pos, click_shift);
1445 }
1446 }
1447 #[cfg(not(feature = "text-styling"))]
1448 {
1449 if is_double_click {
1450 state.select_word_at(raw_click_pos);
1451 } else {
1452 state.click_to_cursor(adjusted_x, &char_x_positions, click_shift);
1453 }
1454 }
1455 state.last_click_time = self.current_time;
1456 state.last_click_element = elem_id;
1457 }
1458 }
1459 } else if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
1460 state.cursor_pos = 0;
1461 state.selection_anchor = None;
1462 state.last_click_time = self.current_time;
1463 state.last_click_element = elem_id;
1464 state.reset_blink();
1465 }
1466 }
1467 } else {
1468 self.pending_text_click = Some((click_elem, click_x, click_y, click_shift));
1470 }
1471 }
1472
1473 if declaration.accessibility.is_none() || !declaration.accessibility.as_ref().unwrap().focusable {
1475 let already = self.focusable_elements.iter().any(|e| e.element_id == elem_id);
1477 if !already {
1478 self.focusable_elements.push(FocusableEntry {
1479 element_id: elem_id,
1480 tab_index: None,
1481 insertion_order: self.focusable_elements.len() as u32,
1482 });
1483 }
1484 }
1485 }
1486
1487 if declaration.preserve_focus {
1489 let elem_id = self.layout_elements[open_idx].id;
1490 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
1491 item.preserve_focus = true;
1492 }
1493 }
1494 }
1495
1496 pub fn close_element(&mut self) {
1497 if self.boolean_warnings.max_elements_exceeded {
1498 return;
1499 }
1500
1501 let open_idx = self.get_open_layout_element();
1502 let layout_config_index = self.layout_elements[open_idx].layout_config_index;
1503 let layout_config = self.layout_configs[layout_config_index];
1504
1505 let mut element_has_clip_horizontal = false;
1507 let mut element_has_clip_vertical = false;
1508 let element_configs_start = self.layout_elements[open_idx].element_configs.start;
1509 let element_configs_length = self.layout_elements[open_idx].element_configs.length;
1510
1511 for i in 0..element_configs_length {
1512 let config = &self.element_configs[element_configs_start + i as usize];
1513 if config.config_type == ElementConfigType::Clip {
1514 let clip = &self.clip_element_configs[config.config_index];
1515 element_has_clip_horizontal = clip.horizontal;
1516 element_has_clip_vertical = clip.vertical;
1517 self.open_clip_element_stack.pop();
1518 break;
1519 } else if config.config_type == ElementConfigType::Floating {
1520 self.open_clip_element_stack.pop();
1521 }
1522 }
1523
1524 let left_right_padding =
1525 (layout_config.padding.left + layout_config.padding.right) as f32;
1526 let top_bottom_padding =
1527 (layout_config.padding.top + layout_config.padding.bottom) as f32;
1528
1529 let children_length = self.layout_elements[open_idx].children_length;
1530
1531 let children_start = self.layout_element_children.len();
1533 self.layout_elements[open_idx].children_start = children_start;
1534
1535 if layout_config.layout_direction == LayoutDirection::LeftToRight {
1536 self.layout_elements[open_idx].dimensions.width = left_right_padding;
1537 self.layout_elements[open_idx].min_dimensions.width = left_right_padding;
1538
1539 for i in 0..children_length {
1540 let buf_idx = self.layout_element_children_buffer.len()
1541 - children_length as usize
1542 + i as usize;
1543 let child_index = self.layout_element_children_buffer[buf_idx];
1544 let child = &self.layout_elements[child_index as usize];
1545 let child_width = child.dimensions.width;
1546 let child_height = child.dimensions.height;
1547 let child_min_width = child.min_dimensions.width;
1548 let child_min_height = child.min_dimensions.height;
1549
1550 self.layout_elements[open_idx].dimensions.width += child_width;
1551 let current_height = self.layout_elements[open_idx].dimensions.height;
1552 self.layout_elements[open_idx].dimensions.height =
1553 f32::max(current_height, child_height + top_bottom_padding);
1554
1555 if !element_has_clip_horizontal {
1556 self.layout_elements[open_idx].min_dimensions.width += child_min_width;
1557 }
1558 if !element_has_clip_vertical {
1559 let current_min_h = self.layout_elements[open_idx].min_dimensions.height;
1560 self.layout_elements[open_idx].min_dimensions.height =
1561 f32::max(current_min_h, child_min_height + top_bottom_padding);
1562 }
1563 self.layout_element_children.push(child_index);
1564 }
1565 let child_gap =
1566 (children_length.saturating_sub(1) as u32 * layout_config.child_gap as u32) as f32;
1567 self.layout_elements[open_idx].dimensions.width += child_gap;
1568 if !element_has_clip_horizontal {
1569 self.layout_elements[open_idx].min_dimensions.width += child_gap;
1570 }
1571 } else {
1572 self.layout_elements[open_idx].dimensions.height = top_bottom_padding;
1574 self.layout_elements[open_idx].min_dimensions.height = top_bottom_padding;
1575
1576 for i in 0..children_length {
1577 let buf_idx = self.layout_element_children_buffer.len()
1578 - children_length as usize
1579 + i as usize;
1580 let child_index = self.layout_element_children_buffer[buf_idx];
1581 let child = &self.layout_elements[child_index as usize];
1582 let child_width = child.dimensions.width;
1583 let child_height = child.dimensions.height;
1584 let child_min_width = child.min_dimensions.width;
1585 let child_min_height = child.min_dimensions.height;
1586
1587 self.layout_elements[open_idx].dimensions.height += child_height;
1588 let current_width = self.layout_elements[open_idx].dimensions.width;
1589 self.layout_elements[open_idx].dimensions.width =
1590 f32::max(current_width, child_width + left_right_padding);
1591
1592 if !element_has_clip_vertical {
1593 self.layout_elements[open_idx].min_dimensions.height += child_min_height;
1594 }
1595 if !element_has_clip_horizontal {
1596 let current_min_w = self.layout_elements[open_idx].min_dimensions.width;
1597 self.layout_elements[open_idx].min_dimensions.width =
1598 f32::max(current_min_w, child_min_width + left_right_padding);
1599 }
1600 self.layout_element_children.push(child_index);
1601 }
1602 let child_gap =
1603 (children_length.saturating_sub(1) as u32 * layout_config.child_gap as u32) as f32;
1604 self.layout_elements[open_idx].dimensions.height += child_gap;
1605 if !element_has_clip_vertical {
1606 self.layout_elements[open_idx].min_dimensions.height += child_gap;
1607 }
1608 }
1609
1610 let remove_count = children_length as usize;
1612 let new_len = self.layout_element_children_buffer.len().saturating_sub(remove_count);
1613 self.layout_element_children_buffer.truncate(new_len);
1614
1615 {
1617 let sizing_type = self.layout_configs[layout_config_index].sizing.width.type_;
1618 if sizing_type != SizingType::Percent {
1619 let mut max_w = self.layout_configs[layout_config_index].sizing.width.min_max.max;
1620 if max_w <= 0.0 {
1621 max_w = MAXFLOAT;
1622 self.layout_configs[layout_config_index].sizing.width.min_max.max = max_w;
1623 }
1624 let min_w = self.layout_configs[layout_config_index].sizing.width.min_max.min;
1625 self.layout_elements[open_idx].dimensions.width = f32::min(
1626 f32::max(self.layout_elements[open_idx].dimensions.width, min_w),
1627 max_w,
1628 );
1629 self.layout_elements[open_idx].min_dimensions.width = f32::min(
1630 f32::max(self.layout_elements[open_idx].min_dimensions.width, min_w),
1631 max_w,
1632 );
1633 } else {
1634 self.layout_elements[open_idx].dimensions.width = 0.0;
1635 }
1636 }
1637
1638 {
1640 let sizing_type = self.layout_configs[layout_config_index].sizing.height.type_;
1641 if sizing_type != SizingType::Percent {
1642 let mut max_h = self.layout_configs[layout_config_index].sizing.height.min_max.max;
1643 if max_h <= 0.0 {
1644 max_h = MAXFLOAT;
1645 self.layout_configs[layout_config_index].sizing.height.min_max.max = max_h;
1646 }
1647 let min_h = self.layout_configs[layout_config_index].sizing.height.min_max.min;
1648 self.layout_elements[open_idx].dimensions.height = f32::min(
1649 f32::max(self.layout_elements[open_idx].dimensions.height, min_h),
1650 max_h,
1651 );
1652 self.layout_elements[open_idx].min_dimensions.height = f32::min(
1653 f32::max(self.layout_elements[open_idx].min_dimensions.height, min_h),
1654 max_h,
1655 );
1656 } else {
1657 self.layout_elements[open_idx].dimensions.height = 0.0;
1658 }
1659 }
1660
1661 self.update_aspect_ratio_box(open_idx);
1662
1663 if let Some(shape_rot) = self.element_shape_rotations.get(open_idx).copied().flatten() {
1665 if !shape_rot.is_noop() {
1666 let orig_w = self.layout_elements[open_idx].dimensions.width;
1667 let orig_h = self.layout_elements[open_idx].dimensions.height;
1668
1669 let cr = self
1671 .find_element_config_index(open_idx, ElementConfigType::Shared)
1672 .map(|idx| self.shared_element_configs[idx].corner_radius)
1673 .unwrap_or_default();
1674
1675 let (eff_w, eff_h) = crate::math::compute_rotated_aabb(
1676 orig_w,
1677 orig_h,
1678 &cr,
1679 shape_rot.rotation_radians,
1680 );
1681
1682 while self.element_pre_rotation_dimensions.len() <= open_idx {
1684 self.element_pre_rotation_dimensions.push(None);
1685 }
1686 self.element_pre_rotation_dimensions[open_idx] =
1687 Some(Dimensions::new(orig_w, orig_h));
1688
1689 self.layout_elements[open_idx].dimensions.width = eff_w;
1691 self.layout_elements[open_idx].dimensions.height = eff_h;
1692 self.layout_elements[open_idx].min_dimensions.width = eff_w;
1693 self.layout_elements[open_idx].min_dimensions.height = eff_h;
1694 }
1695 }
1696
1697 let element_is_floating =
1698 self.element_has_config(open_idx, ElementConfigType::Floating);
1699
1700 self.open_layout_element_stack.pop();
1702
1703 if self.open_layout_element_stack.len() > 1 {
1705 if element_is_floating {
1706 let parent_idx = self.get_open_layout_element();
1707 self.layout_elements[parent_idx].floating_children_count += 1;
1708 return;
1709 }
1710 let parent_idx = self.get_open_layout_element();
1711 self.layout_elements[parent_idx].children_length += 1;
1712 self.layout_element_children_buffer.push(open_idx as i32);
1713 }
1714 }
1715
1716 pub fn open_text_element(
1717 &mut self,
1718 text: &str,
1719 text_config_index: usize,
1720 ) {
1721 if self.boolean_warnings.max_elements_exceeded {
1722 return;
1723 }
1724
1725 let parent_idx = self.get_open_layout_element();
1726 let parent_id = self.layout_elements[parent_idx].id;
1727 let parent_children_count = self.layout_elements[parent_idx].children_length;
1728
1729 let text_element = LayoutElement {
1731 text_data_index: -1,
1732 ..Default::default()
1733 };
1734 self.layout_elements.push(text_element);
1735 let text_elem_idx = (self.layout_elements.len() - 1) as i32;
1736
1737 while self.layout_element_clip_element_ids.len() < self.layout_elements.len() {
1738 self.layout_element_clip_element_ids.push(0);
1739 }
1740 if !self.open_clip_element_stack.is_empty() {
1741 let clip_id = *self.open_clip_element_stack.last().unwrap();
1742 self.layout_element_clip_element_ids[text_elem_idx as usize] = clip_id;
1743 } else {
1744 self.layout_element_clip_element_ids[text_elem_idx as usize] = 0;
1745 }
1746
1747 self.layout_element_children_buffer.push(text_elem_idx);
1748
1749 let text_config = self.text_element_configs[text_config_index].clone();
1751 let text_measured =
1752 self.measure_text_cached(text, &text_config);
1753
1754 let element_id = hash_number(parent_children_count as u32, parent_id);
1755 self.layout_elements[text_elem_idx as usize].id = element_id.id;
1756 self.add_hash_map_item(&element_id, text_elem_idx);
1757 if self.debug_mode_enabled {
1758 self.layout_element_id_strings.push(element_id.string_id);
1759 }
1760
1761 if text_config.accessible {
1765 let a11y = crate::accessibility::AccessibilityConfig {
1766 role: crate::accessibility::AccessibilityRole::StaticText,
1767 label: text.to_string(),
1768 ..Default::default()
1769 };
1770 self.accessibility_configs.insert(element_id.id, a11y);
1771 self.accessibility_element_order.push(element_id.id);
1772 }
1773
1774 let text_width = text_measured.unwrapped_dimensions.width;
1775 let text_height = if text_config.line_height > 0 {
1776 text_config.line_height as f32
1777 } else {
1778 text_measured.unwrapped_dimensions.height
1779 };
1780 let min_width = text_measured.min_width;
1781
1782 self.layout_elements[text_elem_idx as usize].dimensions =
1783 Dimensions::new(text_width, text_height);
1784 self.layout_elements[text_elem_idx as usize].min_dimensions =
1785 Dimensions::new(min_width, text_height);
1786
1787 let text_data = TextElementData {
1789 text: text.to_string(),
1790 preferred_dimensions: text_measured.unwrapped_dimensions,
1791 element_index: text_elem_idx,
1792 wrapped_lines_start: 0,
1793 wrapped_lines_length: 0,
1794 };
1795 self.text_element_data.push(text_data);
1796 let text_data_idx = (self.text_element_data.len() - 1) as i32;
1797 self.layout_elements[text_elem_idx as usize].text_data_index = text_data_idx;
1798
1799 self.layout_elements[text_elem_idx as usize].element_configs.start =
1801 self.element_configs.len();
1802 self.element_configs.push(ElementConfig {
1803 config_type: ElementConfigType::Text,
1804 config_index: text_config_index,
1805 });
1806 self.layout_elements[text_elem_idx as usize].element_configs.length = 1;
1807
1808 let default_layout_idx = self.store_layout_config(LayoutConfig::default());
1810 self.layout_elements[text_elem_idx as usize].layout_config_index = default_layout_idx;
1811
1812 self.layout_elements[parent_idx].children_length += 1;
1814 }
1815
1816 fn font_height(&mut self, font_asset: Option<&'static crate::renderer::FontAsset>, font_size: u16) -> f32 {
1819 let font_key = font_asset.map(|a| a.key()).unwrap_or("");
1820 let key = (font_key, font_size);
1821 if let Some(&h) = self.font_height_cache.get(&key) {
1822 return h;
1823 }
1824 let h = if let Some(ref measure_fn) = self.measure_text_fn {
1825 let config = TextConfig {
1826 font_asset,
1827 font_size,
1828 ..Default::default()
1829 };
1830 measure_fn("Mg", &config).height
1831 } else {
1832 font_size as f32
1833 };
1834 let font_loaded = font_asset
1835 .map_or(true, |a| crate::renderer::FontManager::is_loaded(a));
1836 if font_loaded {
1837 self.font_height_cache.insert(key, h);
1838 }
1839 h
1840 }
1841
1842 fn measure_text_cached(
1843 &mut self,
1844 text: &str,
1845 config: &TextConfig,
1846 ) -> MeasureTextCacheItem {
1847 match &self.measure_text_fn {
1848 Some(_) => {},
1849 None => {
1850 if !self.boolean_warnings.text_measurement_fn_not_set {
1851 self.boolean_warnings.text_measurement_fn_not_set = true;
1852 }
1853 return MeasureTextCacheItem::default();
1854 }
1855 };
1856
1857 let id = hash_string_contents_with_config(text, config);
1858
1859 if let Some(item) = self.measure_text_cache.get_mut(&id) {
1861 item.generation = self.generation;
1862 return *item;
1863 }
1864
1865 let text_data = text.as_bytes();
1867 let text_length = text_data.len() as i32;
1868
1869 let space_str = " ";
1870 let space_width = (self.measure_text_fn.as_ref().unwrap())(space_str, config).width;
1871
1872 let mut start: i32 = 0;
1873 let mut end: i32 = 0;
1874 let mut line_width: f32 = 0.0;
1875 let mut measured_width: f32 = 0.0;
1876 let mut measured_height: f32 = 0.0;
1877 let mut min_width: f32 = 0.0;
1878 let mut contains_newlines = false;
1879
1880 let mut temp_word_next: i32 = -1;
1881 let mut previous_word_index: i32 = -1;
1882
1883 while end < text_length {
1884 let current = text_data[end as usize];
1885 if current == b' ' || current == b'\n' {
1886 let length = end - start;
1887 let mut dimensions = Dimensions::default();
1888 if length > 0 {
1889 let substr =
1890 core::str::from_utf8(&text_data[start as usize..end as usize]).unwrap();
1891 dimensions = (self.measure_text_fn.as_ref().unwrap())(substr, config);
1892 }
1893 min_width = f32::max(dimensions.width, min_width);
1894 measured_height = f32::max(measured_height, dimensions.height);
1895
1896 if current == b' ' {
1897 dimensions.width += space_width;
1898 let word = MeasuredWord {
1899 start_offset: start,
1900 length: length + 1,
1901 width: dimensions.width,
1902 next: -1,
1903 };
1904 let word_idx = self.add_measured_word(word, previous_word_index);
1905 if previous_word_index == -1 {
1906 temp_word_next = word_idx;
1907 }
1908 previous_word_index = word_idx;
1909 line_width += dimensions.width;
1910 }
1911 if current == b'\n' {
1912 if length > 0 {
1913 let word = MeasuredWord {
1914 start_offset: start,
1915 length,
1916 width: dimensions.width,
1917 next: -1,
1918 };
1919 let word_idx = self.add_measured_word(word, previous_word_index);
1920 if previous_word_index == -1 {
1921 temp_word_next = word_idx;
1922 }
1923 previous_word_index = word_idx;
1924 }
1925 let newline_word = MeasuredWord {
1926 start_offset: end + 1,
1927 length: 0,
1928 width: 0.0,
1929 next: -1,
1930 };
1931 let word_idx = self.add_measured_word(newline_word, previous_word_index);
1932 if previous_word_index == -1 {
1933 temp_word_next = word_idx;
1934 }
1935 previous_word_index = word_idx;
1936 line_width += dimensions.width;
1937 measured_width = f32::max(line_width, measured_width);
1938 contains_newlines = true;
1939 line_width = 0.0;
1940 }
1941 start = end + 1;
1942 }
1943 end += 1;
1944 }
1945
1946 if end - start > 0 {
1947 let substr =
1948 core::str::from_utf8(&text_data[start as usize..end as usize]).unwrap();
1949 let dimensions = (self.measure_text_fn.as_ref().unwrap())(substr, config);
1950 let word = MeasuredWord {
1951 start_offset: start,
1952 length: end - start,
1953 width: dimensions.width,
1954 next: -1,
1955 };
1956 let word_idx = self.add_measured_word(word, previous_word_index);
1957 if previous_word_index == -1 {
1958 temp_word_next = word_idx;
1959 }
1960 line_width += dimensions.width;
1961 measured_height = f32::max(measured_height, dimensions.height);
1962 min_width = f32::max(dimensions.width, min_width);
1963 }
1964
1965 measured_width =
1966 f32::max(line_width, measured_width) - config.letter_spacing as f32;
1967
1968 let result = MeasureTextCacheItem {
1969 id,
1970 generation: self.generation,
1971 measured_words_start_index: temp_word_next,
1972 unwrapped_dimensions: Dimensions::new(measured_width, measured_height),
1973 min_width,
1974 contains_newlines,
1975 };
1976 self.measure_text_cache.insert(id, result);
1977 result
1978 }
1979
1980 fn add_measured_word(&mut self, word: MeasuredWord, previous_word_index: i32) -> i32 {
1981 let new_index: i32;
1982 if let Some(&free_idx) = self.measured_words_free_list.last() {
1983 self.measured_words_free_list.pop();
1984 new_index = free_idx;
1985 self.measured_words[free_idx as usize] = word;
1986 } else {
1987 self.measured_words.push(word);
1988 new_index = (self.measured_words.len() - 1) as i32;
1989 }
1990 if previous_word_index >= 0 {
1991 self.measured_words[previous_word_index as usize].next = new_index;
1992 }
1993 new_index
1994 }
1995
1996 pub fn begin_layout(&mut self) {
1997 self.initialize_ephemeral_memory();
1998 self.generation += 1;
1999 self.dynamic_element_index = 0;
2000
2001 self.evict_stale_text_cache();
2003
2004 let root_width = self.layout_dimensions.width;
2005 let root_height = self.layout_dimensions.height;
2006
2007 self.boolean_warnings = BooleanWarnings::default();
2008
2009 let root_id = hash_string("Ply__RootContainer", 0);
2010 self.open_element_with_id(&root_id);
2011
2012 let root_decl = ElementDeclaration {
2013 layout: LayoutConfig {
2014 sizing: SizingConfig {
2015 width: SizingAxis {
2016 type_: SizingType::Fixed,
2017 min_max: SizingMinMax {
2018 min: root_width,
2019 max: root_width,
2020 },
2021 percent: 0.0,
2022 },
2023 height: SizingAxis {
2024 type_: SizingType::Fixed,
2025 min_max: SizingMinMax {
2026 min: root_height,
2027 max: root_height,
2028 },
2029 percent: 0.0,
2030 },
2031 },
2032 ..Default::default()
2033 },
2034 ..Default::default()
2035 };
2036 self.configure_open_element(&root_decl);
2037 self.open_layout_element_stack.push(0);
2038 self.layout_element_tree_roots.push(LayoutElementTreeRoot {
2039 layout_element_index: 0,
2040 ..Default::default()
2041 });
2042 }
2043
2044 pub fn end_layout(&mut self) -> &[InternalRenderCommand<CustomElementData>] {
2045 self.close_element();
2046
2047 if self.open_layout_element_stack.len() > 1 {
2048 }
2050
2051 if self.debug_mode_enabled {
2052 self.render_debug_view();
2053 }
2054
2055 self.calculate_final_layout();
2056 &self.render_commands
2057 }
2058
2059 fn evict_stale_text_cache(&mut self) {
2062 let gen = self.generation;
2063 let measured_words = &mut self.measured_words;
2064 let free_list = &mut self.measured_words_free_list;
2065 self.measure_text_cache.retain(|_, item| {
2066 if gen.wrapping_sub(item.generation) <= 2 {
2067 true
2068 } else {
2069 let mut idx = item.measured_words_start_index;
2071 while idx != -1 {
2072 let word = measured_words[idx as usize];
2073 free_list.push(idx);
2074 idx = word.next;
2075 }
2076 false
2077 }
2078 });
2079 }
2080
2081 fn initialize_ephemeral_memory(&mut self) {
2082 self.layout_element_children_buffer.clear();
2083 self.layout_elements.clear();
2084 self.layout_configs.clear();
2085 self.element_configs.clear();
2086 self.text_element_configs.clear();
2087 self.aspect_ratio_configs.clear();
2088 self.image_element_configs.clear();
2089 self.floating_element_configs.clear();
2090 self.clip_element_configs.clear();
2091 self.custom_element_configs.clear();
2092 self.border_element_configs.clear();
2093 self.shared_element_configs.clear();
2094 self.element_effects.clear();
2095 self.element_shaders.clear();
2096 self.element_visual_rotations.clear();
2097 self.element_shape_rotations.clear();
2098 self.element_pre_rotation_dimensions.clear();
2099 self.layout_element_id_strings.clear();
2100 self.wrapped_text_lines.clear();
2101 self.tree_node_array.clear();
2102 self.layout_element_tree_roots.clear();
2103 self.layout_element_children.clear();
2104 self.open_layout_element_stack.clear();
2105 self.text_element_data.clear();
2106 self.aspect_ratio_element_indexes.clear();
2107 self.render_commands.clear();
2108 self.tree_node_visited.clear();
2109 self.open_clip_element_stack.clear();
2110 self.reusable_element_index_buffer.clear();
2111 self.layout_element_clip_element_ids.clear();
2112 self.dynamic_string_data.clear();
2113 self.focusable_elements.clear();
2114 self.accessibility_configs.clear();
2115 self.accessibility_element_order.clear();
2116 self.text_input_configs.clear();
2117 self.text_input_element_ids.clear();
2118 }
2119
2120 fn size_containers_along_axis(&mut self, x_axis: bool) {
2121 let mut bfs_buffer: Vec<i32> = Vec::new();
2122 let mut resizable_container_buffer: Vec<i32> = Vec::new();
2123
2124 for root_index in 0..self.layout_element_tree_roots.len() {
2125 bfs_buffer.clear();
2126 let root = self.layout_element_tree_roots[root_index];
2127 let root_elem_idx = root.layout_element_index as usize;
2128 bfs_buffer.push(root.layout_element_index);
2129
2130 if self.element_has_config(root_elem_idx, ElementConfigType::Floating) {
2132 if let Some(float_cfg_idx) =
2133 self.find_element_config_index(root_elem_idx, ElementConfigType::Floating)
2134 {
2135 let parent_id = self.floating_element_configs[float_cfg_idx].parent_id;
2136 if let Some(parent_item) = self.layout_element_map.get(&parent_id) {
2137 let parent_elem_idx = parent_item.layout_element_index as usize;
2138 let parent_dims = self.layout_elements[parent_elem_idx].dimensions;
2139 let root_layout_idx =
2140 self.layout_elements[root_elem_idx].layout_config_index;
2141
2142 let w_type = self.layout_configs[root_layout_idx].sizing.width.type_;
2143 match w_type {
2144 SizingType::Grow => {
2145 self.layout_elements[root_elem_idx].dimensions.width =
2146 parent_dims.width;
2147 }
2148 SizingType::Percent => {
2149 self.layout_elements[root_elem_idx].dimensions.width =
2150 parent_dims.width
2151 * self.layout_configs[root_layout_idx]
2152 .sizing
2153 .width
2154 .percent;
2155 }
2156 _ => {}
2157 }
2158 let h_type = self.layout_configs[root_layout_idx].sizing.height.type_;
2159 match h_type {
2160 SizingType::Grow => {
2161 self.layout_elements[root_elem_idx].dimensions.height =
2162 parent_dims.height;
2163 }
2164 SizingType::Percent => {
2165 self.layout_elements[root_elem_idx].dimensions.height =
2166 parent_dims.height
2167 * self.layout_configs[root_layout_idx]
2168 .sizing
2169 .height
2170 .percent;
2171 }
2172 _ => {}
2173 }
2174 }
2175 }
2176 }
2177
2178 let root_layout_idx = self.layout_elements[root_elem_idx].layout_config_index;
2180 if self.layout_configs[root_layout_idx].sizing.width.type_ != SizingType::Percent {
2181 let min = self.layout_configs[root_layout_idx].sizing.width.min_max.min;
2182 let max = self.layout_configs[root_layout_idx].sizing.width.min_max.max;
2183 self.layout_elements[root_elem_idx].dimensions.width = f32::min(
2184 f32::max(self.layout_elements[root_elem_idx].dimensions.width, min),
2185 max,
2186 );
2187 }
2188 if self.layout_configs[root_layout_idx].sizing.height.type_ != SizingType::Percent {
2189 let min = self.layout_configs[root_layout_idx].sizing.height.min_max.min;
2190 let max = self.layout_configs[root_layout_idx].sizing.height.min_max.max;
2191 self.layout_elements[root_elem_idx].dimensions.height = f32::min(
2192 f32::max(self.layout_elements[root_elem_idx].dimensions.height, min),
2193 max,
2194 );
2195 }
2196
2197 let mut i = 0;
2198 while i < bfs_buffer.len() {
2199 let parent_index = bfs_buffer[i] as usize;
2200 i += 1;
2201
2202 let parent_layout_idx = self.layout_elements[parent_index].layout_config_index;
2203 let parent_config = self.layout_configs[parent_layout_idx];
2204 let parent_size = if x_axis {
2205 self.layout_elements[parent_index].dimensions.width
2206 } else {
2207 self.layout_elements[parent_index].dimensions.height
2208 };
2209 let parent_padding = if x_axis {
2210 (parent_config.padding.left + parent_config.padding.right) as f32
2211 } else {
2212 (parent_config.padding.top + parent_config.padding.bottom) as f32
2213 };
2214 let sizing_along_axis = (x_axis
2215 && parent_config.layout_direction == LayoutDirection::LeftToRight)
2216 || (!x_axis
2217 && parent_config.layout_direction == LayoutDirection::TopToBottom);
2218
2219 let mut inner_content_size: f32 = 0.0;
2220 let mut total_padding_and_child_gaps = parent_padding;
2221 let mut grow_container_count: i32 = 0;
2222 let parent_child_gap = parent_config.child_gap as f32;
2223
2224 resizable_container_buffer.clear();
2225
2226 let children_start = self.layout_elements[parent_index].children_start;
2227 let children_length = self.layout_elements[parent_index].children_length as usize;
2228
2229 for child_offset in 0..children_length {
2230 let child_element_index =
2231 self.layout_element_children[children_start + child_offset] as usize;
2232 let child_layout_idx =
2233 self.layout_elements[child_element_index].layout_config_index;
2234 let child_sizing = if x_axis {
2235 self.layout_configs[child_layout_idx].sizing.width
2236 } else {
2237 self.layout_configs[child_layout_idx].sizing.height
2238 };
2239 let child_size = if x_axis {
2240 self.layout_elements[child_element_index].dimensions.width
2241 } else {
2242 self.layout_elements[child_element_index].dimensions.height
2243 };
2244
2245 let is_text_element =
2246 self.element_has_config(child_element_index, ElementConfigType::Text);
2247 let has_children = self.layout_elements[child_element_index].children_length > 0;
2248
2249 if !is_text_element && has_children {
2250 bfs_buffer.push(child_element_index as i32);
2251 }
2252
2253 let is_wrapping_text = if is_text_element {
2254 if let Some(text_cfg_idx) = self.find_element_config_index(
2255 child_element_index,
2256 ElementConfigType::Text,
2257 ) {
2258 self.text_element_configs[text_cfg_idx].wrap_mode
2259 == WrapMode::Words
2260 } else {
2261 false
2262 }
2263 } else {
2264 false
2265 };
2266
2267 if child_sizing.type_ != SizingType::Percent
2268 && child_sizing.type_ != SizingType::Fixed
2269 && (!is_text_element || is_wrapping_text)
2270 {
2271 resizable_container_buffer.push(child_element_index as i32);
2272 }
2273
2274 if sizing_along_axis {
2275 inner_content_size += if child_sizing.type_ == SizingType::Percent {
2276 0.0
2277 } else {
2278 child_size
2279 };
2280 if child_sizing.type_ == SizingType::Grow {
2281 grow_container_count += 1;
2282 }
2283 if child_offset > 0 {
2284 inner_content_size += parent_child_gap;
2285 total_padding_and_child_gaps += parent_child_gap;
2286 }
2287 } else {
2288 inner_content_size = f32::max(child_size, inner_content_size);
2289 }
2290 }
2291
2292 for child_offset in 0..children_length {
2294 let child_element_index =
2295 self.layout_element_children[children_start + child_offset] as usize;
2296 let child_layout_idx =
2297 self.layout_elements[child_element_index].layout_config_index;
2298 let child_sizing = if x_axis {
2299 self.layout_configs[child_layout_idx].sizing.width
2300 } else {
2301 self.layout_configs[child_layout_idx].sizing.height
2302 };
2303 if child_sizing.type_ == SizingType::Percent {
2304 let new_size =
2305 (parent_size - total_padding_and_child_gaps) * child_sizing.percent;
2306 if x_axis {
2307 self.layout_elements[child_element_index].dimensions.width = new_size;
2308 } else {
2309 self.layout_elements[child_element_index].dimensions.height = new_size;
2310 }
2311 if sizing_along_axis {
2312 inner_content_size += new_size;
2313 }
2314 self.update_aspect_ratio_box(child_element_index);
2315 }
2316 }
2317
2318 if sizing_along_axis {
2319 let size_to_distribute = parent_size - parent_padding - inner_content_size;
2320
2321 if size_to_distribute < 0.0 {
2322 let parent_clips = if let Some(clip_idx) = self
2324 .find_element_config_index(parent_index, ElementConfigType::Clip)
2325 {
2326 let clip = &self.clip_element_configs[clip_idx];
2327 (x_axis && clip.horizontal) || (!x_axis && clip.vertical)
2328 } else {
2329 false
2330 };
2331 if parent_clips {
2332 continue;
2333 }
2334
2335 let mut distribute = size_to_distribute;
2337 while distribute < -EPSILON && !resizable_container_buffer.is_empty() {
2338 let mut largest: f32 = 0.0;
2339 let mut second_largest: f32 = 0.0;
2340 let mut width_to_add = distribute;
2341
2342 for &child_idx in &resizable_container_buffer {
2343 let cs = if x_axis {
2344 self.layout_elements[child_idx as usize].dimensions.width
2345 } else {
2346 self.layout_elements[child_idx as usize].dimensions.height
2347 };
2348 if float_equal(cs, largest) {
2349 continue;
2350 }
2351 if cs > largest {
2352 second_largest = largest;
2353 largest = cs;
2354 }
2355 if cs < largest {
2356 second_largest = f32::max(second_largest, cs);
2357 width_to_add = second_largest - largest;
2358 }
2359 }
2360 width_to_add = f32::max(
2361 width_to_add,
2362 distribute / resizable_container_buffer.len() as f32,
2363 );
2364
2365 let mut j = 0;
2366 while j < resizable_container_buffer.len() {
2367 let child_idx = resizable_container_buffer[j] as usize;
2368 let current_size = if x_axis {
2369 self.layout_elements[child_idx].dimensions.width
2370 } else {
2371 self.layout_elements[child_idx].dimensions.height
2372 };
2373 let min_size = if x_axis {
2374 self.layout_elements[child_idx].min_dimensions.width
2375 } else {
2376 self.layout_elements[child_idx].min_dimensions.height
2377 };
2378 if float_equal(current_size, largest) {
2379 let new_size = current_size + width_to_add;
2380 if new_size <= min_size {
2381 if x_axis {
2382 self.layout_elements[child_idx].dimensions.width = min_size;
2383 } else {
2384 self.layout_elements[child_idx].dimensions.height = min_size;
2385 }
2386 distribute -= min_size - current_size;
2387 resizable_container_buffer.swap_remove(j);
2388 continue;
2389 }
2390 if x_axis {
2391 self.layout_elements[child_idx].dimensions.width = new_size;
2392 } else {
2393 self.layout_elements[child_idx].dimensions.height = new_size;
2394 }
2395 distribute -= new_size - current_size;
2396 }
2397 j += 1;
2398 }
2399 }
2400 } else if size_to_distribute > 0.0 && grow_container_count > 0 {
2401 let mut j = 0;
2403 while j < resizable_container_buffer.len() {
2404 let child_idx = resizable_container_buffer[j] as usize;
2405 let child_layout_idx =
2406 self.layout_elements[child_idx].layout_config_index;
2407 let child_sizing_type = if x_axis {
2408 self.layout_configs[child_layout_idx].sizing.width.type_
2409 } else {
2410 self.layout_configs[child_layout_idx].sizing.height.type_
2411 };
2412 if child_sizing_type != SizingType::Grow {
2413 resizable_container_buffer.swap_remove(j);
2414 } else {
2415 j += 1;
2416 }
2417 }
2418
2419 let mut distribute = size_to_distribute;
2420 while distribute > EPSILON && !resizable_container_buffer.is_empty() {
2421 let mut smallest: f32 = MAXFLOAT;
2422 let mut second_smallest: f32 = MAXFLOAT;
2423 let mut width_to_add = distribute;
2424
2425 for &child_idx in &resizable_container_buffer {
2426 let cs = if x_axis {
2427 self.layout_elements[child_idx as usize].dimensions.width
2428 } else {
2429 self.layout_elements[child_idx as usize].dimensions.height
2430 };
2431 if float_equal(cs, smallest) {
2432 continue;
2433 }
2434 if cs < smallest {
2435 second_smallest = smallest;
2436 smallest = cs;
2437 }
2438 if cs > smallest {
2439 second_smallest = f32::min(second_smallest, cs);
2440 width_to_add = second_smallest - smallest;
2441 }
2442 }
2443 width_to_add = f32::min(
2444 width_to_add,
2445 distribute / resizable_container_buffer.len() as f32,
2446 );
2447
2448 let mut j = 0;
2449 while j < resizable_container_buffer.len() {
2450 let child_idx = resizable_container_buffer[j] as usize;
2451 let child_layout_idx =
2452 self.layout_elements[child_idx].layout_config_index;
2453 let max_size = if x_axis {
2454 self.layout_configs[child_layout_idx]
2455 .sizing
2456 .width
2457 .min_max
2458 .max
2459 } else {
2460 self.layout_configs[child_layout_idx]
2461 .sizing
2462 .height
2463 .min_max
2464 .max
2465 };
2466 let child_size_ref = if x_axis {
2467 &mut self.layout_elements[child_idx].dimensions.width
2468 } else {
2469 &mut self.layout_elements[child_idx].dimensions.height
2470 };
2471 if float_equal(*child_size_ref, smallest) {
2472 let previous = *child_size_ref;
2473 *child_size_ref += width_to_add;
2474 if *child_size_ref >= max_size {
2475 *child_size_ref = max_size;
2476 resizable_container_buffer.swap_remove(j);
2477 continue;
2478 }
2479 distribute -= *child_size_ref - previous;
2480 }
2481 j += 1;
2482 }
2483 }
2484 }
2485 } else {
2486 for &child_idx in &resizable_container_buffer {
2488 let child_idx = child_idx as usize;
2489 let child_layout_idx =
2490 self.layout_elements[child_idx].layout_config_index;
2491 let child_sizing = if x_axis {
2492 self.layout_configs[child_layout_idx].sizing.width
2493 } else {
2494 self.layout_configs[child_layout_idx].sizing.height
2495 };
2496 let min_size = if x_axis {
2497 self.layout_elements[child_idx].min_dimensions.width
2498 } else {
2499 self.layout_elements[child_idx].min_dimensions.height
2500 };
2501
2502 let mut max_size = parent_size - parent_padding;
2503 if let Some(clip_idx) =
2504 self.find_element_config_index(parent_index, ElementConfigType::Clip)
2505 {
2506 let clip = &self.clip_element_configs[clip_idx];
2507 if (x_axis && clip.horizontal) || (!x_axis && clip.vertical) {
2508 max_size = f32::max(max_size, inner_content_size);
2509 }
2510 }
2511
2512 let child_size_ref = if x_axis {
2513 &mut self.layout_elements[child_idx].dimensions.width
2514 } else {
2515 &mut self.layout_elements[child_idx].dimensions.height
2516 };
2517
2518 if child_sizing.type_ == SizingType::Grow {
2519 *child_size_ref =
2520 f32::min(max_size, child_sizing.min_max.max);
2521 }
2522 *child_size_ref = f32::max(min_size, f32::min(*child_size_ref, max_size));
2523 }
2524 }
2525 }
2526 }
2527 }
2528
2529 fn calculate_final_layout(&mut self) {
2530 self.size_containers_along_axis(true);
2532
2533 self.wrap_text();
2535
2536 for i in 0..self.aspect_ratio_element_indexes.len() {
2538 let elem_idx = self.aspect_ratio_element_indexes[i] as usize;
2539 if let Some(cfg_idx) =
2540 self.find_element_config_index(elem_idx, ElementConfigType::Aspect)
2541 {
2542 let aspect_ratio = self.aspect_ratio_configs[cfg_idx];
2543 let new_height =
2544 (1.0 / aspect_ratio) * self.layout_elements[elem_idx].dimensions.width;
2545 self.layout_elements[elem_idx].dimensions.height = new_height;
2546 let layout_idx = self.layout_elements[elem_idx].layout_config_index;
2547 self.layout_configs[layout_idx].sizing.height.min_max.min = new_height;
2548 self.layout_configs[layout_idx].sizing.height.min_max.max = new_height;
2549 }
2550 }
2551
2552 self.propagate_sizes_up_tree();
2554
2555 self.size_containers_along_axis(false);
2557
2558 for i in 0..self.aspect_ratio_element_indexes.len() {
2560 let elem_idx = self.aspect_ratio_element_indexes[i] as usize;
2561 if let Some(cfg_idx) =
2562 self.find_element_config_index(elem_idx, ElementConfigType::Aspect)
2563 {
2564 let aspect_ratio = self.aspect_ratio_configs[cfg_idx];
2565 let new_width =
2566 aspect_ratio * self.layout_elements[elem_idx].dimensions.height;
2567 self.layout_elements[elem_idx].dimensions.width = new_width;
2568 let layout_idx = self.layout_elements[elem_idx].layout_config_index;
2569 self.layout_configs[layout_idx].sizing.width.min_max.min = new_width;
2570 self.layout_configs[layout_idx].sizing.width.min_max.max = new_width;
2571 }
2572 }
2573
2574 let mut sort_max = self.layout_element_tree_roots.len().saturating_sub(1);
2576 while sort_max > 0 {
2577 for i in 0..sort_max {
2578 if self.layout_element_tree_roots[i + 1].z_index
2579 < self.layout_element_tree_roots[i].z_index
2580 {
2581 self.layout_element_tree_roots.swap(i, i + 1);
2582 }
2583 }
2584 sort_max -= 1;
2585 }
2586
2587 self.generate_render_commands();
2589 }
2590
2591 fn wrap_text(&mut self) {
2592 for text_idx in 0..self.text_element_data.len() {
2593 let elem_index = self.text_element_data[text_idx].element_index as usize;
2594 let text = self.text_element_data[text_idx].text.clone();
2595 let preferred_dims = self.text_element_data[text_idx].preferred_dimensions;
2596
2597 self.text_element_data[text_idx].wrapped_lines_start = self.wrapped_text_lines.len();
2598 self.text_element_data[text_idx].wrapped_lines_length = 0;
2599
2600 let container_width = self.layout_elements[elem_index].dimensions.width;
2601
2602 let text_config_idx = self
2604 .find_element_config_index(elem_index, ElementConfigType::Text)
2605 .unwrap_or(0);
2606 let text_config = self.text_element_configs[text_config_idx].clone();
2607
2608 let measured = self.measure_text_cached(&text, &text_config);
2609
2610 let line_height = if text_config.line_height > 0 {
2611 text_config.line_height as f32
2612 } else {
2613 preferred_dims.height
2614 };
2615
2616 if !measured.contains_newlines && preferred_dims.width <= container_width {
2617 self.wrapped_text_lines.push(WrappedTextLine {
2619 dimensions: self.layout_elements[elem_index].dimensions,
2620 start: 0,
2621 length: text.len(),
2622 });
2623 self.text_element_data[text_idx].wrapped_lines_length = 1;
2624 continue;
2625 }
2626
2627 let measure_fn = self.measure_text_fn.as_ref().unwrap();
2629 let space_width = {
2630 let space_config = text_config.clone();
2631 measure_fn(" ", &space_config).width
2632 };
2633
2634 let mut word_index = measured.measured_words_start_index;
2635 let mut line_width: f32 = 0.0;
2636 let mut line_length_chars: i32 = 0;
2637 let mut line_start_offset: i32 = 0;
2638
2639 while word_index != -1 {
2640 let measured_word = self.measured_words[word_index as usize];
2641
2642 if line_length_chars == 0 && line_width + measured_word.width > container_width {
2644 self.wrapped_text_lines.push(WrappedTextLine {
2645 dimensions: Dimensions::new(measured_word.width, line_height),
2646 start: measured_word.start_offset as usize,
2647 length: measured_word.length as usize,
2648 });
2649 self.text_element_data[text_idx].wrapped_lines_length += 1;
2650 word_index = measured_word.next;
2651 line_start_offset = measured_word.start_offset + measured_word.length;
2652 }
2653 else if measured_word.length == 0
2655 || line_width + measured_word.width > container_width
2656 {
2657 let text_bytes = text.as_bytes();
2658 let final_char_idx = (line_start_offset + line_length_chars - 1).max(0) as usize;
2659 let final_char_is_space =
2660 final_char_idx < text_bytes.len() && text_bytes[final_char_idx] == b' ';
2661 let adj_width = line_width
2662 + if final_char_is_space {
2663 -space_width
2664 } else {
2665 0.0
2666 };
2667 let adj_length = line_length_chars
2668 + if final_char_is_space { -1 } else { 0 };
2669
2670 self.wrapped_text_lines.push(WrappedTextLine {
2671 dimensions: Dimensions::new(adj_width, line_height),
2672 start: line_start_offset as usize,
2673 length: adj_length as usize,
2674 });
2675 self.text_element_data[text_idx].wrapped_lines_length += 1;
2676
2677 if line_length_chars == 0 || measured_word.length == 0 {
2678 word_index = measured_word.next;
2679 }
2680 line_width = 0.0;
2681 line_length_chars = 0;
2682 line_start_offset = measured_word.start_offset;
2683 } else {
2684 line_width += measured_word.width + text_config.letter_spacing as f32;
2685 line_length_chars += measured_word.length;
2686 word_index = measured_word.next;
2687 }
2688 }
2689
2690 if line_length_chars > 0 {
2691 self.wrapped_text_lines.push(WrappedTextLine {
2692 dimensions: Dimensions::new(
2693 line_width - text_config.letter_spacing as f32,
2694 line_height,
2695 ),
2696 start: line_start_offset as usize,
2697 length: line_length_chars as usize,
2698 });
2699 self.text_element_data[text_idx].wrapped_lines_length += 1;
2700 }
2701
2702 let num_lines = self.text_element_data[text_idx].wrapped_lines_length;
2703 self.layout_elements[elem_index].dimensions.height =
2704 line_height * num_lines as f32;
2705 }
2706 }
2707
2708 fn propagate_sizes_up_tree(&mut self) {
2709 let mut dfs_buffer: Vec<i32> = Vec::new();
2710 let mut visited: Vec<bool> = Vec::new();
2711
2712 for i in 0..self.layout_element_tree_roots.len() {
2713 let root = self.layout_element_tree_roots[i];
2714 dfs_buffer.push(root.layout_element_index);
2715 visited.push(false);
2716 }
2717
2718 while !dfs_buffer.is_empty() {
2719 let buf_idx = dfs_buffer.len() - 1;
2720 let current_elem_idx = dfs_buffer[buf_idx] as usize;
2721
2722 if !visited[buf_idx] {
2723 visited[buf_idx] = true;
2724 let is_text =
2725 self.element_has_config(current_elem_idx, ElementConfigType::Text);
2726 let children_length = self.layout_elements[current_elem_idx].children_length;
2727 if is_text || children_length == 0 {
2728 dfs_buffer.pop();
2729 visited.pop();
2730 continue;
2731 }
2732 let children_start = self.layout_elements[current_elem_idx].children_start;
2733 for j in 0..children_length as usize {
2734 let child_idx = self.layout_element_children[children_start + j];
2735 dfs_buffer.push(child_idx);
2736 visited.push(false);
2737 }
2738 continue;
2739 }
2740
2741 dfs_buffer.pop();
2742 visited.pop();
2743
2744 let layout_idx = self.layout_elements[current_elem_idx].layout_config_index;
2745 let layout_config = self.layout_configs[layout_idx];
2746 let children_start = self.layout_elements[current_elem_idx].children_start;
2747 let children_length = self.layout_elements[current_elem_idx].children_length;
2748
2749 if layout_config.layout_direction == LayoutDirection::LeftToRight {
2750 for j in 0..children_length as usize {
2751 let child_idx =
2752 self.layout_element_children[children_start + j] as usize;
2753 let child_height_with_padding = f32::max(
2754 self.layout_elements[child_idx].dimensions.height
2755 + layout_config.padding.top as f32
2756 + layout_config.padding.bottom as f32,
2757 self.layout_elements[current_elem_idx].dimensions.height,
2758 );
2759 self.layout_elements[current_elem_idx].dimensions.height = f32::min(
2760 f32::max(
2761 child_height_with_padding,
2762 layout_config.sizing.height.min_max.min,
2763 ),
2764 layout_config.sizing.height.min_max.max,
2765 );
2766 }
2767 } else {
2768 let mut content_height = layout_config.padding.top as f32
2769 + layout_config.padding.bottom as f32;
2770 for j in 0..children_length as usize {
2771 let child_idx =
2772 self.layout_element_children[children_start + j] as usize;
2773 content_height += self.layout_elements[child_idx].dimensions.height;
2774 }
2775 content_height += children_length.saturating_sub(1) as f32
2776 * layout_config.child_gap as f32;
2777 self.layout_elements[current_elem_idx].dimensions.height = f32::min(
2778 f32::max(content_height, layout_config.sizing.height.min_max.min),
2779 layout_config.sizing.height.min_max.max,
2780 );
2781 }
2782 }
2783 }
2784
2785 fn element_is_offscreen(&self, bbox: &BoundingBox) -> bool {
2786 if self.culling_disabled {
2787 return false;
2788 }
2789 bbox.x > self.layout_dimensions.width
2790 || bbox.y > self.layout_dimensions.height
2791 || bbox.x + bbox.width < 0.0
2792 || bbox.y + bbox.height < 0.0
2793 }
2794
2795 fn add_render_command(&mut self, cmd: InternalRenderCommand<CustomElementData>) {
2796 self.render_commands.push(cmd);
2797 }
2798
2799 fn generate_render_commands(&mut self) {
2800 self.render_commands.clear();
2801 let mut dfs_buffer: Vec<LayoutElementTreeNode> = Vec::new();
2802 let mut visited: Vec<bool> = Vec::new();
2803
2804 for root_index in 0..self.layout_element_tree_roots.len() {
2805 dfs_buffer.clear();
2806 visited.clear();
2807 let root = self.layout_element_tree_roots[root_index];
2808 let root_elem_idx = root.layout_element_index as usize;
2809 let root_element = &self.layout_elements[root_elem_idx];
2810 let mut root_position = Vector2::default();
2811
2812 if self.element_has_config(root_elem_idx, ElementConfigType::Floating) {
2814 if let Some(parent_item) = self.layout_element_map.get(&root.parent_id) {
2815 let parent_bbox = parent_item.bounding_box;
2816 if let Some(float_cfg_idx) = self
2817 .find_element_config_index(root_elem_idx, ElementConfigType::Floating)
2818 {
2819 let config = &self.floating_element_configs[float_cfg_idx];
2820 let root_dims = root_element.dimensions;
2821 let mut target = Vector2::default();
2822
2823 match config.attach_points.parent_x {
2825 AlignX::Left => {
2826 target.x = parent_bbox.x;
2827 }
2828 AlignX::CenterX => {
2829 target.x = parent_bbox.x + parent_bbox.width / 2.0;
2830 }
2831 AlignX::Right => {
2832 target.x = parent_bbox.x + parent_bbox.width;
2833 }
2834 }
2835 match config.attach_points.element_x {
2837 AlignX::Left => {}
2838 AlignX::CenterX => {
2839 target.x -= root_dims.width / 2.0;
2840 }
2841 AlignX::Right => {
2842 target.x -= root_dims.width;
2843 }
2844 }
2845 match config.attach_points.parent_y {
2847 AlignY::Top => {
2848 target.y = parent_bbox.y;
2849 }
2850 AlignY::CenterY => {
2851 target.y = parent_bbox.y + parent_bbox.height / 2.0;
2852 }
2853 AlignY::Bottom => {
2854 target.y = parent_bbox.y + parent_bbox.height;
2855 }
2856 }
2857 match config.attach_points.element_y {
2859 AlignY::Top => {}
2860 AlignY::CenterY => {
2861 target.y -= root_dims.height / 2.0;
2862 }
2863 AlignY::Bottom => {
2864 target.y -= root_dims.height;
2865 }
2866 }
2867 target.x += config.offset.x;
2868 target.y += config.offset.y;
2869 root_position = target;
2870 }
2871 }
2872 }
2873
2874 if root.clip_element_id != 0 {
2876 if let Some(clip_item) = self.layout_element_map.get(&root.clip_element_id) {
2877 let clip_bbox = clip_item.bounding_box;
2878 self.add_render_command(InternalRenderCommand {
2879 bounding_box: clip_bbox,
2880 command_type: RenderCommandType::ScissorStart,
2881 id: hash_number(
2882 root_element.id,
2883 root_element.children_length as u32 + 10,
2884 )
2885 .id,
2886 z_index: root.z_index,
2887 ..Default::default()
2888 });
2889 }
2890 }
2891
2892 let root_layout_idx = self.layout_elements[root_elem_idx].layout_config_index;
2893 let root_padding_left = self.layout_configs[root_layout_idx].padding.left as f32;
2894 let root_padding_top = self.layout_configs[root_layout_idx].padding.top as f32;
2895
2896 dfs_buffer.push(LayoutElementTreeNode {
2897 layout_element_index: root.layout_element_index,
2898 position: root_position,
2899 next_child_offset: Vector2::new(root_padding_left, root_padding_top),
2900 });
2901 visited.push(false);
2902
2903 while !dfs_buffer.is_empty() {
2904 let buf_idx = dfs_buffer.len() - 1;
2905 let current_node = dfs_buffer[buf_idx];
2906 let current_elem_idx = current_node.layout_element_index as usize;
2907 let layout_idx = self.layout_elements[current_elem_idx].layout_config_index;
2908 let layout_config = self.layout_configs[layout_idx];
2909 let mut scroll_offset = Vector2::default();
2910
2911 if !visited[buf_idx] {
2912 visited[buf_idx] = true;
2913
2914 let current_bbox = BoundingBox::new(
2915 current_node.position.x,
2916 current_node.position.y,
2917 self.layout_elements[current_elem_idx].dimensions.width,
2918 self.layout_elements[current_elem_idx].dimensions.height,
2919 );
2920
2921 let mut _scroll_container_data_idx: Option<usize> = None;
2923 if self.element_has_config(current_elem_idx, ElementConfigType::Clip) {
2924 if let Some(clip_cfg_idx) = self
2925 .find_element_config_index(current_elem_idx, ElementConfigType::Clip)
2926 {
2927 let clip_config = self.clip_element_configs[clip_cfg_idx];
2928 for si in 0..self.scroll_container_datas.len() {
2929 if self.scroll_container_datas[si].layout_element_index
2930 == current_elem_idx as i32
2931 {
2932 _scroll_container_data_idx = Some(si);
2933 self.scroll_container_datas[si].bounding_box = current_bbox;
2934 scroll_offset = clip_config.child_offset;
2935 break;
2936 }
2937 }
2938 }
2939 }
2940
2941 let elem_id = self.layout_elements[current_elem_idx].id;
2943 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
2944 item.bounding_box = current_bbox;
2945 }
2946
2947 let shared_config = self
2949 .find_element_config_index(current_elem_idx, ElementConfigType::Shared)
2950 .map(|idx| self.shared_element_configs[idx]);
2951 let shared = shared_config.unwrap_or_default();
2952 let mut emit_rectangle = shared.background_color.a > 0.0;
2953 let offscreen = self.element_is_offscreen(¤t_bbox);
2954 let should_render_base = !offscreen;
2955
2956 let elem_effects = self.element_effects.get(current_elem_idx).cloned().unwrap_or_default();
2958
2959 let elem_visual_rotation = self.element_visual_rotations.get(current_elem_idx).cloned().flatten();
2961 let elem_visual_rotation = elem_visual_rotation.filter(|vr| !vr.is_noop());
2963
2964 let elem_shape_rotation = self.element_shape_rotations.get(current_elem_idx).cloned().flatten()
2966 .filter(|sr| !sr.is_noop());
2967 let shape_draw_bbox = if let Some(ref _sr) = elem_shape_rotation {
2970 if let Some(orig_dims) = self.element_pre_rotation_dimensions.get(current_elem_idx).copied().flatten() {
2971 let offset_x = (current_bbox.width - orig_dims.width) / 2.0;
2972 let offset_y = (current_bbox.height - orig_dims.height) / 2.0;
2973 BoundingBox::new(
2974 current_bbox.x + offset_x,
2975 current_bbox.y + offset_y,
2976 orig_dims.width,
2977 orig_dims.height,
2978 )
2979 } else {
2980 current_bbox
2981 }
2982 } else {
2983 current_bbox
2984 };
2985
2986 let elem_shaders = self.element_shaders.get(current_elem_idx).cloned().unwrap_or_default();
2990
2991 if !elem_shaders.is_empty() {
2992 for (i, shader) in elem_shaders.iter().rev().enumerate() {
2994 let vr = if i == 0 { elem_visual_rotation } else { None };
2996 self.add_render_command(InternalRenderCommand {
2997 bounding_box: current_bbox,
2998 command_type: RenderCommandType::GroupBegin,
2999 effects: vec![shader.clone()],
3000 id: elem_id,
3001 z_index: root.z_index,
3002 visual_rotation: vr,
3003 ..Default::default()
3004 });
3005 }
3006 } else if let Some(vr) = elem_visual_rotation {
3007 self.add_render_command(InternalRenderCommand {
3009 bounding_box: current_bbox,
3010 command_type: RenderCommandType::GroupBegin,
3011 effects: vec![],
3012 id: elem_id,
3013 z_index: root.z_index,
3014 visual_rotation: Some(vr),
3015 ..Default::default()
3016 });
3017 }
3018
3019 let configs_start = self.layout_elements[current_elem_idx].element_configs.start;
3021 let configs_length =
3022 self.layout_elements[current_elem_idx].element_configs.length;
3023
3024 for cfg_i in 0..configs_length {
3025 let config = self.element_configs[configs_start + cfg_i as usize];
3026 let should_render = should_render_base;
3027
3028 match config.config_type {
3029 ElementConfigType::Shared
3030 | ElementConfigType::Aspect
3031 | ElementConfigType::Floating
3032 | ElementConfigType::Border => {}
3033 ElementConfigType::Clip => {
3034 if should_render {
3035 let clip = &self.clip_element_configs[config.config_index];
3036 self.add_render_command(InternalRenderCommand {
3037 bounding_box: current_bbox,
3038 command_type: RenderCommandType::ScissorStart,
3039 render_data: InternalRenderData::Clip {
3040 horizontal: clip.horizontal,
3041 vertical: clip.vertical,
3042 },
3043 user_data: 0,
3044 id: elem_id,
3045 z_index: root.z_index,
3046 visual_rotation: None,
3047 shape_rotation: None,
3048 effects: Vec::new(),
3049 });
3050 }
3051 }
3052 ElementConfigType::Image => {
3053 if should_render {
3054 let image_data =
3055 self.image_element_configs[config.config_index].clone();
3056 self.add_render_command(InternalRenderCommand {
3057 bounding_box: shape_draw_bbox,
3058 command_type: RenderCommandType::Image,
3059 render_data: InternalRenderData::Image {
3060 background_color: shared.background_color,
3061 corner_radius: shared.corner_radius,
3062 image_data,
3063 },
3064 user_data: shared.user_data,
3065 id: elem_id,
3066 z_index: root.z_index,
3067 visual_rotation: None,
3068 shape_rotation: elem_shape_rotation,
3069 effects: elem_effects.clone(),
3070 });
3071 }
3072 emit_rectangle = false;
3073 }
3074 ElementConfigType::Text => {
3075 if !should_render {
3076 continue;
3077 }
3078 let text_config =
3079 self.text_element_configs[config.config_index].clone();
3080 let text_data_idx =
3081 self.layout_elements[current_elem_idx].text_data_index;
3082 if text_data_idx < 0 {
3083 continue;
3084 }
3085 let text_data = &self.text_element_data[text_data_idx as usize];
3086 let natural_line_height = text_data.preferred_dimensions.height;
3087 let final_line_height = if text_config.line_height > 0 {
3088 text_config.line_height as f32
3089 } else {
3090 natural_line_height
3091 };
3092 let line_height_offset =
3093 (final_line_height - natural_line_height) / 2.0;
3094 let mut y_position = line_height_offset;
3095
3096 let lines_start = text_data.wrapped_lines_start;
3097 let lines_length = text_data.wrapped_lines_length;
3098 let parent_text = text_data.text.clone();
3099
3100 let lines_data: Vec<_> = (0..lines_length)
3102 .map(|li| {
3103 let line = &self.wrapped_text_lines[lines_start + li as usize];
3104 (line.start, line.length, line.dimensions)
3105 })
3106 .collect();
3107
3108 for (line_index, &(start, length, line_dims)) in lines_data.iter().enumerate() {
3109 if length == 0 {
3110 y_position += final_line_height;
3111 continue;
3112 }
3113
3114 let line_text = parent_text[start..start + length].to_string();
3115
3116 let align_width = if buf_idx > 0 {
3117 let parent_node = dfs_buffer[buf_idx - 1];
3118 let parent_elem_idx =
3119 parent_node.layout_element_index as usize;
3120 let parent_layout_idx = self.layout_elements
3121 [parent_elem_idx]
3122 .layout_config_index;
3123 let pp = self.layout_configs[parent_layout_idx].padding;
3124 self.layout_elements[parent_elem_idx].dimensions.width
3125 - pp.left as f32
3126 - pp.right as f32
3127 } else {
3128 current_bbox.width
3129 };
3130
3131 let mut offset = align_width - line_dims.width;
3132 if text_config.alignment == AlignX::Left {
3133 offset = 0.0;
3134 }
3135 if text_config.alignment == AlignX::CenterX {
3136 offset /= 2.0;
3137 }
3138
3139 self.add_render_command(InternalRenderCommand {
3140 bounding_box: BoundingBox::new(
3141 current_bbox.x + offset,
3142 current_bbox.y + y_position,
3143 line_dims.width,
3144 line_dims.height,
3145 ),
3146 command_type: RenderCommandType::Text,
3147 render_data: InternalRenderData::Text {
3148 text: line_text,
3149 text_color: text_config.color,
3150 font_size: text_config.font_size,
3151 letter_spacing: text_config.letter_spacing,
3152 line_height: text_config.line_height,
3153 font_asset: text_config.font_asset,
3154 },
3155 user_data: text_config.user_data,
3156 id: hash_number(line_index as u32, elem_id).id,
3157 z_index: root.z_index,
3158 visual_rotation: None,
3159 shape_rotation: None,
3160 effects: text_config.effects.clone(),
3161 });
3162 y_position += final_line_height;
3163 }
3164 }
3165 ElementConfigType::Custom => {
3166 if should_render {
3167 let custom_data =
3168 self.custom_element_configs[config.config_index].clone();
3169 self.add_render_command(InternalRenderCommand {
3170 bounding_box: shape_draw_bbox,
3171 command_type: RenderCommandType::Custom,
3172 render_data: InternalRenderData::Custom {
3173 background_color: shared.background_color,
3174 corner_radius: shared.corner_radius,
3175 custom_data,
3176 },
3177 user_data: shared.user_data,
3178 id: elem_id,
3179 z_index: root.z_index,
3180 visual_rotation: None,
3181 shape_rotation: elem_shape_rotation,
3182 effects: elem_effects.clone(),
3183 });
3184 }
3185 emit_rectangle = false;
3186 }
3187 ElementConfigType::TextInput => {
3188 if should_render {
3189 let ti_config = self.text_input_configs[config.config_index].clone();
3190 let is_focused = self.focused_element_id == elem_id;
3191
3192 if shared.background_color.a > 0.0 || shared.corner_radius.bottom_left > 0.0 {
3194 self.add_render_command(InternalRenderCommand {
3195 bounding_box: shape_draw_bbox,
3196 command_type: RenderCommandType::Rectangle,
3197 render_data: InternalRenderData::Rectangle {
3198 background_color: shared.background_color,
3199 corner_radius: shared.corner_radius,
3200 },
3201 user_data: shared.user_data,
3202 id: elem_id,
3203 z_index: root.z_index,
3204 visual_rotation: None,
3205 shape_rotation: elem_shape_rotation,
3206 effects: elem_effects.clone(),
3207 });
3208 }
3209
3210 let state = self.text_edit_states
3212 .entry(elem_id)
3213 .or_insert_with(crate::text_input::TextEditState::default)
3214 .clone();
3215
3216 let disp_text = crate::text_input::display_text(
3217 &state.text,
3218 &ti_config.placeholder,
3219 ti_config.is_password && !ti_config.is_multiline,
3220 );
3221
3222 let is_placeholder = state.text.is_empty();
3223 let text_color = if is_placeholder {
3224 ti_config.placeholder_color
3225 } else {
3226 ti_config.text_color
3227 };
3228
3229 let natural_font_height = self.font_height(ti_config.font_asset, ti_config.font_size);
3231 let line_step = if ti_config.line_height > 0 {
3232 ti_config.line_height as f32
3233 } else {
3234 natural_font_height
3235 };
3236 let line_y_offset = (line_step - natural_font_height) / 2.0;
3238
3239 self.add_render_command(InternalRenderCommand {
3241 bounding_box: current_bbox,
3242 command_type: RenderCommandType::ScissorStart,
3243 render_data: InternalRenderData::Clip {
3244 horizontal: true,
3245 vertical: true,
3246 },
3247 user_data: 0,
3248 id: hash_number(1000, elem_id).id,
3249 z_index: root.z_index,
3250 visual_rotation: None,
3251 shape_rotation: None,
3252 effects: Vec::new(),
3253 });
3254
3255 if ti_config.is_multiline {
3256 let scroll_offset_x = state.scroll_offset;
3258 let scroll_offset_y = state.scroll_offset_y;
3259
3260 let visual_lines = if let Some(ref measure_fn) = self.measure_text_fn {
3261 crate::text_input::wrap_lines(
3262 &disp_text,
3263 current_bbox.width,
3264 ti_config.font_asset,
3265 ti_config.font_size,
3266 measure_fn.as_ref(),
3267 )
3268 } else {
3269 vec![crate::text_input::VisualLine {
3270 text: disp_text.clone(),
3271 global_char_start: 0,
3272 char_count: disp_text.chars().count(),
3273 }]
3274 };
3275
3276 let (cursor_line, cursor_col) = if is_placeholder {
3277 (0, 0)
3278 } else {
3279 #[cfg(feature = "text-styling")]
3280 let raw_cursor = state.cursor_pos_raw();
3281 #[cfg(not(feature = "text-styling"))]
3282 let raw_cursor = state.cursor_pos;
3283 crate::text_input::cursor_to_visual_pos(&visual_lines, raw_cursor)
3284 };
3285
3286 let line_positions: Vec<Vec<f32>> = if let Some(ref measure_fn) = self.measure_text_fn {
3288 visual_lines.iter().map(|vl| {
3289 crate::text_input::compute_char_x_positions(
3290 &vl.text,
3291 ti_config.font_asset,
3292 ti_config.font_size,
3293 measure_fn.as_ref(),
3294 )
3295 }).collect()
3296 } else {
3297 visual_lines.iter().map(|_| vec![0.0]).collect()
3298 };
3299
3300 if is_focused {
3302 #[cfg(feature = "text-styling")]
3303 let sel_range = state.selection_range_raw();
3304 #[cfg(not(feature = "text-styling"))]
3305 let sel_range = state.selection_range();
3306 if let Some((sel_start, sel_end)) = sel_range {
3307 let (sel_start_line, sel_start_col) = crate::text_input::cursor_to_visual_pos(&visual_lines, sel_start);
3308 let (sel_end_line, sel_end_col) = crate::text_input::cursor_to_visual_pos(&visual_lines, sel_end);
3309 for (line_idx, vl) in visual_lines.iter().enumerate() {
3310 if line_idx < sel_start_line || line_idx > sel_end_line {
3311 continue;
3312 }
3313 let positions = &line_positions[line_idx];
3314 let col_start = if line_idx == sel_start_line { sel_start_col } else { 0 };
3315 let col_end = if line_idx == sel_end_line { sel_end_col } else { vl.char_count };
3316 let x_start = positions.get(col_start).copied().unwrap_or(0.0);
3317 let x_end = positions.get(col_end).copied().unwrap_or(
3318 positions.last().copied().unwrap_or(0.0)
3319 );
3320 let sel_width = x_end - x_start;
3321 if sel_width > 0.0 {
3322 let sel_y = current_bbox.y + line_idx as f32 * line_step - scroll_offset_y;
3323 self.add_render_command(InternalRenderCommand {
3324 bounding_box: BoundingBox::new(
3325 current_bbox.x - scroll_offset_x + x_start,
3326 sel_y,
3327 sel_width,
3328 line_step,
3329 ),
3330 command_type: RenderCommandType::Rectangle,
3331 render_data: InternalRenderData::Rectangle {
3332 background_color: ti_config.selection_color,
3333 corner_radius: CornerRadius::default(),
3334 },
3335 user_data: 0,
3336 id: hash_number(1001 + line_idx as u32, elem_id).id,
3337 z_index: root.z_index,
3338 visual_rotation: None,
3339 shape_rotation: None,
3340 effects: Vec::new(),
3341 });
3342 }
3343 }
3344 }
3345 }
3346
3347 for (line_idx, vl) in visual_lines.iter().enumerate() {
3349 if !vl.text.is_empty() {
3350 let positions = &line_positions[line_idx];
3351 let text_width = positions.last().copied().unwrap_or(0.0);
3352 let line_y = current_bbox.y + line_idx as f32 * line_step + line_y_offset - scroll_offset_y;
3353 self.add_render_command(InternalRenderCommand {
3354 bounding_box: BoundingBox::new(
3355 current_bbox.x - scroll_offset_x,
3356 line_y,
3357 text_width,
3358 natural_font_height,
3359 ),
3360 command_type: RenderCommandType::Text,
3361 render_data: InternalRenderData::Text {
3362 text: vl.text.clone(),
3363 text_color,
3364 font_size: ti_config.font_size,
3365 letter_spacing: 0,
3366 line_height: 0,
3367 font_asset: ti_config.font_asset,
3368 },
3369 user_data: 0,
3370 id: hash_number(2000 + line_idx as u32, elem_id).id,
3371 z_index: root.z_index,
3372 visual_rotation: None,
3373 shape_rotation: None,
3374 effects: Vec::new(),
3375 });
3376 }
3377 }
3378
3379 if is_focused && state.cursor_visible() {
3381 let cursor_positions = &line_positions[cursor_line.min(line_positions.len() - 1)];
3382 let cursor_x_pos = cursor_positions.get(cursor_col).copied().unwrap_or(0.0);
3383 let cursor_y = current_bbox.y + cursor_line as f32 * line_step - scroll_offset_y;
3384 self.add_render_command(InternalRenderCommand {
3385 bounding_box: BoundingBox::new(
3386 current_bbox.x - scroll_offset_x + cursor_x_pos,
3387 cursor_y,
3388 2.0,
3389 line_step,
3390 ),
3391 command_type: RenderCommandType::Rectangle,
3392 render_data: InternalRenderData::Rectangle {
3393 background_color: ti_config.cursor_color,
3394 corner_radius: CornerRadius::default(),
3395 },
3396 user_data: 0,
3397 id: hash_number(1003, elem_id).id,
3398 z_index: root.z_index,
3399 visual_rotation: None,
3400 shape_rotation: None,
3401 effects: Vec::new(),
3402 });
3403 }
3404 } else {
3405 let char_x_positions = if let Some(ref measure_fn) = self.measure_text_fn {
3407 crate::text_input::compute_char_x_positions(
3408 &disp_text,
3409 ti_config.font_asset,
3410 ti_config.font_size,
3411 measure_fn.as_ref(),
3412 )
3413 } else {
3414 vec![0.0]
3415 };
3416
3417 let scroll_offset = state.scroll_offset;
3418 let text_x = current_bbox.x - scroll_offset;
3419 let font_height = natural_font_height;
3420
3421 #[cfg(feature = "text-styling")]
3423 let render_cursor_pos = if is_placeholder { 0 } else { state.cursor_pos_raw() };
3424 #[cfg(not(feature = "text-styling"))]
3425 let render_cursor_pos = if is_placeholder { 0 } else { state.cursor_pos };
3426
3427 #[cfg(feature = "text-styling")]
3428 let render_selection = if !is_placeholder { state.selection_range_raw() } else { None };
3429 #[cfg(not(feature = "text-styling"))]
3430 let render_selection = if !is_placeholder { state.selection_range() } else { None };
3431
3432 if is_focused {
3434 if let Some((sel_start, sel_end)) = render_selection {
3435 let sel_start_x = char_x_positions.get(sel_start).copied().unwrap_or(0.0);
3436 let sel_end_x = char_x_positions.get(sel_end).copied().unwrap_or(0.0);
3437 let sel_width = sel_end_x - sel_start_x;
3438 if sel_width > 0.0 {
3439 let sel_y = current_bbox.y + (current_bbox.height - font_height) / 2.0;
3440 self.add_render_command(InternalRenderCommand {
3441 bounding_box: BoundingBox::new(
3442 text_x + sel_start_x,
3443 sel_y,
3444 sel_width,
3445 font_height,
3446 ),
3447 command_type: RenderCommandType::Rectangle,
3448 render_data: InternalRenderData::Rectangle {
3449 background_color: ti_config.selection_color,
3450 corner_radius: CornerRadius::default(),
3451 },
3452 user_data: 0,
3453 id: hash_number(1001, elem_id).id,
3454 z_index: root.z_index,
3455 visual_rotation: None,
3456 shape_rotation: None,
3457 effects: Vec::new(),
3458 });
3459 }
3460 }
3461 }
3462
3463 if !disp_text.is_empty() {
3465 let text_width = char_x_positions.last().copied().unwrap_or(0.0);
3466 let text_y = current_bbox.y + (current_bbox.height - font_height) / 2.0;
3467 self.add_render_command(InternalRenderCommand {
3468 bounding_box: BoundingBox::new(
3469 text_x,
3470 text_y,
3471 text_width,
3472 font_height,
3473 ),
3474 command_type: RenderCommandType::Text,
3475 render_data: InternalRenderData::Text {
3476 text: disp_text,
3477 text_color,
3478 font_size: ti_config.font_size,
3479 letter_spacing: 0,
3480 line_height: 0,
3481 font_asset: ti_config.font_asset,
3482 },
3483 user_data: 0,
3484 id: hash_number(1002, elem_id).id,
3485 z_index: root.z_index,
3486 visual_rotation: None,
3487 shape_rotation: None,
3488 effects: Vec::new(),
3489 });
3490 }
3491
3492 if is_focused && state.cursor_visible() {
3494 let cursor_x_pos = char_x_positions
3495 .get(render_cursor_pos)
3496 .copied()
3497 .unwrap_or(0.0);
3498 let cursor_y = current_bbox.y + (current_bbox.height - font_height) / 2.0;
3499 self.add_render_command(InternalRenderCommand {
3500 bounding_box: BoundingBox::new(
3501 text_x + cursor_x_pos,
3502 cursor_y,
3503 2.0,
3504 font_height,
3505 ),
3506 command_type: RenderCommandType::Rectangle,
3507 render_data: InternalRenderData::Rectangle {
3508 background_color: ti_config.cursor_color,
3509 corner_radius: CornerRadius::default(),
3510 },
3511 user_data: 0,
3512 id: hash_number(1003, elem_id).id,
3513 z_index: root.z_index,
3514 visual_rotation: None,
3515 shape_rotation: None,
3516 effects: Vec::new(),
3517 });
3518 }
3519 }
3520
3521 self.add_render_command(InternalRenderCommand {
3523 bounding_box: current_bbox,
3524 command_type: RenderCommandType::ScissorEnd,
3525 render_data: InternalRenderData::None,
3526 user_data: 0,
3527 id: hash_number(1004, elem_id).id,
3528 z_index: root.z_index,
3529 visual_rotation: None,
3530 shape_rotation: None,
3531 effects: Vec::new(),
3532 });
3533 }
3534 emit_rectangle = false;
3536 }
3537 }
3538 }
3539
3540 if emit_rectangle {
3541 self.add_render_command(InternalRenderCommand {
3542 bounding_box: shape_draw_bbox,
3543 command_type: RenderCommandType::Rectangle,
3544 render_data: InternalRenderData::Rectangle {
3545 background_color: shared.background_color,
3546 corner_radius: shared.corner_radius,
3547 },
3548 user_data: shared.user_data,
3549 id: elem_id,
3550 z_index: root.z_index,
3551 visual_rotation: None,
3552 shape_rotation: elem_shape_rotation,
3553 effects: elem_effects.clone(),
3554 });
3555 }
3556
3557 let is_text =
3559 self.element_has_config(current_elem_idx, ElementConfigType::Text);
3560 if !is_text {
3561 let children_start =
3562 self.layout_elements[current_elem_idx].children_start;
3563 let children_length =
3564 self.layout_elements[current_elem_idx].children_length as usize;
3565
3566 if layout_config.layout_direction == LayoutDirection::LeftToRight {
3567 let mut content_width: f32 = 0.0;
3568 for ci in 0..children_length {
3569 let child_idx =
3570 self.layout_element_children[children_start + ci] as usize;
3571 content_width +=
3572 self.layout_elements[child_idx].dimensions.width;
3573 }
3574 content_width += children_length.saturating_sub(1) as f32
3575 * layout_config.child_gap as f32;
3576 let mut extra_space = self.layout_elements[current_elem_idx]
3577 .dimensions
3578 .width
3579 - (layout_config.padding.left + layout_config.padding.right) as f32
3580 - content_width;
3581 if _scroll_container_data_idx.is_some() {
3582 extra_space = extra_space.max(0.0);
3583 }
3584 match layout_config.child_alignment.x {
3585 AlignX::Left => extra_space = 0.0,
3586 AlignX::CenterX => extra_space /= 2.0,
3587 _ => {} }
3589 dfs_buffer[buf_idx].next_child_offset.x += extra_space;
3590 } else {
3591 let mut content_height: f32 = 0.0;
3592 for ci in 0..children_length {
3593 let child_idx =
3594 self.layout_element_children[children_start + ci] as usize;
3595 content_height +=
3596 self.layout_elements[child_idx].dimensions.height;
3597 }
3598 content_height += children_length.saturating_sub(1) as f32
3599 * layout_config.child_gap as f32;
3600 let mut extra_space = self.layout_elements[current_elem_idx]
3601 .dimensions
3602 .height
3603 - (layout_config.padding.top + layout_config.padding.bottom) as f32
3604 - content_height;
3605 if _scroll_container_data_idx.is_some() {
3606 extra_space = extra_space.max(0.0);
3607 }
3608 match layout_config.child_alignment.y {
3609 AlignY::Top => extra_space = 0.0,
3610 AlignY::CenterY => extra_space /= 2.0,
3611 _ => {}
3612 }
3613 dfs_buffer[buf_idx].next_child_offset.y += extra_space;
3614 }
3615
3616 if let Some(si) = _scroll_container_data_idx {
3618 let child_gap_total = children_length.saturating_sub(1) as f32
3619 * layout_config.child_gap as f32;
3620 let lr_padding = (layout_config.padding.left + layout_config.padding.right) as f32;
3621 let tb_padding = (layout_config.padding.top + layout_config.padding.bottom) as f32;
3622
3623 let (content_w, content_h) = if layout_config.layout_direction == LayoutDirection::LeftToRight {
3624 let w: f32 = (0..children_length)
3626 .map(|ci| {
3627 let idx = self.layout_element_children[children_start + ci] as usize;
3628 self.layout_elements[idx].dimensions.width
3629 })
3630 .sum::<f32>()
3631 + lr_padding + child_gap_total;
3632 let h: f32 = (0..children_length)
3633 .map(|ci| {
3634 let idx = self.layout_element_children[children_start + ci] as usize;
3635 self.layout_elements[idx].dimensions.height
3636 })
3637 .fold(0.0_f32, |a, b| a.max(b))
3638 + tb_padding;
3639 (w, h)
3640 } else {
3641 let w: f32 = (0..children_length)
3643 .map(|ci| {
3644 let idx = self.layout_element_children[children_start + ci] as usize;
3645 self.layout_elements[idx].dimensions.width
3646 })
3647 .fold(0.0_f32, |a, b| a.max(b))
3648 + lr_padding;
3649 let h: f32 = (0..children_length)
3650 .map(|ci| {
3651 let idx = self.layout_element_children[children_start + ci] as usize;
3652 self.layout_elements[idx].dimensions.height
3653 })
3654 .sum::<f32>()
3655 + tb_padding + child_gap_total;
3656 (w, h)
3657 };
3658 self.scroll_container_datas[si].content_size =
3659 Dimensions::new(content_w, content_h);
3660 }
3661 }
3662 } else {
3663 let mut close_clip = false;
3666
3667 if self.element_has_config(current_elem_idx, ElementConfigType::Clip) {
3668 close_clip = true;
3669 if let Some(clip_cfg_idx) = self
3670 .find_element_config_index(current_elem_idx, ElementConfigType::Clip)
3671 {
3672 let clip_config = self.clip_element_configs[clip_cfg_idx];
3673 for si in 0..self.scroll_container_datas.len() {
3674 if self.scroll_container_datas[si].layout_element_index
3675 == current_elem_idx as i32
3676 {
3677 scroll_offset = clip_config.child_offset;
3678 break;
3679 }
3680 }
3681 }
3682 }
3683
3684 if self.element_has_config(current_elem_idx, ElementConfigType::Border) {
3686 let border_elem_id = self.layout_elements[current_elem_idx].id;
3687 if let Some(border_bbox) = self.layout_element_map.get(&border_elem_id).map(|item| item.bounding_box) {
3688 let bbox = border_bbox;
3689 if !self.element_is_offscreen(&bbox) {
3690 let shared = self
3691 .find_element_config_index(
3692 current_elem_idx,
3693 ElementConfigType::Shared,
3694 )
3695 .map(|idx| self.shared_element_configs[idx])
3696 .unwrap_or_default();
3697 let border_cfg_idx = self
3698 .find_element_config_index(
3699 current_elem_idx,
3700 ElementConfigType::Border,
3701 )
3702 .unwrap();
3703 let border_config = self.border_element_configs[border_cfg_idx];
3704
3705 let children_count =
3706 self.layout_elements[current_elem_idx].children_length;
3707 self.add_render_command(InternalRenderCommand {
3708 bounding_box: bbox,
3709 command_type: RenderCommandType::Border,
3710 render_data: InternalRenderData::Border {
3711 color: border_config.color,
3712 corner_radius: shared.corner_radius,
3713 width: border_config.width,
3714 position: border_config.position,
3715 },
3716 user_data: shared.user_data,
3717 id: hash_number(
3718 self.layout_elements[current_elem_idx].id,
3719 children_count as u32,
3720 )
3721 .id,
3722 z_index: root.z_index,
3723 visual_rotation: None,
3724 shape_rotation: None,
3725 effects: Vec::new(),
3726 });
3727
3728 if border_config.width.between_children > 0
3730 && border_config.color.a > 0.0
3731 {
3732 let half_gap = layout_config.child_gap as f32 / 2.0;
3733 let children_start =
3734 self.layout_elements[current_elem_idx].children_start;
3735 let children_length = self.layout_elements[current_elem_idx]
3736 .children_length
3737 as usize;
3738
3739 if layout_config.layout_direction
3740 == LayoutDirection::LeftToRight
3741 {
3742 let mut border_offset_x =
3743 layout_config.padding.left as f32 - half_gap;
3744 for ci in 0..children_length {
3745 let child_idx = self.layout_element_children
3746 [children_start + ci]
3747 as usize;
3748 if ci > 0 {
3749 self.add_render_command(InternalRenderCommand {
3750 bounding_box: BoundingBox::new(
3751 bbox.x + border_offset_x + scroll_offset.x,
3752 bbox.y + scroll_offset.y,
3753 border_config.width.between_children as f32,
3754 self.layout_elements[current_elem_idx]
3755 .dimensions
3756 .height,
3757 ),
3758 command_type: RenderCommandType::Rectangle,
3759 render_data: InternalRenderData::Rectangle {
3760 background_color: border_config.color,
3761 corner_radius: CornerRadius::default(),
3762 },
3763 user_data: shared.user_data,
3764 id: hash_number(
3765 self.layout_elements[current_elem_idx].id,
3766 children_count as u32 + 1 + ci as u32,
3767 )
3768 .id,
3769 z_index: root.z_index,
3770 visual_rotation: None,
3771 shape_rotation: None,
3772 effects: Vec::new(),
3773 });
3774 }
3775 border_offset_x +=
3776 self.layout_elements[child_idx].dimensions.width
3777 + layout_config.child_gap as f32;
3778 }
3779 } else {
3780 let mut border_offset_y =
3781 layout_config.padding.top as f32 - half_gap;
3782 for ci in 0..children_length {
3783 let child_idx = self.layout_element_children
3784 [children_start + ci]
3785 as usize;
3786 if ci > 0 {
3787 self.add_render_command(InternalRenderCommand {
3788 bounding_box: BoundingBox::new(
3789 bbox.x + scroll_offset.x,
3790 bbox.y + border_offset_y + scroll_offset.y,
3791 self.layout_elements[current_elem_idx]
3792 .dimensions
3793 .width,
3794 border_config.width.between_children as f32,
3795 ),
3796 command_type: RenderCommandType::Rectangle,
3797 render_data: InternalRenderData::Rectangle {
3798 background_color: border_config.color,
3799 corner_radius: CornerRadius::default(),
3800 },
3801 user_data: shared.user_data,
3802 id: hash_number(
3803 self.layout_elements[current_elem_idx].id,
3804 children_count as u32 + 1 + ci as u32,
3805 )
3806 .id,
3807 z_index: root.z_index,
3808 visual_rotation: None,
3809 shape_rotation: None,
3810 effects: Vec::new(),
3811 });
3812 }
3813 border_offset_y +=
3814 self.layout_elements[child_idx].dimensions.height
3815 + layout_config.child_gap as f32;
3816 }
3817 }
3818 }
3819 }
3820 }
3821 }
3822
3823 if close_clip {
3824 let root_elem = &self.layout_elements[root_elem_idx];
3825 self.add_render_command(InternalRenderCommand {
3826 command_type: RenderCommandType::ScissorEnd,
3827 id: hash_number(
3828 self.layout_elements[current_elem_idx].id,
3829 root_elem.children_length as u32 + 11,
3830 )
3831 .id,
3832 ..Default::default()
3833 });
3834 }
3835
3836 let elem_shaders = self.element_shaders.get(current_elem_idx).cloned().unwrap_or_default();
3838 let elem_visual_rotation = self.element_visual_rotations.get(current_elem_idx).cloned().flatten()
3839 .filter(|vr| !vr.is_noop());
3840
3841 for _shader in elem_shaders.iter() {
3843 self.add_render_command(InternalRenderCommand {
3844 command_type: RenderCommandType::GroupEnd,
3845 id: self.layout_elements[current_elem_idx].id,
3846 z_index: root.z_index,
3847 ..Default::default()
3848 });
3849 }
3850 if elem_shaders.is_empty() && elem_visual_rotation.is_some() {
3852 self.add_render_command(InternalRenderCommand {
3853 command_type: RenderCommandType::GroupEnd,
3854 id: self.layout_elements[current_elem_idx].id,
3855 z_index: root.z_index,
3856 ..Default::default()
3857 });
3858 }
3859
3860 dfs_buffer.pop();
3861 visited.pop();
3862 continue;
3863 }
3864
3865 let is_text =
3867 self.element_has_config(current_elem_idx, ElementConfigType::Text);
3868 if !is_text {
3869 let children_start = self.layout_elements[current_elem_idx].children_start;
3870 let children_length =
3871 self.layout_elements[current_elem_idx].children_length as usize;
3872
3873 let new_len = dfs_buffer.len() + children_length;
3875 dfs_buffer.resize(new_len, LayoutElementTreeNode::default());
3876 visited.resize(new_len, false);
3877
3878 for ci in 0..children_length {
3879 let child_idx =
3880 self.layout_element_children[children_start + ci] as usize;
3881 let child_layout_idx =
3882 self.layout_elements[child_idx].layout_config_index;
3883
3884 let mut child_offset = dfs_buffer[buf_idx].next_child_offset;
3886 if layout_config.layout_direction == LayoutDirection::LeftToRight {
3887 child_offset.y = layout_config.padding.top as f32;
3888 let whitespace = self.layout_elements[current_elem_idx].dimensions.height
3889 - (layout_config.padding.top + layout_config.padding.bottom) as f32
3890 - self.layout_elements[child_idx].dimensions.height;
3891 match layout_config.child_alignment.y {
3892 AlignY::Top => {}
3893 AlignY::CenterY => {
3894 child_offset.y += whitespace / 2.0;
3895 }
3896 AlignY::Bottom => {
3897 child_offset.y += whitespace;
3898 }
3899 }
3900 } else {
3901 child_offset.x = layout_config.padding.left as f32;
3902 let whitespace = self.layout_elements[current_elem_idx].dimensions.width
3903 - (layout_config.padding.left + layout_config.padding.right) as f32
3904 - self.layout_elements[child_idx].dimensions.width;
3905 match layout_config.child_alignment.x {
3906 AlignX::Left => {}
3907 AlignX::CenterX => {
3908 child_offset.x += whitespace / 2.0;
3909 }
3910 AlignX::Right => {
3911 child_offset.x += whitespace;
3912 }
3913 }
3914 }
3915
3916 let child_position = Vector2::new(
3917 dfs_buffer[buf_idx].position.x + child_offset.x + scroll_offset.x,
3918 dfs_buffer[buf_idx].position.y + child_offset.y + scroll_offset.y,
3919 );
3920
3921 let new_node_index = new_len - 1 - ci;
3922 let child_padding_left =
3923 self.layout_configs[child_layout_idx].padding.left as f32;
3924 let child_padding_top =
3925 self.layout_configs[child_layout_idx].padding.top as f32;
3926 dfs_buffer[new_node_index] = LayoutElementTreeNode {
3927 layout_element_index: child_idx as i32,
3928 position: child_position,
3929 next_child_offset: Vector2::new(child_padding_left, child_padding_top),
3930 };
3931 visited[new_node_index] = false;
3932
3933 if layout_config.layout_direction == LayoutDirection::LeftToRight {
3935 dfs_buffer[buf_idx].next_child_offset.x +=
3936 self.layout_elements[child_idx].dimensions.width
3937 + layout_config.child_gap as f32;
3938 } else {
3939 dfs_buffer[buf_idx].next_child_offset.y +=
3940 self.layout_elements[child_idx].dimensions.height
3941 + layout_config.child_gap as f32;
3942 }
3943 }
3944 }
3945 }
3946
3947 if root.clip_element_id != 0 {
3949 let root_elem = &self.layout_elements[root_elem_idx];
3950 self.add_render_command(InternalRenderCommand {
3951 command_type: RenderCommandType::ScissorEnd,
3952 id: hash_number(root_elem.id, root_elem.children_length as u32 + 11).id,
3953 ..Default::default()
3954 });
3955 }
3956 }
3957
3958 if self.focused_element_id != 0 && self.focus_from_keyboard {
3960 let a11y = self.accessibility_configs.get(&self.focused_element_id);
3962 let show_ring = a11y.map_or(true, |c| c.show_ring);
3963 if show_ring {
3964 if let Some(item) = self.layout_element_map.get(&self.focused_element_id) {
3965 let bbox = item.bounding_box;
3966 if !self.element_is_offscreen(&bbox) {
3967 let elem_idx = item.layout_element_index as usize;
3968 let corner_radius = self
3969 .find_element_config_index(elem_idx, ElementConfigType::Shared)
3970 .map(|idx| self.shared_element_configs[idx].corner_radius)
3971 .unwrap_or_default();
3972 let ring_width = a11y.and_then(|c| c.ring_width).unwrap_or(2);
3973 let ring_color = a11y.and_then(|c| c.ring_color).unwrap_or(Color::rgba(255.0, 60.0, 40.0, 255.0));
3974 let expanded_bbox = BoundingBox::new(
3976 bbox.x - ring_width as f32,
3977 bbox.y - ring_width as f32,
3978 bbox.width + ring_width as f32 * 2.0,
3979 bbox.height + ring_width as f32 * 2.0,
3980 );
3981 self.add_render_command(InternalRenderCommand {
3982 bounding_box: expanded_bbox,
3983 command_type: RenderCommandType::Border,
3984 render_data: InternalRenderData::Border {
3985 color: ring_color,
3986 corner_radius: CornerRadius {
3987 top_left: corner_radius.top_left + ring_width as f32,
3988 top_right: corner_radius.top_right + ring_width as f32,
3989 bottom_left: corner_radius.bottom_left + ring_width as f32,
3990 bottom_right: corner_radius.bottom_right + ring_width as f32,
3991 },
3992 width: BorderWidth {
3993 left: ring_width,
3994 right: ring_width,
3995 top: ring_width,
3996 bottom: ring_width,
3997 between_children: 0,
3998 },
3999 position: BorderPosition::Middle,
4000 },
4001 id: hash_number(self.focused_element_id, 0xF0C5).id,
4002 z_index: 32764, ..Default::default()
4004 });
4005 }
4006 }
4007 }
4008 }
4009 }
4010
4011 pub fn set_layout_dimensions(&mut self, dimensions: Dimensions) {
4012 self.layout_dimensions = dimensions;
4013 }
4014
4015 pub fn set_pointer_state(&mut self, position: Vector2, is_down: bool) {
4016 if self.boolean_warnings.max_elements_exceeded {
4017 return;
4018 }
4019 self.pointer_info.position = position;
4020 self.pointer_over_ids.clear();
4021
4022 for root_index in (0..self.layout_element_tree_roots.len()).rev() {
4024 let root = self.layout_element_tree_roots[root_index];
4025 let mut dfs: Vec<i32> = vec![root.layout_element_index];
4026 let mut vis: Vec<bool> = vec![false];
4027 let mut found = false;
4028
4029 while !dfs.is_empty() {
4030 let idx = dfs.len() - 1;
4031 if vis[idx] {
4032 dfs.pop();
4033 vis.pop();
4034 continue;
4035 }
4036 vis[idx] = true;
4037 let current_idx = dfs[idx] as usize;
4038 let elem_id = self.layout_elements[current_idx].id;
4039
4040 let map_data = self.layout_element_map.get(&elem_id).map(|item| {
4042 (item.bounding_box, item.element_id.clone(), item.on_hover_fn.is_some())
4043 });
4044 if let Some((raw_box, elem_id_copy, has_hover)) = map_data {
4045 let mut elem_box = raw_box;
4046 elem_box.x -= root.pointer_offset.x;
4047 elem_box.y -= root.pointer_offset.y;
4048
4049 let clip_id =
4050 self.layout_element_clip_element_ids[current_idx] as u32;
4051 let clip_ok = clip_id == 0
4052 || self
4053 .layout_element_map
4054 .get(&clip_id)
4055 .map(|ci| {
4056 point_is_inside_rect(
4057 position,
4058 ci.bounding_box,
4059 )
4060 })
4061 .unwrap_or(false);
4062
4063 if point_is_inside_rect(position, elem_box) && clip_ok {
4064 if has_hover {
4066 let pointer_data = self.pointer_info;
4067 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4068 if let Some(ref mut callback) = item.on_hover_fn {
4069 callback(elem_id_copy.clone(), pointer_data);
4070 }
4071 }
4072 }
4073 self.pointer_over_ids.push(elem_id_copy);
4074 found = true;
4075 }
4076
4077 if self.element_has_config(current_idx, ElementConfigType::Text) {
4078 dfs.pop();
4079 vis.pop();
4080 continue;
4081 }
4082 let children_start = self.layout_elements[current_idx].children_start;
4083 let children_length =
4084 self.layout_elements[current_idx].children_length as usize;
4085 for ci in (0..children_length).rev() {
4086 let child = self.layout_element_children[children_start + ci];
4087 dfs.push(child);
4088 vis.push(false);
4089 }
4090 } else {
4091 dfs.pop();
4092 vis.pop();
4093 }
4094 }
4095
4096 if found {
4097 let root_elem_idx = root.layout_element_index as usize;
4098 if self.element_has_config(root_elem_idx, ElementConfigType::Floating) {
4099 if let Some(cfg_idx) = self
4100 .find_element_config_index(root_elem_idx, ElementConfigType::Floating)
4101 {
4102 if self.floating_element_configs[cfg_idx].pointer_capture_mode
4103 == PointerCaptureMode::Capture
4104 {
4105 break;
4106 }
4107 }
4108 }
4109 }
4110 }
4111
4112 if is_down {
4114 match self.pointer_info.state {
4115 PointerDataInteractionState::PressedThisFrame => {
4116 self.pointer_info.state = PointerDataInteractionState::Pressed;
4117 }
4118 s if s != PointerDataInteractionState::Pressed => {
4119 self.pointer_info.state = PointerDataInteractionState::PressedThisFrame;
4120 }
4121 _ => {}
4122 }
4123 } else {
4124 match self.pointer_info.state {
4125 PointerDataInteractionState::ReleasedThisFrame => {
4126 self.pointer_info.state = PointerDataInteractionState::Released;
4127 }
4128 s if s != PointerDataInteractionState::Released => {
4129 self.pointer_info.state = PointerDataInteractionState::ReleasedThisFrame;
4130 }
4131 _ => {}
4132 }
4133 }
4134
4135 match self.pointer_info.state {
4137 PointerDataInteractionState::PressedThisFrame => {
4138 let clicked_text_input = self.pointer_over_ids.last()
4140 .and_then(|top| self.layout_element_map.get(&top.id))
4141 .map(|item| item.is_text_input)
4142 .unwrap_or(false);
4143
4144 if clicked_text_input {
4145 self.focus_from_keyboard = false;
4147 if let Some(top) = self.pointer_over_ids.last().cloned() {
4148 if self.focused_element_id != top.id {
4149 self.change_focus(top.id);
4150 }
4151 if let Some(item) = self.layout_element_map.get(&top.id) {
4153 let click_x = self.pointer_info.position.x - item.bounding_box.x;
4154 let click_y = self.pointer_info.position.y - item.bounding_box.y;
4155 self.pending_text_click = Some((top.id, click_x, click_y, false));
4158 }
4159 self.pressed_element_ids = self.pointer_over_ids.clone();
4160 }
4161 } else {
4162 let preserves = self.pointer_over_ids.iter().any(|eid| {
4165 self.layout_element_map.get(&eid.id)
4166 .map(|item| item.preserve_focus)
4167 .unwrap_or(false)
4168 });
4169
4170 if !preserves && self.focused_element_id != 0 {
4172 self.change_focus(0);
4173 }
4174
4175 self.pressed_element_ids = self.pointer_over_ids.clone();
4177 for eid in self.pointer_over_ids.clone().iter() {
4178 if let Some(item) = self.layout_element_map.get_mut(&eid.id) {
4179 if let Some(ref mut callback) = item.on_press_fn {
4180 callback(eid.clone(), self.pointer_info);
4181 }
4182 }
4183 }
4184 }
4185 }
4186 PointerDataInteractionState::ReleasedThisFrame => {
4187 let pressed = std::mem::take(&mut self.pressed_element_ids);
4189 for eid in pressed.iter() {
4190 if let Some(item) = self.layout_element_map.get_mut(&eid.id) {
4191 if let Some(ref mut callback) = item.on_release_fn {
4192 callback(eid.clone(), self.pointer_info);
4193 }
4194 }
4195 }
4196 }
4197 _ => {}
4198 }
4199 }
4200
4201 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(
4207 &mut self,
4208 enable_drag_scrolling: bool,
4209 scroll_delta: Vector2,
4210 delta_time: f32,
4211 ) {
4212 let pointer = self.pointer_info.position;
4213 let dt = delta_time.max(0.0001); let mut i = 0;
4217 while i < self.scroll_container_datas.len() {
4218 if !self.scroll_container_datas[i].open_this_frame {
4219 self.scroll_container_datas.swap_remove(i);
4220 continue;
4221 }
4222 self.scroll_container_datas[i].open_this_frame = false;
4223 i += 1;
4224 }
4225
4226 if enable_drag_scrolling {
4228 let pointer_state = self.pointer_info.state;
4229
4230 match pointer_state {
4231 PointerDataInteractionState::PressedThisFrame => {
4232 let mut best: Option<usize> = None;
4234 for si in 0..self.scroll_container_datas.len() {
4235 let bb = self.scroll_container_datas[si].bounding_box;
4236 if pointer.x >= bb.x
4237 && pointer.x <= bb.x + bb.width
4238 && pointer.y >= bb.y
4239 && pointer.y <= bb.y + bb.height
4240 {
4241 best = Some(si);
4242 }
4243 }
4244 if let Some(si) = best {
4245 let scd = &mut self.scroll_container_datas[si];
4246 scd.pointer_scroll_active = true;
4247 scd.pointer_origin = pointer;
4248 scd.scroll_origin = scd.scroll_position;
4249 scd.scroll_momentum = Vector2::default();
4250 scd.previous_delta = Vector2::default();
4251 }
4252 }
4253 PointerDataInteractionState::Pressed => {
4254 for si in 0..self.scroll_container_datas.len() {
4256 let scd = &mut self.scroll_container_datas[si];
4257 if !scd.pointer_scroll_active {
4258 continue;
4259 }
4260
4261 let drag_delta = Vector2::new(
4262 pointer.x - scd.pointer_origin.x,
4263 pointer.y - scd.pointer_origin.y,
4264 );
4265 scd.scroll_position = Vector2::new(
4266 scd.scroll_origin.x + drag_delta.x,
4267 scd.scroll_origin.y + drag_delta.y,
4268 );
4269
4270 let frame_delta = Vector2::new(
4272 drag_delta.x - scd.previous_delta.x,
4273 drag_delta.y - scd.previous_delta.y,
4274 );
4275 let moved = frame_delta.x.abs() > 0.5 || frame_delta.y.abs() > 0.5;
4276
4277 if moved {
4278 let instant_velocity = Vector2::new(
4280 frame_delta.x / dt,
4281 frame_delta.y / dt,
4282 );
4283 let s = Self::SCROLL_VELOCITY_SMOOTHING;
4284 scd.scroll_momentum = Vector2::new(
4285 scd.scroll_momentum.x * (1.0 - s) + instant_velocity.x * s,
4286 scd.scroll_momentum.y * (1.0 - s) + instant_velocity.y * s,
4287 );
4288 }
4289 scd.previous_delta = drag_delta;
4290 }
4291 }
4292 PointerDataInteractionState::ReleasedThisFrame
4293 | PointerDataInteractionState::Released => {
4294 for si in 0..self.scroll_container_datas.len() {
4295 let scd = &mut self.scroll_container_datas[si];
4296 if !scd.pointer_scroll_active {
4297 continue;
4298 }
4299 scd.pointer_scroll_active = false;
4300 }
4301 }
4302 }
4303 }
4304
4305 for si in 0..self.scroll_container_datas.len() {
4307 let scd = &mut self.scroll_container_datas[si];
4308 if scd.pointer_scroll_active {
4309 } else if scd.scroll_momentum.x.abs() > Self::SCROLL_MIN_VELOCITY
4311 || scd.scroll_momentum.y.abs() > Self::SCROLL_MIN_VELOCITY
4312 {
4313 scd.scroll_position.x += scd.scroll_momentum.x * dt;
4315 scd.scroll_position.y += scd.scroll_momentum.y * dt;
4316
4317 let decay = (-Self::SCROLL_DECEL * dt).exp();
4319 scd.scroll_momentum.x *= decay;
4320 scd.scroll_momentum.y *= decay;
4321
4322 if scd.scroll_momentum.x.abs() < Self::SCROLL_MIN_VELOCITY {
4324 scd.scroll_momentum.x = 0.0;
4325 }
4326 if scd.scroll_momentum.y.abs() < Self::SCROLL_MIN_VELOCITY {
4327 scd.scroll_momentum.y = 0.0;
4328 }
4329 }
4330 }
4331
4332 if scroll_delta.x != 0.0 || scroll_delta.y != 0.0 {
4334 let mut best: Option<usize> = None;
4336 for si in 0..self.scroll_container_datas.len() {
4337 let bb = self.scroll_container_datas[si].bounding_box;
4338 if pointer.x >= bb.x
4339 && pointer.x <= bb.x + bb.width
4340 && pointer.y >= bb.y
4341 && pointer.y <= bb.y + bb.height
4342 {
4343 best = Some(si);
4344 }
4345 }
4346 if let Some(si) = best {
4347 let scd = &mut self.scroll_container_datas[si];
4348 scd.scroll_position.y += scroll_delta.y;
4349 scd.scroll_position.x += scroll_delta.x;
4350 scd.scroll_momentum = Vector2::default();
4352 }
4353 }
4354
4355 for si in 0..self.scroll_container_datas.len() {
4357 let scd = &mut self.scroll_container_datas[si];
4358 let max_scroll_y =
4359 -(scd.content_size.height - scd.bounding_box.height).max(0.0);
4360 let max_scroll_x =
4361 -(scd.content_size.width - scd.bounding_box.width).max(0.0);
4362 scd.scroll_position.y = scd.scroll_position.y.clamp(max_scroll_y, 0.0);
4363 scd.scroll_position.x = scd.scroll_position.x.clamp(max_scroll_x, 0.0);
4364
4365 if scd.scroll_position.y >= 0.0 || scd.scroll_position.y <= max_scroll_y {
4367 scd.scroll_momentum.y = 0.0;
4368 }
4369 if scd.scroll_position.x >= 0.0 || scd.scroll_position.x <= max_scroll_x {
4370 scd.scroll_momentum.x = 0.0;
4371 }
4372 }
4373 }
4374
4375 pub fn hovered(&self) -> bool {
4376 let open_idx = self.get_open_layout_element();
4377 let elem_id = self.layout_elements[open_idx].id;
4378 self.pointer_over_ids.iter().any(|eid| eid.id == elem_id)
4379 }
4380
4381 pub fn on_hover(&mut self, callback: Box<dyn FnMut(Id, PointerData)>) {
4382 let open_idx = self.get_open_layout_element();
4383 let elem_id = self.layout_elements[open_idx].id;
4384 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4385 item.on_hover_fn = Some(callback);
4386 }
4387 }
4388
4389 pub fn pressed(&self) -> bool {
4390 let open_idx = self.get_open_layout_element();
4391 let elem_id = self.layout_elements[open_idx].id;
4392 self.pressed_element_ids.iter().any(|eid| eid.id == elem_id)
4393 }
4394
4395 pub fn set_press_callbacks(
4396 &mut self,
4397 on_press: Option<Box<dyn FnMut(Id, PointerData)>>,
4398 on_release: Option<Box<dyn FnMut(Id, PointerData)>>,
4399 ) {
4400 let open_idx = self.get_open_layout_element();
4401 let elem_id = self.layout_elements[open_idx].id;
4402 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4403 item.on_press_fn = on_press;
4404 item.on_release_fn = on_release;
4405 }
4406 }
4407
4408 pub fn focused(&self) -> bool {
4410 let open_idx = self.get_open_layout_element();
4411 let elem_id = self.layout_elements[open_idx].id;
4412 self.focused_element_id == elem_id && elem_id != 0
4413 }
4414
4415 pub fn focused_element(&self) -> Option<Id> {
4417 if self.focused_element_id != 0 {
4418 self.layout_element_map
4419 .get(&self.focused_element_id)
4420 .map(|item| item.element_id.clone())
4421 } else {
4422 None
4423 }
4424 }
4425
4426 pub fn set_focus(&mut self, element_id: u32) {
4428 self.change_focus(element_id);
4429 }
4430
4431 pub fn clear_focus(&mut self) {
4433 self.change_focus(0);
4434 }
4435
4436 pub(crate) fn change_focus(&mut self, new_id: u32) {
4438 let old_id = self.focused_element_id;
4439 if old_id == new_id {
4440 return;
4441 }
4442 self.focused_element_id = new_id;
4443 if new_id == 0 {
4444 self.focus_from_keyboard = false;
4445 }
4446
4447 if old_id != 0 {
4449 if let Some(item) = self.layout_element_map.get_mut(&old_id) {
4450 let id_copy = item.element_id.clone();
4451 if let Some(ref mut callback) = item.on_unfocus_fn {
4452 callback(id_copy);
4453 }
4454 }
4455 }
4456
4457 if new_id != 0 {
4459 if let Some(item) = self.layout_element_map.get_mut(&new_id) {
4460 let id_copy = item.element_id.clone();
4461 if let Some(ref mut callback) = item.on_focus_fn {
4462 callback(id_copy);
4463 }
4464 }
4465 }
4466 }
4467
4468 #[allow(dead_code)]
4471 pub(crate) fn fire_press(&mut self, element_id: u32) {
4472 if let Some(item) = self.layout_element_map.get_mut(&element_id) {
4473 let id_copy = item.element_id.clone();
4474 if let Some(ref mut callback) = item.on_press_fn {
4475 callback(id_copy, PointerData::default());
4476 }
4477 }
4478 }
4479
4480 pub fn set_focus_callbacks(
4481 &mut self,
4482 on_focus: Option<Box<dyn FnMut(Id)>>,
4483 on_unfocus: Option<Box<dyn FnMut(Id)>>,
4484 ) {
4485 let open_idx = self.get_open_layout_element();
4486 let elem_id = self.layout_elements[open_idx].id;
4487 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4488 item.on_focus_fn = on_focus;
4489 item.on_unfocus_fn = on_unfocus;
4490 }
4491 }
4492
4493 pub fn set_text_input_callbacks(
4495 &mut self,
4496 on_changed: Option<Box<dyn FnMut(&str)>>,
4497 on_submit: Option<Box<dyn FnMut(&str)>>,
4498 ) {
4499 let open_idx = self.get_open_layout_element();
4500 let elem_id = self.layout_elements[open_idx].id;
4501 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4502 item.on_text_changed_fn = on_changed;
4503 item.on_text_submit_fn = on_submit;
4504 }
4505 }
4506
4507 pub fn is_text_input_focused(&self) -> bool {
4509 if self.focused_element_id == 0 {
4510 return false;
4511 }
4512 self.text_edit_states.contains_key(&self.focused_element_id)
4513 }
4514
4515 pub fn is_focused_text_input_multiline(&self) -> bool {
4517 if self.focused_element_id == 0 {
4518 return false;
4519 }
4520 self.text_input_element_ids.iter()
4521 .position(|&id| id == self.focused_element_id)
4522 .and_then(|idx| self.text_input_configs.get(idx))
4523 .map_or(false, |cfg| cfg.is_multiline)
4524 }
4525
4526 pub fn get_text_value(&self, element_id: u32) -> &str {
4528 self.text_edit_states
4529 .get(&element_id)
4530 .map(|state| state.text.as_str())
4531 .unwrap_or("")
4532 }
4533
4534 pub fn set_text_value(&mut self, element_id: u32, value: &str) {
4536 let state = self.text_edit_states
4537 .entry(element_id)
4538 .or_insert_with(crate::text_input::TextEditState::default);
4539 state.text = value.to_string();
4540 #[cfg(feature = "text-styling")]
4541 let max_pos = crate::text_input::styling::cursor_len(&state.text);
4542 #[cfg(not(feature = "text-styling"))]
4543 let max_pos = state.text.chars().count();
4544 if state.cursor_pos > max_pos {
4545 state.cursor_pos = max_pos;
4546 }
4547 state.selection_anchor = None;
4548 state.reset_blink();
4549 }
4550
4551 pub fn get_cursor_pos(&self, element_id: u32) -> usize {
4554 self.text_edit_states
4555 .get(&element_id)
4556 .map(|state| state.cursor_pos)
4557 .unwrap_or(0)
4558 }
4559
4560 pub fn set_cursor_pos(&mut self, element_id: u32, pos: usize) {
4564 if let Some(state) = self.text_edit_states.get_mut(&element_id) {
4565 #[cfg(feature = "text-styling")]
4566 let max_pos = crate::text_input::styling::cursor_len(&state.text);
4567 #[cfg(not(feature = "text-styling"))]
4568 let max_pos = state.text.chars().count();
4569 state.cursor_pos = pos.min(max_pos);
4570 state.selection_anchor = None;
4571 state.reset_blink();
4572 }
4573 }
4574
4575 pub fn get_selection_range(&self, element_id: u32) -> Option<(usize, usize)> {
4578 self.text_edit_states
4579 .get(&element_id)
4580 .and_then(|state| state.selection_range())
4581 }
4582
4583 pub fn set_selection(&mut self, element_id: u32, anchor: usize, cursor: usize) {
4587 if let Some(state) = self.text_edit_states.get_mut(&element_id) {
4588 #[cfg(feature = "text-styling")]
4589 let max_pos = crate::text_input::styling::cursor_len(&state.text);
4590 #[cfg(not(feature = "text-styling"))]
4591 let max_pos = state.text.chars().count();
4592 state.selection_anchor = Some(anchor.min(max_pos));
4593 state.cursor_pos = cursor.min(max_pos);
4594 state.reset_blink();
4595 }
4596 }
4597
4598 pub fn is_element_pressed(&self, element_id: u32) -> bool {
4600 self.pressed_element_ids.iter().any(|eid| eid.id == element_id)
4601 }
4602
4603 pub fn process_text_input_char(&mut self, ch: char) -> bool {
4606 if !self.is_text_input_focused() {
4607 return false;
4608 }
4609 let elem_id = self.focused_element_id;
4610
4611 let max_length = self.text_input_element_ids.iter()
4613 .position(|&id| id == elem_id)
4614 .and_then(|idx| self.text_input_configs.get(idx))
4615 .and_then(|cfg| cfg.max_length);
4616
4617 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
4618 let old_text = state.text.clone();
4619 state.push_undo(crate::text_input::UndoActionKind::InsertChar);
4620 #[cfg(feature = "text-styling")]
4621 {
4622 state.insert_char_styled(ch, max_length);
4623 }
4624 #[cfg(not(feature = "text-styling"))]
4625 {
4626 state.insert_text(&ch.to_string(), max_length);
4627 }
4628 if state.text != old_text {
4629 let new_text = state.text.clone();
4630 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4632 if let Some(ref mut callback) = item.on_text_changed_fn {
4633 callback(&new_text);
4634 }
4635 }
4636 }
4637 true
4638 } else {
4639 false
4640 }
4641 }
4642
4643 pub fn process_text_input_action(&mut self, action: TextInputAction) -> bool {
4647 if !self.is_text_input_focused() {
4648 return false;
4649 }
4650 let elem_id = self.focused_element_id;
4651
4652 let config_idx = self.text_input_element_ids.iter()
4654 .position(|&id| id == elem_id);
4655 let (max_length, is_multiline, font_asset, font_size) = config_idx
4656 .and_then(|idx| self.text_input_configs.get(idx))
4657 .map(|cfg| (cfg.max_length, cfg.is_multiline, cfg.font_asset, cfg.font_size))
4658 .unwrap_or((None, false, None, 16));
4659
4660 let visual_lines_opt = if is_multiline {
4662 let visible_width = self.layout_element_map
4663 .get(&elem_id)
4664 .map(|item| item.bounding_box.width)
4665 .unwrap_or(0.0);
4666 if visible_width > 0.0 {
4667 if let Some(state) = self.text_edit_states.get(&elem_id) {
4668 if let Some(ref measure_fn) = self.measure_text_fn {
4669 Some(crate::text_input::wrap_lines(
4670 &state.text,
4671 visible_width,
4672 font_asset,
4673 font_size,
4674 measure_fn.as_ref(),
4675 ))
4676 } else { None }
4677 } else { None }
4678 } else { None }
4679 } else { None };
4680
4681 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
4682 let old_text = state.text.clone();
4683
4684 match &action {
4686 TextInputAction::Backspace => state.push_undo(crate::text_input::UndoActionKind::Backspace),
4687 TextInputAction::Delete => state.push_undo(crate::text_input::UndoActionKind::Delete),
4688 TextInputAction::BackspaceWord => state.push_undo(crate::text_input::UndoActionKind::DeleteWord),
4689 TextInputAction::DeleteWord => state.push_undo(crate::text_input::UndoActionKind::DeleteWord),
4690 TextInputAction::Cut => state.push_undo(crate::text_input::UndoActionKind::Cut),
4691 TextInputAction::Paste { .. } => state.push_undo(crate::text_input::UndoActionKind::Paste),
4692 TextInputAction::Submit if is_multiline => state.push_undo(crate::text_input::UndoActionKind::InsertChar),
4693 _ => {}
4694 }
4695
4696 match action {
4697 TextInputAction::MoveLeft { shift } => {
4698 #[cfg(feature = "text-styling")]
4699 { state.move_left_styled(shift); }
4700 #[cfg(not(feature = "text-styling"))]
4701 { state.move_left(shift); }
4702 }
4703 TextInputAction::MoveRight { shift } => {
4704 #[cfg(feature = "text-styling")]
4705 { state.move_right_styled(shift); }
4706 #[cfg(not(feature = "text-styling"))]
4707 { state.move_right(shift); }
4708 }
4709 TextInputAction::MoveWordLeft { shift } => {
4710 #[cfg(feature = "text-styling")]
4711 { state.move_word_left_styled(shift); }
4712 #[cfg(not(feature = "text-styling"))]
4713 { state.move_word_left(shift); }
4714 }
4715 TextInputAction::MoveWordRight { shift } => {
4716 #[cfg(feature = "text-styling")]
4717 { state.move_word_right_styled(shift); }
4718 #[cfg(not(feature = "text-styling"))]
4719 { state.move_word_right(shift); }
4720 }
4721 TextInputAction::MoveHome { shift } => {
4722 #[cfg(not(feature = "text-styling"))]
4724 {
4725 if let Some(ref vl) = visual_lines_opt {
4726 let new_pos = crate::text_input::visual_line_home(vl, state.cursor_pos);
4727 if shift && state.selection_anchor.is_none() {
4728 state.selection_anchor = Some(state.cursor_pos);
4729 }
4730 state.cursor_pos = new_pos;
4731 if !shift { state.selection_anchor = None; }
4732 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
4733 state.reset_blink();
4734 } else {
4735 state.move_home(shift);
4736 }
4737 }
4738 #[cfg(feature = "text-styling")]
4739 {
4740 state.move_home_styled(shift);
4741 }
4742 }
4743 TextInputAction::MoveEnd { shift } => {
4744 #[cfg(not(feature = "text-styling"))]
4745 {
4746 if let Some(ref vl) = visual_lines_opt {
4747 let new_pos = crate::text_input::visual_line_end(vl, state.cursor_pos);
4748 if shift && state.selection_anchor.is_none() {
4749 state.selection_anchor = Some(state.cursor_pos);
4750 }
4751 state.cursor_pos = new_pos;
4752 if !shift { state.selection_anchor = None; }
4753 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
4754 state.reset_blink();
4755 } else {
4756 state.move_end(shift);
4757 }
4758 }
4759 #[cfg(feature = "text-styling")]
4760 {
4761 state.move_end_styled(shift);
4762 }
4763 }
4764 TextInputAction::MoveUp { shift } => {
4765 #[cfg(not(feature = "text-styling"))]
4766 {
4767 if let Some(ref vl) = visual_lines_opt {
4768 let new_pos = crate::text_input::visual_move_up(vl, state.cursor_pos);
4769 if shift && state.selection_anchor.is_none() {
4770 state.selection_anchor = Some(state.cursor_pos);
4771 }
4772 state.cursor_pos = new_pos;
4773 if !shift { state.selection_anchor = None; }
4774 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
4775 state.reset_blink();
4776 } else {
4777 state.move_up(shift);
4778 }
4779 }
4780 #[cfg(feature = "text-styling")]
4781 {
4782 state.move_up_styled(shift, visual_lines_opt.as_deref());
4783 }
4784 }
4785 TextInputAction::MoveDown { shift } => {
4786 #[cfg(not(feature = "text-styling"))]
4787 {
4788 if let Some(ref vl) = visual_lines_opt {
4789 let text_len = state.text.chars().count();
4790 let new_pos = crate::text_input::visual_move_down(vl, state.cursor_pos, text_len);
4791 if shift && state.selection_anchor.is_none() {
4792 state.selection_anchor = Some(state.cursor_pos);
4793 }
4794 state.cursor_pos = new_pos;
4795 if !shift { state.selection_anchor = None; }
4796 else if state.selection_anchor == Some(state.cursor_pos) { state.selection_anchor = None; }
4797 state.reset_blink();
4798 } else {
4799 state.move_down(shift);
4800 }
4801 }
4802 #[cfg(feature = "text-styling")]
4803 {
4804 state.move_down_styled(shift, visual_lines_opt.as_deref());
4805 }
4806 }
4807 TextInputAction::Backspace => {
4808 #[cfg(feature = "text-styling")]
4809 { state.backspace_styled(); }
4810 #[cfg(not(feature = "text-styling"))]
4811 { state.backspace(); }
4812 }
4813 TextInputAction::Delete => {
4814 #[cfg(feature = "text-styling")]
4815 { state.delete_forward_styled(); }
4816 #[cfg(not(feature = "text-styling"))]
4817 { state.delete_forward(); }
4818 }
4819 TextInputAction::BackspaceWord => {
4820 #[cfg(feature = "text-styling")]
4821 { state.backspace_word_styled(); }
4822 #[cfg(not(feature = "text-styling"))]
4823 { state.backspace_word(); }
4824 }
4825 TextInputAction::DeleteWord => {
4826 #[cfg(feature = "text-styling")]
4827 { state.delete_word_forward_styled(); }
4828 #[cfg(not(feature = "text-styling"))]
4829 { state.delete_word_forward(); }
4830 }
4831 TextInputAction::SelectAll => {
4832 #[cfg(feature = "text-styling")]
4833 { state.select_all_styled(); }
4834 #[cfg(not(feature = "text-styling"))]
4835 { state.select_all(); }
4836 }
4837 TextInputAction::Copy => {
4838 }
4840 TextInputAction::Cut => {
4841 #[cfg(feature = "text-styling")]
4842 { state.delete_selection_styled(); }
4843 #[cfg(not(feature = "text-styling"))]
4844 { state.delete_selection(); }
4845 }
4846 TextInputAction::Paste { text } => {
4847 #[cfg(feature = "text-styling")]
4848 {
4849 let escaped = crate::text_input::styling::escape_str(&text);
4850 state.insert_text_styled(&escaped, max_length);
4851 }
4852 #[cfg(not(feature = "text-styling"))]
4853 {
4854 state.insert_text(&text, max_length);
4855 }
4856 }
4857 TextInputAction::Submit => {
4858 if is_multiline {
4859 #[cfg(feature = "text-styling")]
4860 { state.insert_text_styled("\n", max_length); }
4861 #[cfg(not(feature = "text-styling"))]
4862 { state.insert_text("\n", max_length); }
4863 } else {
4864 let text = state.text.clone();
4865 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4867 if let Some(ref mut callback) = item.on_text_submit_fn {
4868 callback(&text);
4869 }
4870 }
4871 return true;
4872 }
4873 }
4874 TextInputAction::Undo => {
4875 state.undo();
4876 }
4877 TextInputAction::Redo => {
4878 state.redo();
4879 }
4880 }
4881 if state.text != old_text {
4882 let new_text = state.text.clone();
4883 if let Some(item) = self.layout_element_map.get_mut(&elem_id) {
4884 if let Some(ref mut callback) = item.on_text_changed_fn {
4885 callback(&new_text);
4886 }
4887 }
4888 }
4889 true
4890 } else {
4891 false
4892 }
4893 }
4894
4895 pub fn update_text_input_blink_timers(&mut self) {
4897 let dt = self.frame_delta_time as f64;
4898 for state in self.text_edit_states.values_mut() {
4899 state.cursor_blink_timer += dt;
4900 }
4901 }
4902
4903 pub fn update_text_input_scroll(&mut self) {
4905 let focused = self.focused_element_id;
4906 if focused == 0 {
4907 return;
4908 }
4909 let (visible_width, visible_height) = self.layout_element_map
4911 .get(&focused)
4912 .map(|item| (item.bounding_box.width, item.bounding_box.height))
4913 .unwrap_or((0.0, 0.0));
4914 if visible_width <= 0.0 {
4915 return;
4916 }
4917
4918 if let Some(state) = self.text_edit_states.get(&focused) {
4920 let config_idx = self.text_input_element_ids.iter()
4921 .position(|&id| id == focused);
4922 if let Some(idx) = config_idx {
4923 if let Some(cfg) = self.text_input_configs.get(idx) {
4924 if let Some(ref measure_fn) = self.measure_text_fn {
4925 let disp_text = crate::text_input::display_text(
4926 &state.text,
4927 &cfg.placeholder,
4928 cfg.is_password && !cfg.is_multiline,
4929 );
4930 if !state.text.is_empty() {
4931 if cfg.is_multiline {
4932 let visual_lines = crate::text_input::wrap_lines(
4934 &disp_text,
4935 visible_width,
4936 cfg.font_asset,
4937 cfg.font_size,
4938 measure_fn.as_ref(),
4939 );
4940 #[cfg(feature = "text-styling")]
4941 let raw_cursor = state.cursor_pos_raw();
4942 #[cfg(not(feature = "text-styling"))]
4943 let raw_cursor = state.cursor_pos;
4944 let (cursor_line, cursor_col) = crate::text_input::cursor_to_visual_pos(&visual_lines, raw_cursor);
4945 let vl_text = visual_lines.get(cursor_line).map(|vl| vl.text.as_str()).unwrap_or("");
4946 let line_positions = crate::text_input::compute_char_x_positions(
4947 vl_text,
4948 cfg.font_asset,
4949 cfg.font_size,
4950 measure_fn.as_ref(),
4951 );
4952 let cursor_x = line_positions.get(cursor_col).copied().unwrap_or(0.0);
4953 let cfg_font_asset = cfg.font_asset;
4954 let cfg_font_size = cfg.font_size;
4955 let cfg_line_height_val = cfg.line_height;
4956 let natural_height = self.font_height(cfg_font_asset, cfg_font_size);
4957 let line_height = if cfg_line_height_val > 0 { cfg_line_height_val as f32 } else { natural_height };
4958 if let Some(state_mut) = self.text_edit_states.get_mut(&focused) {
4959 state_mut.ensure_cursor_visible(cursor_x, visible_width);
4960 state_mut.ensure_cursor_visible_vertical(cursor_line, line_height, visible_height);
4961 }
4962 } else {
4963 let char_x_positions = crate::text_input::compute_char_x_positions(
4964 &disp_text,
4965 cfg.font_asset,
4966 cfg.font_size,
4967 measure_fn.as_ref(),
4968 );
4969 #[cfg(feature = "text-styling")]
4970 let raw_cursor = state.cursor_pos_raw();
4971 #[cfg(not(feature = "text-styling"))]
4972 let raw_cursor = state.cursor_pos;
4973 let cursor_x = char_x_positions
4974 .get(raw_cursor)
4975 .copied()
4976 .unwrap_or(0.0);
4977 if let Some(state_mut) = self.text_edit_states.get_mut(&focused) {
4978 state_mut.ensure_cursor_visible(cursor_x, visible_width);
4979 }
4980 }
4981 } else if let Some(state_mut) = self.text_edit_states.get_mut(&focused) {
4982 state_mut.scroll_offset = 0.0;
4983 state_mut.scroll_offset_y = 0.0;
4984 }
4985 }
4986 }
4987 }
4988 }
4989 }
4990
4991 pub fn update_text_input_pointer_scroll(&mut self, scroll_delta: Vector2) -> bool {
4996 let mut consumed_scroll = false;
4997
4998 let focused = self.focused_element_id;
4999
5000 let has_scroll = scroll_delta.x.abs() > 0.01 || scroll_delta.y.abs() > 0.01;
5002 if has_scroll {
5003 let p = self.pointer_info.position;
5004 let hovered_ti = self.text_input_element_ids.iter().enumerate().find(|&(_, &id)| {
5006 self.layout_element_map.get(&id)
5007 .map(|item| {
5008 let bb = item.bounding_box;
5009 p.x >= bb.x && p.x <= bb.x + bb.width
5010 && p.y >= bb.y && p.y <= bb.y + bb.height
5011 })
5012 .unwrap_or(false)
5013 });
5014 if let Some((idx, &elem_id)) = hovered_ti {
5015 let is_multiline = self.text_input_configs.get(idx)
5016 .map(|cfg| cfg.is_multiline)
5017 .unwrap_or(false);
5018 if let Some(state) = self.text_edit_states.get_mut(&elem_id) {
5019 if is_multiline {
5020 if scroll_delta.y.abs() > 0.01 {
5021 state.scroll_offset_y -= scroll_delta.y;
5022 if state.scroll_offset_y < 0.0 {
5023 state.scroll_offset_y = 0.0;
5024 }
5025 }
5026 } else {
5027 let h_delta = if scroll_delta.x.abs() > scroll_delta.y.abs() {
5028 scroll_delta.x
5029 } else {
5030 scroll_delta.y
5031 };
5032 if h_delta.abs() > 0.01 {
5033 state.scroll_offset -= h_delta;
5034 if state.scroll_offset < 0.0 {
5035 state.scroll_offset = 0.0;
5036 }
5037 }
5038 }
5039 consumed_scroll = true;
5040 }
5041 }
5042 }
5043
5044 if focused == 0 {
5046 if self.text_input_drag_active {
5047 let pointer_state = self.pointer_info.state;
5048 if matches!(pointer_state, PointerDataInteractionState::ReleasedThisFrame | PointerDataInteractionState::Released) {
5049 self.text_input_drag_active = false;
5050 }
5051 }
5052 return consumed_scroll;
5053 }
5054
5055 let ti_info = self.text_input_element_ids.iter()
5056 .position(|&id| id == focused)
5057 .and_then(|idx| self.text_input_configs.get(idx).map(|cfg| cfg.is_multiline));
5058 let is_text_input = ti_info.is_some();
5059 let is_multiline = ti_info.unwrap_or(false);
5060
5061 if !is_text_input {
5062 if self.text_input_drag_active {
5063 let pointer_state = self.pointer_info.state;
5064 if matches!(pointer_state, PointerDataInteractionState::ReleasedThisFrame | PointerDataInteractionState::Released) {
5065 self.text_input_drag_active = false;
5066 }
5067 }
5068 return consumed_scroll;
5069 }
5070
5071 let pointer_over_focused = self.layout_element_map.get(&focused)
5072 .map(|item| {
5073 let bb = item.bounding_box;
5074 let p = self.pointer_info.position;
5075 p.x >= bb.x && p.x <= bb.x + bb.width
5076 && p.y >= bb.y && p.y <= bb.y + bb.height
5077 })
5078 .unwrap_or(false);
5079
5080 let pointer = self.pointer_info.position;
5081 let pointer_state = self.pointer_info.state;
5082
5083 match pointer_state {
5084 PointerDataInteractionState::PressedThisFrame => {
5085 if pointer_over_focused {
5086 let (scroll_x, scroll_y) = self.text_edit_states.get(&focused)
5087 .map(|s| (s.scroll_offset, s.scroll_offset_y))
5088 .unwrap_or((0.0, 0.0));
5089 self.text_input_drag_active = true;
5090 self.text_input_drag_origin = pointer;
5091 self.text_input_drag_scroll_origin = Vector2::new(scroll_x, scroll_y);
5092 self.text_input_drag_element_id = focused;
5093 }
5094 }
5095 PointerDataInteractionState::Pressed => {
5096 if self.text_input_drag_active {
5097 if let Some(state) = self.text_edit_states.get_mut(&self.text_input_drag_element_id) {
5098 if is_multiline {
5099 let drag_delta_y = self.text_input_drag_origin.y - pointer.y;
5100 state.scroll_offset_y = (self.text_input_drag_scroll_origin.y + drag_delta_y).max(0.0);
5101 } else {
5102 let drag_delta_x = self.text_input_drag_origin.x - pointer.x;
5103 state.scroll_offset = (self.text_input_drag_scroll_origin.x + drag_delta_x).max(0.0);
5104 }
5105 }
5106 }
5107 }
5108 PointerDataInteractionState::ReleasedThisFrame
5109 | PointerDataInteractionState::Released => {
5110 self.text_input_drag_active = false;
5111 }
5112 }
5113 consumed_scroll
5114 }
5115
5116 pub fn clamp_text_input_scroll(&mut self) {
5120 for i in 0..self.text_input_element_ids.len() {
5121 let elem_id = self.text_input_element_ids[i];
5122 let cfg = match self.text_input_configs.get(i) {
5123 Some(c) => c,
5124 None => continue,
5125 };
5126
5127 let font_asset = cfg.font_asset;
5128 let font_size = cfg.font_size;
5129 let cfg_line_height = cfg.line_height;
5130 let is_multiline = cfg.is_multiline;
5131 let is_password = cfg.is_password;
5132
5133 let (visible_width, visible_height) = self.layout_element_map.get(&elem_id)
5134 .map(|item| (item.bounding_box.width, item.bounding_box.height))
5135 .unwrap_or((200.0, 0.0));
5136
5137 let text_empty = self.text_edit_states.get(&elem_id)
5138 .map(|s| s.text.is_empty())
5139 .unwrap_or(true);
5140
5141 if text_empty {
5142 if let Some(state_mut) = self.text_edit_states.get_mut(&elem_id) {
5143 state_mut.scroll_offset = 0.0;
5144 state_mut.scroll_offset_y = 0.0;
5145 }
5146 continue;
5147 }
5148
5149 if let Some(ref measure_fn) = self.measure_text_fn {
5150 let disp_text = self.text_edit_states.get(&elem_id)
5151 .map(|s| crate::text_input::display_text(&s.text, "", is_password && !is_multiline))
5152 .unwrap_or_default();
5153
5154 if is_multiline {
5155 let visual_lines = crate::text_input::wrap_lines(
5156 &disp_text,
5157 visible_width,
5158 font_asset,
5159 font_size,
5160 measure_fn.as_ref(),
5161 );
5162 let natural_height = self.font_height(font_asset, font_size);
5163 let font_height = if cfg_line_height > 0 { cfg_line_height as f32 } else { natural_height };
5164 let total_height = visual_lines.len() as f32 * font_height;
5165 let max_scroll = (total_height - visible_height).max(0.0);
5166 if let Some(state_mut) = self.text_edit_states.get_mut(&elem_id) {
5167 if state_mut.scroll_offset_y > max_scroll {
5168 state_mut.scroll_offset_y = max_scroll;
5169 }
5170 }
5171 } else {
5172 let char_x_positions = crate::text_input::compute_char_x_positions(
5174 &disp_text,
5175 font_asset,
5176 font_size,
5177 measure_fn.as_ref(),
5178 );
5179 let total_width = char_x_positions.last().copied().unwrap_or(0.0);
5180 let max_scroll = (total_width - visible_width).max(0.0);
5181 if let Some(state_mut) = self.text_edit_states.get_mut(&elem_id) {
5182 if state_mut.scroll_offset > max_scroll {
5183 state_mut.scroll_offset = max_scroll;
5184 }
5185 }
5186 }
5187 }
5188 }
5189 }
5190
5191 pub fn cycle_focus(&mut self, reverse: bool) {
5194 if self.focusable_elements.is_empty() {
5195 return;
5196 }
5197 self.focus_from_keyboard = true;
5198
5199 let mut sorted: Vec<FocusableEntry> = self.focusable_elements.clone();
5201 sorted.sort_by(|a, b| {
5202 match (a.tab_index, b.tab_index) {
5203 (Some(ai), Some(bi)) => ai.cmp(&bi).then(a.insertion_order.cmp(&b.insertion_order)),
5204 (Some(_), None) => std::cmp::Ordering::Less,
5205 (None, Some(_)) => std::cmp::Ordering::Greater,
5206 (None, None) => a.insertion_order.cmp(&b.insertion_order),
5207 }
5208 });
5209
5210 let current_pos = sorted
5212 .iter()
5213 .position(|e| e.element_id == self.focused_element_id);
5214
5215 let next_pos = match current_pos {
5216 Some(pos) => {
5217 if reverse {
5218 if pos == 0 { sorted.len() - 1 } else { pos - 1 }
5219 } else {
5220 if pos + 1 >= sorted.len() { 0 } else { pos + 1 }
5221 }
5222 }
5223 None => {
5224 if reverse { sorted.len() - 1 } else { 0 }
5226 }
5227 };
5228
5229 self.change_focus(sorted[next_pos].element_id);
5230 }
5231
5232 pub fn arrow_focus(&mut self, direction: ArrowDirection) {
5234 if self.focused_element_id == 0 {
5235 return;
5236 }
5237 self.focus_from_keyboard = true;
5238 if let Some(config) = self.accessibility_configs.get(&self.focused_element_id) {
5239 let target = match direction {
5240 ArrowDirection::Left => config.focus_left,
5241 ArrowDirection::Right => config.focus_right,
5242 ArrowDirection::Up => config.focus_up,
5243 ArrowDirection::Down => config.focus_down,
5244 };
5245 if let Some(target_id) = target {
5246 self.change_focus(target_id);
5247 }
5248 }
5249 }
5250
5251 pub fn handle_keyboard_activation(&mut self, pressed: bool, released: bool) {
5253 if self.focused_element_id == 0 {
5254 return;
5255 }
5256 if pressed {
5257 let id_copy = self
5258 .layout_element_map
5259 .get(&self.focused_element_id)
5260 .map(|item| item.element_id.clone());
5261 if let Some(id) = id_copy {
5262 self.pressed_element_ids = vec![id.clone()];
5263 if let Some(item) = self.layout_element_map.get_mut(&self.focused_element_id) {
5264 if let Some(ref mut callback) = item.on_press_fn {
5265 callback(id, PointerData::default());
5266 }
5267 }
5268 }
5269 }
5270 if released {
5271 let pressed = std::mem::take(&mut self.pressed_element_ids);
5272 for eid in pressed.iter() {
5273 if let Some(item) = self.layout_element_map.get_mut(&eid.id) {
5274 if let Some(ref mut callback) = item.on_release_fn {
5275 callback(eid.clone(), PointerData::default());
5276 }
5277 }
5278 }
5279 }
5280 }
5281
5282 pub fn pointer_over(&self, element_id: Id) -> bool {
5283 self.pointer_over_ids.iter().any(|eid| eid.id == element_id.id)
5284 }
5285
5286 pub fn get_pointer_over_ids(&self) -> &[Id] {
5287 &self.pointer_over_ids
5288 }
5289
5290 pub fn get_element_data(&self, id: Id) -> Option<BoundingBox> {
5291 self.layout_element_map
5292 .get(&id.id)
5293 .map(|item| item.bounding_box)
5294 }
5295
5296 pub fn get_scroll_container_data(&self, id: Id) -> ScrollContainerData {
5297 for scd in &self.scroll_container_datas {
5298 if scd.element_id == id.id {
5299 return ScrollContainerData {
5300 scroll_position: scd.scroll_position,
5301 scroll_container_dimensions: Dimensions::new(
5302 scd.bounding_box.width,
5303 scd.bounding_box.height,
5304 ),
5305 content_dimensions: scd.content_size,
5306 horizontal: false,
5307 vertical: false,
5308 found: true,
5309 };
5310 }
5311 }
5312 ScrollContainerData::default()
5313 }
5314
5315 pub fn get_scroll_offset(&self) -> Vector2 {
5316 let open_idx = self.get_open_layout_element();
5317 let elem_id = self.layout_elements[open_idx].id;
5318 for scd in &self.scroll_container_datas {
5319 if scd.element_id == elem_id {
5320 return scd.scroll_position;
5321 }
5322 }
5323 Vector2::default()
5324 }
5325
5326 const DEBUG_VIEW_WIDTH: f32 = 400.0;
5327 const DEBUG_VIEW_ROW_HEIGHT: f32 = 30.0;
5328 const DEBUG_VIEW_OUTER_PADDING: u16 = 10;
5329 const DEBUG_VIEW_INDENT_WIDTH: u16 = 16;
5330
5331 const DEBUG_COLOR_1: Color = Color::rgba(58.0, 56.0, 52.0, 255.0);
5332 const DEBUG_COLOR_2: Color = Color::rgba(62.0, 60.0, 58.0, 255.0);
5333 const DEBUG_COLOR_3: Color = Color::rgba(141.0, 133.0, 135.0, 255.0);
5334 const DEBUG_COLOR_4: Color = Color::rgba(238.0, 226.0, 231.0, 255.0);
5335 #[allow(dead_code)]
5336 const DEBUG_COLOR_SELECTED_ROW: Color = Color::rgba(102.0, 80.0, 78.0, 255.0);
5337 const DEBUG_HIGHLIGHT_COLOR: Color = Color::rgba(168.0, 66.0, 28.0, 100.0);
5338
5339 #[cfg(feature = "text-styling")]
5342 fn debug_escape_str(s: &str) -> String {
5343 let mut result = String::with_capacity(s.len());
5344 for c in s.chars() {
5345 match c {
5346 '{' | '}' | '|' | '\\' => {
5347 result.push('\\');
5348 result.push(c);
5349 }
5350 _ => result.push(c),
5351 }
5352 }
5353 result
5354 }
5355
5356 fn debug_text(&mut self, text: &'static str, config_index: usize) {
5360 #[cfg(feature = "text-styling")]
5361 {
5362 let escaped = Self::debug_escape_str(text);
5363 self.open_text_element(&escaped, config_index);
5364 }
5365 #[cfg(not(feature = "text-styling"))]
5366 {
5367 self.open_text_element(text, config_index);
5368 }
5369 }
5370
5371 fn debug_raw_text(&mut self, text: &str, config_index: usize) {
5375 #[cfg(feature = "text-styling")]
5376 {
5377 let escaped = Self::debug_escape_str(text);
5378 self.open_text_element(&escaped, config_index);
5379 }
5380 #[cfg(not(feature = "text-styling"))]
5381 {
5382 self.open_text_element(text, config_index);
5383 }
5384 }
5385
5386 fn debug_int_text(&mut self, value: f32, config_index: usize) {
5388 let s = format!("{}", value as i32);
5389 self.open_text_element(&s, config_index);
5390 }
5391
5392 fn debug_float_text(&mut self, value: f32, config_index: usize) {
5394 let s = format!("{:.2}", value);
5395 self.open_text_element(&s, config_index);
5396 }
5397
5398 fn debug_open(&mut self, decl: &ElementDeclaration<CustomElementData>) {
5400 self.open_element();
5401 self.configure_open_element(decl);
5402 }
5403
5404 fn debug_open_id(&mut self, name: &str, decl: &ElementDeclaration<CustomElementData>) {
5406 self.open_element_with_id(&hash_string(name, 0));
5407 self.configure_open_element(decl);
5408 }
5409
5410 fn debug_open_idi(&mut self, name: &str, offset: u32, decl: &ElementDeclaration<CustomElementData>) {
5412 self.open_element_with_id(&hash_string_with_offset(name, offset, 0));
5413 self.configure_open_element(decl);
5414 }
5415
5416 fn debug_get_config_type_label(config_type: ElementConfigType) -> (&'static str, Color) {
5417 match config_type {
5418 ElementConfigType::Shared => ("Shared", Color::rgba(243.0, 134.0, 48.0, 255.0)),
5419 ElementConfigType::Text => ("Text", Color::rgba(105.0, 210.0, 231.0, 255.0)),
5420 ElementConfigType::Aspect => ("Aspect", Color::rgba(101.0, 149.0, 194.0, 255.0)),
5421 ElementConfigType::Image => ("Image", Color::rgba(121.0, 189.0, 154.0, 255.0)),
5422 ElementConfigType::Floating => ("Floating", Color::rgba(250.0, 105.0, 0.0, 255.0)),
5423 ElementConfigType::Clip => ("Overflow", Color::rgba(242.0, 196.0, 90.0, 255.0)),
5424 ElementConfigType::Border => ("Border", Color::rgba(108.0, 91.0, 123.0, 255.0)),
5425 ElementConfigType::Custom => ("Custom", Color::rgba(11.0, 72.0, 107.0, 255.0)),
5426 ElementConfigType::TextInput => ("TextInput", Color::rgba(52.0, 152.0, 219.0, 255.0)),
5427 }
5428 }
5429
5430 fn render_debug_layout_sizing(&mut self, sizing: SizingAxis, config_index: usize) {
5432 let label = match sizing.type_ {
5433 SizingType::Fit => "FIT",
5434 SizingType::Grow => "GROW",
5435 SizingType::Percent => "PERCENT",
5436 SizingType::Fixed => "FIXED",
5437 };
5439 self.debug_text(label, config_index);
5440 if matches!(sizing.type_, SizingType::Grow | SizingType::Fit | SizingType::Fixed) {
5441 self.debug_text("(", config_index);
5442 if sizing.min_max.min != 0.0 {
5443 self.debug_text("min: ", config_index);
5444 self.debug_int_text(sizing.min_max.min, config_index);
5445 if sizing.min_max.max != MAXFLOAT {
5446 self.debug_text(", ", config_index);
5447 }
5448 }
5449 if sizing.min_max.max != MAXFLOAT {
5450 self.debug_text("max: ", config_index);
5451 self.debug_int_text(sizing.min_max.max, config_index);
5452 }
5453 self.debug_text(")", config_index);
5454 } else if sizing.type_ == SizingType::Percent {
5455 self.debug_text("(", config_index);
5456 self.debug_int_text(sizing.percent * 100.0, config_index);
5457 self.debug_text("%)", config_index);
5458 }
5459 }
5460
5461 fn render_debug_view_element_config_header(
5463 &mut self,
5464 element_id_string: StringId,
5465 config_type: ElementConfigType,
5466 _info_title_config: usize,
5467 ) {
5468 let (label, label_color) = Self::debug_get_config_type_label(config_type);
5469 self.render_debug_view_category_header(label, label_color, element_id_string);
5470 }
5471
5472 fn render_debug_view_category_header(
5474 &mut self,
5475 label: &str,
5476 label_color: Color,
5477 element_id_string: StringId,
5478 ) {
5479 let bg = Color::rgba(label_color.r, label_color.g, label_color.b, 90.0);
5480 self.debug_open(&ElementDeclaration {
5481 layout: LayoutConfig {
5482 sizing: SizingConfig {
5483 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
5484 ..Default::default()
5485 },
5486 padding: PaddingConfig {
5487 left: Self::DEBUG_VIEW_OUTER_PADDING,
5488 right: Self::DEBUG_VIEW_OUTER_PADDING,
5489 top: Self::DEBUG_VIEW_OUTER_PADDING,
5490 bottom: Self::DEBUG_VIEW_OUTER_PADDING,
5491 },
5492 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
5493 ..Default::default()
5494 },
5495 ..Default::default()
5496 });
5497 {
5498 self.debug_open(&ElementDeclaration {
5500 layout: LayoutConfig {
5501 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
5502 ..Default::default()
5503 },
5504 background_color: bg,
5505 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
5506 border: BorderConfig {
5507 color: label_color,
5508 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
5509 ..Default::default()
5510 },
5511 ..Default::default()
5512 });
5513 {
5514 let tc = self.store_text_element_config(TextConfig {
5515 color: Self::DEBUG_COLOR_4,
5516 font_size: 16,
5517 ..Default::default()
5518 });
5519 self.debug_raw_text(label, tc);
5520 }
5521 self.close_element();
5522 self.debug_open(&ElementDeclaration {
5524 layout: LayoutConfig {
5525 sizing: SizingConfig {
5526 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
5527 ..Default::default()
5528 },
5529 ..Default::default()
5530 },
5531 ..Default::default()
5532 });
5533 self.close_element();
5534 let tc = self.store_text_element_config(TextConfig {
5536 color: Self::DEBUG_COLOR_3,
5537 font_size: 16,
5538 wrap_mode: WrapMode::None,
5539 ..Default::default()
5540 });
5541 if !element_id_string.is_empty() {
5542 self.debug_raw_text(element_id_string.as_str(), tc);
5543 }
5544 }
5545 self.close_element();
5546 }
5547
5548 fn render_debug_view_color(&mut self, color: Color, config_index: usize) {
5550 self.debug_open(&ElementDeclaration {
5551 layout: LayoutConfig {
5552 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
5553 ..Default::default()
5554 },
5555 ..Default::default()
5556 });
5557 {
5558 self.debug_text("{ r: ", config_index);
5559 self.debug_int_text(color.r, config_index);
5560 self.debug_text(", g: ", config_index);
5561 self.debug_int_text(color.g, config_index);
5562 self.debug_text(", b: ", config_index);
5563 self.debug_int_text(color.b, config_index);
5564 self.debug_text(", a: ", config_index);
5565 self.debug_int_text(color.a, config_index);
5566 self.debug_text(" }", config_index);
5567 self.debug_open(&ElementDeclaration {
5569 layout: LayoutConfig {
5570 sizing: SizingConfig {
5571 width: SizingAxis {
5572 type_: SizingType::Fixed,
5573 min_max: SizingMinMax { min: 10.0, max: 10.0 },
5574 ..Default::default()
5575 },
5576 ..Default::default()
5577 },
5578 ..Default::default()
5579 },
5580 ..Default::default()
5581 });
5582 self.close_element();
5583 let swatch_size = Self::DEBUG_VIEW_ROW_HEIGHT - 8.0;
5585 self.debug_open(&ElementDeclaration {
5586 layout: LayoutConfig {
5587 sizing: SizingConfig {
5588 width: SizingAxis {
5589 type_: SizingType::Fixed,
5590 min_max: SizingMinMax { min: swatch_size, max: swatch_size },
5591 ..Default::default()
5592 },
5593 height: SizingAxis {
5594 type_: SizingType::Fixed,
5595 min_max: SizingMinMax { min: swatch_size, max: swatch_size },
5596 ..Default::default()
5597 },
5598 },
5599 ..Default::default()
5600 },
5601 background_color: color,
5602 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
5603 border: BorderConfig {
5604 color: Self::DEBUG_COLOR_4,
5605 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
5606 ..Default::default()
5607 },
5608 ..Default::default()
5609 });
5610 self.close_element();
5611 }
5612 self.close_element();
5613 }
5614
5615 fn render_debug_view_corner_radius(&mut self, cr: CornerRadius, config_index: usize) {
5617 self.debug_open(&ElementDeclaration {
5618 layout: LayoutConfig {
5619 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
5620 ..Default::default()
5621 },
5622 ..Default::default()
5623 });
5624 {
5625 self.debug_text("{ topLeft: ", config_index);
5626 self.debug_int_text(cr.top_left, config_index);
5627 self.debug_text(", topRight: ", config_index);
5628 self.debug_int_text(cr.top_right, config_index);
5629 self.debug_text(", bottomLeft: ", config_index);
5630 self.debug_int_text(cr.bottom_left, config_index);
5631 self.debug_text(", bottomRight: ", config_index);
5632 self.debug_int_text(cr.bottom_right, config_index);
5633 self.debug_text(" }", config_index);
5634 }
5635 self.close_element();
5636 }
5637
5638 fn render_debug_shader_uniform_value(&mut self, value: &crate::shaders::ShaderUniformValue, config_index: usize) {
5640 use crate::shaders::ShaderUniformValue;
5641 match value {
5642 ShaderUniformValue::Float(v) => {
5643 self.debug_float_text(*v, config_index);
5644 }
5645 ShaderUniformValue::Vec2(v) => {
5646 self.debug_text("(", config_index);
5647 self.debug_float_text(v[0], config_index);
5648 self.debug_text(", ", config_index);
5649 self.debug_float_text(v[1], config_index);
5650 self.debug_text(")", config_index);
5651 }
5652 ShaderUniformValue::Vec3(v) => {
5653 self.debug_text("(", config_index);
5654 self.debug_float_text(v[0], config_index);
5655 self.debug_text(", ", config_index);
5656 self.debug_float_text(v[1], config_index);
5657 self.debug_text(", ", config_index);
5658 self.debug_float_text(v[2], config_index);
5659 self.debug_text(")", config_index);
5660 }
5661 ShaderUniformValue::Vec4(v) => {
5662 self.debug_text("(", config_index);
5663 self.debug_float_text(v[0], config_index);
5664 self.debug_text(", ", config_index);
5665 self.debug_float_text(v[1], config_index);
5666 self.debug_text(", ", config_index);
5667 self.debug_float_text(v[2], config_index);
5668 self.debug_text(", ", config_index);
5669 self.debug_float_text(v[3], config_index);
5670 self.debug_text(")", config_index);
5671 }
5672 ShaderUniformValue::Int(v) => {
5673 self.debug_int_text(*v as f32, config_index);
5674 }
5675 ShaderUniformValue::Mat4(_) => {
5676 self.debug_text("[mat4]", config_index);
5677 }
5678 }
5679 }
5680
5681 fn render_debug_layout_elements_list(
5683 &mut self,
5684 initial_roots_length: usize,
5685 highlighted_row: i32,
5686 ) -> (i32, i32) {
5687 let row_height = Self::DEBUG_VIEW_ROW_HEIGHT;
5688 let indent_width = Self::DEBUG_VIEW_INDENT_WIDTH;
5689 let mut row_count: i32 = 0;
5690 let mut selected_element_row_index: i32 = 0;
5691 let mut highlighted_element_id: u32 = 0;
5692
5693 let scroll_item_layout = LayoutConfig {
5694 sizing: SizingConfig {
5695 height: SizingAxis {
5696 type_: SizingType::Fixed,
5697 min_max: SizingMinMax { min: row_height, max: row_height },
5698 ..Default::default()
5699 },
5700 ..Default::default()
5701 },
5702 child_gap: 6,
5703 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
5704 ..Default::default()
5705 };
5706
5707 let name_text_config = TextConfig {
5708 color: Self::DEBUG_COLOR_4,
5709 font_size: 16,
5710 wrap_mode: WrapMode::None,
5711 ..Default::default()
5712 };
5713
5714 for root_index in 0..initial_roots_length {
5715 let mut dfs_buffer: Vec<i32> = Vec::new();
5716 let root_layout_index = self.layout_element_tree_roots[root_index].layout_element_index;
5717 dfs_buffer.push(root_layout_index);
5718 let mut visited: Vec<bool> = vec![false; self.layout_elements.len()];
5719
5720 if root_index > 0 {
5722 self.debug_open_idi("Ply__DebugView_EmptyRowOuter", root_index as u32, &ElementDeclaration {
5723 layout: LayoutConfig {
5724 sizing: SizingConfig {
5725 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
5726 ..Default::default()
5727 },
5728 padding: PaddingConfig { left: indent_width / 2, right: 0, top: 0, bottom: 0 },
5729 ..Default::default()
5730 },
5731 ..Default::default()
5732 });
5733 {
5734 self.debug_open_idi("Ply__DebugView_EmptyRow", root_index as u32, &ElementDeclaration {
5735 layout: LayoutConfig {
5736 sizing: SizingConfig {
5737 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
5738 height: SizingAxis {
5739 type_: SizingType::Fixed,
5740 min_max: SizingMinMax { min: row_height, max: row_height },
5741 ..Default::default()
5742 },
5743 },
5744 ..Default::default()
5745 },
5746 border: BorderConfig {
5747 color: Self::DEBUG_COLOR_3,
5748 width: BorderWidth { top: 1, ..Default::default() },
5749 ..Default::default()
5750 },
5751 ..Default::default()
5752 });
5753 self.close_element();
5754 }
5755 self.close_element();
5756 row_count += 1;
5757 }
5758
5759 while !dfs_buffer.is_empty() {
5760 let current_element_index = *dfs_buffer.last().unwrap() as usize;
5761 let depth = dfs_buffer.len() - 1;
5762
5763 if visited[depth] {
5764 let is_text = self.element_has_config(current_element_index, ElementConfigType::Text);
5766 let children_len = self.layout_elements[current_element_index].children_length;
5767 if !is_text && children_len > 0 {
5768 self.close_element();
5769 self.close_element();
5770 self.close_element();
5771 }
5772 dfs_buffer.pop();
5773 continue;
5774 }
5775
5776 if highlighted_row == row_count {
5778 if self.pointer_info.state == PointerDataInteractionState::PressedThisFrame {
5779 let elem_id = self.layout_elements[current_element_index].id;
5780 if self.debug_selected_element_id == elem_id {
5781 self.debug_selected_element_id = 0; } else {
5783 self.debug_selected_element_id = elem_id;
5784 }
5785 }
5786 highlighted_element_id = self.layout_elements[current_element_index].id;
5787 }
5788
5789 visited[depth] = true;
5790 let current_elem_id = self.layout_elements[current_element_index].id;
5791
5792 let bounding_box = self.layout_element_map
5794 .get(¤t_elem_id)
5795 .map(|item| item.bounding_box)
5796 .unwrap_or_default();
5797 let collision = self.layout_element_map
5798 .get(¤t_elem_id)
5799 .map(|item| item.collision)
5800 .unwrap_or(false);
5801 let collapsed = self.layout_element_map
5802 .get(¤t_elem_id)
5803 .map(|item| item.collapsed)
5804 .unwrap_or(false);
5805
5806 let offscreen = self.element_is_offscreen(&bounding_box);
5807
5808 if self.debug_selected_element_id == current_elem_id {
5809 selected_element_row_index = row_count;
5810 }
5811
5812 let row_bg = if self.debug_selected_element_id == current_elem_id {
5814 Color::rgba(217.0, 91.0, 67.0, 40.0) } else {
5816 Color::rgba(0.0, 0.0, 0.0, 0.0)
5817 };
5818 self.debug_open_idi("Ply__DebugView_ElementOuter", current_elem_id, &ElementDeclaration {
5819 layout: scroll_item_layout,
5820 background_color: row_bg,
5821 ..Default::default()
5822 });
5823 {
5824 let is_text = self.element_has_config(current_element_index, ElementConfigType::Text);
5825 let children_len = self.layout_elements[current_element_index].children_length;
5826
5827 if !is_text && children_len > 0 {
5829 self.debug_open_idi("Ply__DebugView_CollapseElement", current_elem_id, &ElementDeclaration {
5831 layout: LayoutConfig {
5832 sizing: SizingConfig {
5833 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
5834 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
5835 },
5836 child_alignment: ChildAlignmentConfig { x: AlignX::CenterX, y: AlignY::CenterY },
5837 ..Default::default()
5838 },
5839 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
5840 border: BorderConfig {
5841 color: Self::DEBUG_COLOR_3,
5842 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
5843 ..Default::default()
5844 },
5845 ..Default::default()
5846 });
5847 {
5848 let tc = self.store_text_element_config(TextConfig {
5849 color: Self::DEBUG_COLOR_4,
5850 font_size: 16,
5851 ..Default::default()
5852 });
5853 if collapsed {
5854 self.debug_text("+", tc);
5855 } else {
5856 self.debug_text("-", tc);
5857 }
5858 }
5859 self.close_element();
5860 } else {
5861 self.debug_open(&ElementDeclaration {
5863 layout: LayoutConfig {
5864 sizing: SizingConfig {
5865 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
5866 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 16.0, max: 16.0 }, ..Default::default() },
5867 },
5868 child_alignment: ChildAlignmentConfig { x: AlignX::CenterX, y: AlignY::CenterY },
5869 ..Default::default()
5870 },
5871 ..Default::default()
5872 });
5873 {
5874 self.debug_open(&ElementDeclaration {
5875 layout: LayoutConfig {
5876 sizing: SizingConfig {
5877 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 8.0, max: 8.0 }, ..Default::default() },
5878 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 8.0, max: 8.0 }, ..Default::default() },
5879 },
5880 ..Default::default()
5881 },
5882 background_color: Self::DEBUG_COLOR_3,
5883 corner_radius: CornerRadius { top_left: 2.0, top_right: 2.0, bottom_left: 2.0, bottom_right: 2.0 },
5884 ..Default::default()
5885 });
5886 self.close_element();
5887 }
5888 self.close_element();
5889 }
5890
5891 if collision {
5893 self.debug_open(&ElementDeclaration {
5894 layout: LayoutConfig {
5895 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
5896 ..Default::default()
5897 },
5898 border: BorderConfig {
5899 color: Color::rgba(177.0, 147.0, 8.0, 255.0),
5900 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
5901 ..Default::default()
5902 },
5903 ..Default::default()
5904 });
5905 {
5906 let tc = self.store_text_element_config(TextConfig {
5907 color: Self::DEBUG_COLOR_3,
5908 font_size: 16,
5909 ..Default::default()
5910 });
5911 self.debug_text("Duplicate ID", tc);
5912 }
5913 self.close_element();
5914 }
5915
5916 if offscreen {
5918 self.debug_open(&ElementDeclaration {
5919 layout: LayoutConfig {
5920 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
5921 ..Default::default()
5922 },
5923 border: BorderConfig {
5924 color: Self::DEBUG_COLOR_3,
5925 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
5926 ..Default::default()
5927 },
5928 ..Default::default()
5929 });
5930 {
5931 let tc = self.store_text_element_config(TextConfig {
5932 color: Self::DEBUG_COLOR_3,
5933 font_size: 16,
5934 ..Default::default()
5935 });
5936 self.debug_text("Offscreen", tc);
5937 }
5938 self.close_element();
5939 }
5940
5941 let id_string = if current_element_index < self.layout_element_id_strings.len() {
5943 self.layout_element_id_strings[current_element_index].clone()
5944 } else {
5945 StringId::empty()
5946 };
5947 if !id_string.is_empty() {
5948 let tc = if offscreen {
5949 self.store_text_element_config(TextConfig {
5950 color: Self::DEBUG_COLOR_3,
5951 font_size: 16,
5952 ..Default::default()
5953 })
5954 } else {
5955 self.store_text_element_config(name_text_config.clone())
5956 };
5957 self.debug_raw_text(id_string.as_str(), tc);
5958 }
5959
5960 let configs_start = self.layout_elements[current_element_index].element_configs.start;
5962 let configs_len = self.layout_elements[current_element_index].element_configs.length;
5963 for ci in 0..configs_len {
5964 let ec = self.element_configs[configs_start + ci as usize];
5965 if ec.config_type == ElementConfigType::Shared {
5966 let shared = self.shared_element_configs[ec.config_index];
5967 let label_color = Color::rgba(243.0, 134.0, 48.0, 90.0);
5968 if shared.background_color.a > 0.0 {
5969 self.debug_open(&ElementDeclaration {
5970 layout: LayoutConfig {
5971 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
5972 ..Default::default()
5973 },
5974 background_color: label_color,
5975 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
5976 border: BorderConfig {
5977 color: label_color,
5978 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
5979 ..Default::default()
5980 },
5981 ..Default::default()
5982 });
5983 {
5984 let tc = self.store_text_element_config(TextConfig {
5985 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
5986 font_size: 16,
5987 ..Default::default()
5988 });
5989 self.debug_text("Color", tc);
5990 }
5991 self.close_element();
5992 }
5993 if shared.corner_radius.bottom_left > 0.0 {
5994 let radius_color = Color::rgba(26.0, 188.0, 156.0, 90.0);
5995 self.debug_open(&ElementDeclaration {
5996 layout: LayoutConfig {
5997 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
5998 ..Default::default()
5999 },
6000 background_color: radius_color,
6001 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
6002 border: BorderConfig {
6003 color: radius_color,
6004 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
6005 ..Default::default()
6006 },
6007 ..Default::default()
6008 });
6009 {
6010 let tc = self.store_text_element_config(TextConfig {
6011 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
6012 font_size: 16,
6013 ..Default::default()
6014 });
6015 self.debug_text("Radius", tc);
6016 }
6017 self.close_element();
6018 }
6019 continue;
6020 }
6021 let (label, label_color) = Self::debug_get_config_type_label(ec.config_type);
6022 let bg = Color::rgba(label_color.r, label_color.g, label_color.b, 90.0);
6023 self.debug_open(&ElementDeclaration {
6024 layout: LayoutConfig {
6025 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
6026 ..Default::default()
6027 },
6028 background_color: bg,
6029 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
6030 border: BorderConfig {
6031 color: label_color,
6032 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
6033 ..Default::default()
6034 },
6035 ..Default::default()
6036 });
6037 {
6038 let tc = self.store_text_element_config(TextConfig {
6039 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
6040 font_size: 16,
6041 ..Default::default()
6042 });
6043 self.debug_text(label, tc);
6044 }
6045 self.close_element();
6046 }
6047
6048 let has_shaders = self.element_shaders.get(current_element_index)
6050 .map_or(false, |s| !s.is_empty());
6051 if has_shaders {
6052 let badge_color = Color::rgba(155.0, 89.0, 182.0, 90.0);
6053 self.debug_open(&ElementDeclaration {
6054 layout: LayoutConfig {
6055 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
6056 ..Default::default()
6057 },
6058 background_color: badge_color,
6059 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
6060 border: BorderConfig {
6061 color: badge_color,
6062 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
6063 ..Default::default()
6064 },
6065 ..Default::default()
6066 });
6067 {
6068 let tc = self.store_text_element_config(TextConfig {
6069 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
6070 font_size: 16,
6071 ..Default::default()
6072 });
6073 self.debug_text("Shader", tc);
6074 }
6075 self.close_element();
6076 }
6077
6078 let has_effects = self.element_effects.get(current_element_index)
6080 .map_or(false, |e| !e.is_empty());
6081 if has_effects {
6082 let badge_color = Color::rgba(155.0, 89.0, 182.0, 90.0);
6083 self.debug_open(&ElementDeclaration {
6084 layout: LayoutConfig {
6085 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
6086 ..Default::default()
6087 },
6088 background_color: badge_color,
6089 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
6090 border: BorderConfig {
6091 color: badge_color,
6092 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
6093 ..Default::default()
6094 },
6095 ..Default::default()
6096 });
6097 {
6098 let tc = self.store_text_element_config(TextConfig {
6099 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
6100 font_size: 16,
6101 ..Default::default()
6102 });
6103 self.debug_text("Effect", tc);
6104 }
6105 self.close_element();
6106 }
6107
6108 let has_visual_rot = self.element_visual_rotations.get(current_element_index)
6110 .map_or(false, |r| r.is_some());
6111 if has_visual_rot {
6112 let badge_color = Color::rgba(155.0, 89.0, 182.0, 90.0);
6113 self.debug_open(&ElementDeclaration {
6114 layout: LayoutConfig {
6115 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
6116 ..Default::default()
6117 },
6118 background_color: badge_color,
6119 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
6120 border: BorderConfig {
6121 color: badge_color,
6122 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
6123 ..Default::default()
6124 },
6125 ..Default::default()
6126 });
6127 {
6128 let tc = self.store_text_element_config(TextConfig {
6129 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
6130 font_size: 16,
6131 ..Default::default()
6132 });
6133 self.debug_text("VisualRot", tc);
6134 }
6135 self.close_element();
6136 }
6137
6138 let has_shape_rot = self.element_shape_rotations.get(current_element_index)
6140 .map_or(false, |r| r.is_some());
6141 if has_shape_rot {
6142 let badge_color = Color::rgba(26.0, 188.0, 156.0, 90.0);
6143 self.debug_open(&ElementDeclaration {
6144 layout: LayoutConfig {
6145 padding: PaddingConfig { left: 8, right: 8, top: 2, bottom: 2 },
6146 ..Default::default()
6147 },
6148 background_color: badge_color,
6149 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
6150 border: BorderConfig {
6151 color: badge_color,
6152 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
6153 ..Default::default()
6154 },
6155 ..Default::default()
6156 });
6157 {
6158 let tc = self.store_text_element_config(TextConfig {
6159 color: if offscreen { Self::DEBUG_COLOR_3 } else { Self::DEBUG_COLOR_4 },
6160 font_size: 16,
6161 ..Default::default()
6162 });
6163 self.debug_text("ShapeRot", tc);
6164 }
6165 self.close_element();
6166 }
6167 }
6168 self.close_element(); let is_text = self.element_has_config(current_element_index, ElementConfigType::Text);
6172 let children_len = self.layout_elements[current_element_index].children_length;
6173 if is_text {
6174 row_count += 1;
6175 let text_data_idx = self.layout_elements[current_element_index].text_data_index;
6176 let text_content = if text_data_idx >= 0 {
6177 self.text_element_data[text_data_idx as usize].text.clone()
6178 } else {
6179 String::new()
6180 };
6181 let raw_tc_idx = if offscreen {
6182 self.store_text_element_config(TextConfig {
6183 color: Self::DEBUG_COLOR_3,
6184 font_size: 16,
6185 ..Default::default()
6186 })
6187 } else {
6188 self.store_text_element_config(name_text_config.clone())
6189 };
6190 self.debug_open(&ElementDeclaration {
6191 layout: LayoutConfig {
6192 sizing: SizingConfig {
6193 height: SizingAxis {
6194 type_: SizingType::Fixed,
6195 min_max: SizingMinMax { min: row_height, max: row_height },
6196 ..Default::default()
6197 },
6198 ..Default::default()
6199 },
6200 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
6201 ..Default::default()
6202 },
6203 ..Default::default()
6204 });
6205 {
6206 self.debug_open(&ElementDeclaration {
6208 layout: LayoutConfig {
6209 sizing: SizingConfig {
6210 width: SizingAxis {
6211 type_: SizingType::Fixed,
6212 min_max: SizingMinMax {
6213 min: (indent_width + 16) as f32,
6214 max: (indent_width + 16) as f32,
6215 },
6216 ..Default::default()
6217 },
6218 ..Default::default()
6219 },
6220 ..Default::default()
6221 },
6222 ..Default::default()
6223 });
6224 self.close_element();
6225 self.debug_text("\"", raw_tc_idx);
6226 if text_content.len() > 40 {
6227 let mut end = 40;
6228 while !text_content.is_char_boundary(end) { end -= 1; }
6229 self.debug_raw_text(&text_content[..end], raw_tc_idx);
6230 self.debug_text("...", raw_tc_idx);
6231 } else if !text_content.is_empty() {
6232 self.debug_raw_text(&text_content, raw_tc_idx);
6233 }
6234 self.debug_text("\"", raw_tc_idx);
6235 }
6236 self.close_element();
6237 } else if children_len > 0 {
6238 self.open_element();
6240 self.configure_open_element(&ElementDeclaration {
6241 layout: LayoutConfig {
6242 padding: PaddingConfig { left: 8, ..Default::default() },
6243 ..Default::default()
6244 },
6245 ..Default::default()
6246 });
6247 self.open_element();
6248 self.configure_open_element(&ElementDeclaration {
6249 layout: LayoutConfig {
6250 padding: PaddingConfig { left: indent_width, ..Default::default() },
6251 ..Default::default()
6252 },
6253 border: BorderConfig {
6254 color: Self::DEBUG_COLOR_3,
6255 width: BorderWidth { left: 1, ..Default::default() },
6256 ..Default::default()
6257 },
6258 ..Default::default()
6259 });
6260 self.open_element();
6261 self.configure_open_element(&ElementDeclaration {
6262 layout: LayoutConfig {
6263 layout_direction: LayoutDirection::TopToBottom,
6264 ..Default::default()
6265 },
6266 ..Default::default()
6267 });
6268 }
6269
6270 row_count += 1;
6271
6272 if !is_text && !collapsed {
6274 let children_start = self.layout_elements[current_element_index].children_start;
6275 let children_length = self.layout_elements[current_element_index].children_length as usize;
6276 for i in (0..children_length).rev() {
6277 let child_idx = self.layout_element_children[children_start + i];
6278 dfs_buffer.push(child_idx);
6279 while visited.len() <= dfs_buffer.len() {
6281 visited.push(false);
6282 }
6283 visited[dfs_buffer.len() - 1] = false;
6284 }
6285 }
6286 }
6287 }
6288
6289 if self.pointer_info.state == PointerDataInteractionState::PressedThisFrame {
6291 let collapse_base_id = hash_string("Ply__DebugView_CollapseElement", 0).base_id;
6292 for i in (0..self.pointer_over_ids.len()).rev() {
6293 let element_id = self.pointer_over_ids[i].clone();
6294 if element_id.base_id == collapse_base_id {
6295 if let Some(item) = self.layout_element_map.get_mut(&element_id.offset) {
6296 item.collapsed = !item.collapsed;
6297 }
6298 break;
6299 }
6300 }
6301 }
6302
6303 let highlight_target = if self.debug_selected_element_id != 0 {
6306 self.debug_selected_element_id
6307 } else {
6308 highlighted_element_id
6309 };
6310 if highlight_target != 0 {
6311 self.debug_open_id("Ply__DebugView_ElementHighlight", &ElementDeclaration {
6312 layout: LayoutConfig {
6313 sizing: SizingConfig {
6314 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6315 height: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6316 },
6317 ..Default::default()
6318 },
6319 floating: FloatingConfig {
6320 parent_id: highlight_target,
6321 z_index: 32767,
6322 pointer_capture_mode: PointerCaptureMode::Passthrough,
6323 attach_to: FloatingAttachToElement::ElementWithId,
6324 ..Default::default()
6325 },
6326 ..Default::default()
6327 });
6328 {
6329 self.debug_open_id("Ply__DebugView_ElementHighlightRectangle", &ElementDeclaration {
6330 layout: LayoutConfig {
6331 sizing: SizingConfig {
6332 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6333 height: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6334 },
6335 ..Default::default()
6336 },
6337 background_color: Self::DEBUG_HIGHLIGHT_COLOR,
6338 ..Default::default()
6339 });
6340 self.close_element();
6341 }
6342 self.close_element();
6343 }
6344
6345 (row_count, selected_element_row_index)
6346 }
6347
6348 fn render_debug_view(&mut self) {
6350 let initial_roots_length = self.layout_element_tree_roots.len();
6351 let initial_elements_length = self.layout_elements.len();
6352 let row_height = Self::DEBUG_VIEW_ROW_HEIGHT;
6353 let outer_padding = Self::DEBUG_VIEW_OUTER_PADDING;
6354 let debug_width = Self::DEBUG_VIEW_WIDTH;
6355
6356 let info_text_config = self.store_text_element_config(TextConfig {
6357 color: Self::DEBUG_COLOR_4,
6358 font_size: 16,
6359 wrap_mode: WrapMode::None,
6360 ..Default::default()
6361 });
6362 let info_title_config = self.store_text_element_config(TextConfig {
6363 color: Self::DEBUG_COLOR_3,
6364 font_size: 16,
6365 wrap_mode: WrapMode::None,
6366 ..Default::default()
6367 });
6368
6369 let scroll_id = hash_string("Ply__DebugViewOuterScrollPane", 0);
6371 let mut scroll_y_offset: f32 = 0.0;
6372 let detail_panel_height = if self.debug_selected_element_id != 0 { 300.0 } else { 0.0 };
6374 let mut pointer_in_debug_view = self.pointer_info.position.y < self.layout_dimensions.height - detail_panel_height;
6375 for scd in &self.scroll_container_datas {
6376 if scd.element_id == scroll_id.id {
6377 if !self.external_scroll_handling_enabled {
6378 scroll_y_offset = scd.scroll_position.y;
6379 } else {
6380 pointer_in_debug_view = self.pointer_info.position.y + scd.scroll_position.y
6381 < self.layout_dimensions.height - detail_panel_height;
6382 }
6383 break;
6384 }
6385 }
6386
6387 let highlighted_row = if pointer_in_debug_view {
6388 ((self.pointer_info.position.y - scroll_y_offset) / row_height) as i32 - 1
6389 } else {
6390 -1
6391 };
6392 let highlighted_row = if self.pointer_info.position.x < self.layout_dimensions.width - debug_width {
6393 -1
6394 } else {
6395 highlighted_row
6396 };
6397
6398 self.debug_open_id("Ply__DebugView", &ElementDeclaration {
6400 layout: LayoutConfig {
6401 sizing: SizingConfig {
6402 width: SizingAxis {
6403 type_: SizingType::Fixed,
6404 min_max: SizingMinMax { min: debug_width, max: debug_width },
6405 ..Default::default()
6406 },
6407 height: SizingAxis {
6408 type_: SizingType::Fixed,
6409 min_max: SizingMinMax { min: self.layout_dimensions.height, max: self.layout_dimensions.height },
6410 ..Default::default()
6411 },
6412 },
6413 layout_direction: LayoutDirection::TopToBottom,
6414 ..Default::default()
6415 },
6416 floating: FloatingConfig {
6417 z_index: 32765,
6418 attach_points: FloatingAttachPoints {
6419 element_x: AlignX::Right,
6420 element_y: AlignY::CenterY,
6421 parent_x: AlignX::Right,
6422 parent_y: AlignY::CenterY,
6423 },
6424 attach_to: FloatingAttachToElement::Root,
6425 clip_to: FloatingClipToElement::AttachedParent,
6426 ..Default::default()
6427 },
6428 border: BorderConfig {
6429 color: Self::DEBUG_COLOR_3,
6430 width: BorderWidth { bottom: 1, ..Default::default() },
6431 ..Default::default()
6432 },
6433 ..Default::default()
6434 });
6435 {
6436 self.debug_open(&ElementDeclaration {
6438 layout: LayoutConfig {
6439 sizing: SizingConfig {
6440 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6441 height: SizingAxis {
6442 type_: SizingType::Fixed,
6443 min_max: SizingMinMax { min: row_height, max: row_height },
6444 ..Default::default()
6445 },
6446 },
6447 padding: PaddingConfig { left: outer_padding, right: outer_padding, top: 0, bottom: 0 },
6448 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
6449 ..Default::default()
6450 },
6451 background_color: Self::DEBUG_COLOR_2,
6452 ..Default::default()
6453 });
6454 {
6455 self.debug_text("Ply Debug Tools", info_text_config);
6456 self.debug_open(&ElementDeclaration {
6458 layout: LayoutConfig {
6459 sizing: SizingConfig {
6460 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6461 ..Default::default()
6462 },
6463 ..Default::default()
6464 },
6465 ..Default::default()
6466 });
6467 self.close_element();
6468 let close_size = row_height - 10.0;
6470 self.debug_open_id("Ply__DebugView_CloseButton", &ElementDeclaration {
6471 layout: LayoutConfig {
6472 sizing: SizingConfig {
6473 width: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: close_size, max: close_size }, ..Default::default() },
6474 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: close_size, max: close_size }, ..Default::default() },
6475 },
6476 child_alignment: ChildAlignmentConfig { x: AlignX::CenterX, y: AlignY::CenterY },
6477 ..Default::default()
6478 },
6479 background_color: Color::rgba(217.0, 91.0, 67.0, 80.0),
6480 corner_radius: CornerRadius { top_left: 4.0, top_right: 4.0, bottom_left: 4.0, bottom_right: 4.0 },
6481 border: BorderConfig {
6482 color: Color::rgba(217.0, 91.0, 67.0, 255.0),
6483 width: BorderWidth { left: 1, right: 1, top: 1, bottom: 1, between_children: 0 },
6484 ..Default::default()
6485 },
6486 ..Default::default()
6487 });
6488 {
6489 let tc = self.store_text_element_config(TextConfig {
6490 color: Self::DEBUG_COLOR_4,
6491 font_size: 16,
6492 ..Default::default()
6493 });
6494 self.debug_text("x", tc);
6495 }
6496 self.close_element();
6497 }
6498 self.close_element();
6499
6500 self.debug_open(&ElementDeclaration {
6502 layout: LayoutConfig {
6503 sizing: SizingConfig {
6504 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6505 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 1.0, max: 1.0 }, ..Default::default() },
6506 },
6507 ..Default::default()
6508 },
6509 background_color: Self::DEBUG_COLOR_3,
6510 ..Default::default()
6511 });
6512 self.close_element();
6513
6514 self.open_element_with_id(&scroll_id);
6516 self.configure_open_element(&ElementDeclaration {
6517 layout: LayoutConfig {
6518 sizing: SizingConfig {
6519 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6520 height: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6521 },
6522 ..Default::default()
6523 },
6524 clip: ClipConfig {
6525 horizontal: true,
6526 vertical: true,
6527 scroll_x: true,
6528 scroll_y: true,
6529 child_offset: self.get_scroll_offset(),
6530 },
6531 ..Default::default()
6532 });
6533 {
6534 let alt_bg = if (initial_elements_length + initial_roots_length) & 1 == 0 {
6535 Self::DEBUG_COLOR_2
6536 } else {
6537 Self::DEBUG_COLOR_1
6538 };
6539 self.debug_open(&ElementDeclaration {
6541 layout: LayoutConfig {
6542 sizing: SizingConfig {
6543 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6544 ..Default::default() },
6546 padding: PaddingConfig {
6547 left: outer_padding,
6548 right: outer_padding,
6549 top: 0,
6550 bottom: 0,
6551 },
6552 layout_direction: LayoutDirection::TopToBottom,
6553 ..Default::default()
6554 },
6555 background_color: alt_bg,
6556 ..Default::default()
6557 });
6558 {
6559 let _layout_data = self.render_debug_layout_elements_list(
6560 initial_roots_length,
6561 highlighted_row,
6562 );
6563 }
6564 self.close_element(); }
6566 self.close_element(); self.debug_open(&ElementDeclaration {
6570 layout: LayoutConfig {
6571 sizing: SizingConfig {
6572 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6573 height: SizingAxis { type_: SizingType::Fixed, min_max: SizingMinMax { min: 1.0, max: 1.0 }, ..Default::default() },
6574 },
6575 ..Default::default()
6576 },
6577 background_color: Self::DEBUG_COLOR_3,
6578 ..Default::default()
6579 });
6580 self.close_element();
6581
6582 if self.debug_selected_element_id != 0 {
6584 self.render_debug_selected_element_panel(info_text_config, info_title_config);
6585 }
6586 }
6587 self.close_element(); if self.pointer_info.state == PointerDataInteractionState::PressedThisFrame {
6591 let close_base_id = hash_string("Ply__DebugView_CloseButton", 0).id;
6592 let header_base_id = hash_string("Ply__DebugView_LayoutConfigHeader", 0).id;
6593 for i in (0..self.pointer_over_ids.len()).rev() {
6594 let id = self.pointer_over_ids[i].id;
6595 if id == close_base_id {
6596 self.debug_mode_enabled = false;
6597 break;
6598 }
6599 if id == header_base_id {
6600 self.debug_selected_element_id = 0;
6601 break;
6602 }
6603 }
6604 }
6605 }
6606
6607 fn render_debug_selected_element_panel(
6609 &mut self,
6610 info_text_config: usize,
6611 info_title_config: usize,
6612 ) {
6613 let row_height = Self::DEBUG_VIEW_ROW_HEIGHT;
6614 let outer_padding = Self::DEBUG_VIEW_OUTER_PADDING;
6615 let attr_padding = PaddingConfig {
6616 left: outer_padding,
6617 right: outer_padding,
6618 top: 8,
6619 bottom: 8,
6620 };
6621
6622 let selected_id = self.debug_selected_element_id;
6623 let selected_item = match self.layout_element_map.get(&selected_id) {
6624 Some(item) => item.clone(),
6625 None => return,
6626 };
6627 let layout_elem_idx = selected_item.layout_element_index as usize;
6628 if layout_elem_idx >= self.layout_elements.len() {
6629 return;
6630 }
6631
6632 let layout_config_index = self.layout_elements[layout_elem_idx].layout_config_index;
6633 let layout_config = self.layout_configs[layout_config_index];
6634
6635 self.debug_open(&ElementDeclaration {
6636 layout: LayoutConfig {
6637 sizing: SizingConfig {
6638 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6639 height: SizingAxis {
6640 type_: SizingType::Fixed,
6641 min_max: SizingMinMax { min: 316.0, max: 316.0 },
6642 ..Default::default()
6643 },
6644 },
6645 layout_direction: LayoutDirection::TopToBottom,
6646 ..Default::default()
6647 },
6648 background_color: Self::DEBUG_COLOR_2,
6649 clip: ClipConfig {
6650 vertical: true,
6651 scroll_y: true,
6652 child_offset: self.get_scroll_offset(),
6653 ..Default::default()
6654 },
6655 border: BorderConfig {
6656 color: Self::DEBUG_COLOR_3,
6657 width: BorderWidth { between_children: 1, ..Default::default() },
6658 ..Default::default()
6659 },
6660 ..Default::default()
6661 });
6662 {
6663 self.debug_open_id("Ply__DebugView_LayoutConfigHeader", &ElementDeclaration {
6665 layout: LayoutConfig {
6666 sizing: SizingConfig {
6667 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6668 height: SizingAxis {
6669 type_: SizingType::Fixed,
6670 min_max: SizingMinMax { min: row_height + 8.0, max: row_height + 8.0 },
6671 ..Default::default()
6672 },
6673 },
6674 padding: PaddingConfig { left: outer_padding, right: outer_padding, top: 0, bottom: 0 },
6675 child_alignment: ChildAlignmentConfig { x: AlignX::Left, y: AlignY::CenterY },
6676 ..Default::default()
6677 },
6678 ..Default::default()
6679 });
6680 {
6681 self.debug_text("Layout Config", info_text_config);
6682 self.debug_open(&ElementDeclaration {
6684 layout: LayoutConfig {
6685 sizing: SizingConfig {
6686 width: SizingAxis { type_: SizingType::Grow, ..Default::default() },
6687 ..Default::default()
6688 },
6689 ..Default::default()
6690 },
6691 ..Default::default()
6692 });
6693 self.close_element();
6694 let sid = selected_item.element_id.string_id.clone();
6696 if !sid.is_empty() {
6697 self.debug_raw_text(sid.as_str(), info_title_config);
6698 if selected_item.element_id.offset != 0 {
6699 self.debug_text(" (", info_title_config);
6700 self.debug_int_text(selected_item.element_id.offset as f32, info_title_config);
6701 self.debug_text(")", info_title_config);
6702 }
6703 }
6704 }
6705 self.close_element();
6706
6707 self.debug_open(&ElementDeclaration {
6709 layout: LayoutConfig {
6710 padding: attr_padding,
6711 child_gap: 8,
6712 layout_direction: LayoutDirection::TopToBottom,
6713 ..Default::default()
6714 },
6715 ..Default::default()
6716 });
6717 {
6718 self.debug_text("Bounding Box", info_title_config);
6720 self.debug_open(&ElementDeclaration::default());
6721 {
6722 self.debug_text("{ x: ", info_text_config);
6723 self.debug_int_text(selected_item.bounding_box.x, info_text_config);
6724 self.debug_text(", y: ", info_text_config);
6725 self.debug_int_text(selected_item.bounding_box.y, info_text_config);
6726 self.debug_text(", width: ", info_text_config);
6727 self.debug_int_text(selected_item.bounding_box.width, info_text_config);
6728 self.debug_text(", height: ", info_text_config);
6729 self.debug_int_text(selected_item.bounding_box.height, info_text_config);
6730 self.debug_text(" }", info_text_config);
6731 }
6732 self.close_element();
6733
6734 self.debug_text("Layout Direction", info_title_config);
6736 if layout_config.layout_direction == LayoutDirection::TopToBottom {
6737 self.debug_text("TOP_TO_BOTTOM", info_text_config);
6738 } else {
6739 self.debug_text("LEFT_TO_RIGHT", info_text_config);
6740 }
6741
6742 self.debug_text("Sizing", info_title_config);
6744 self.debug_open(&ElementDeclaration::default());
6745 {
6746 self.debug_text("width: ", info_text_config);
6747 self.render_debug_layout_sizing(layout_config.sizing.width, info_text_config);
6748 }
6749 self.close_element();
6750 self.debug_open(&ElementDeclaration::default());
6751 {
6752 self.debug_text("height: ", info_text_config);
6753 self.render_debug_layout_sizing(layout_config.sizing.height, info_text_config);
6754 }
6755 self.close_element();
6756
6757 self.debug_text("Padding", info_title_config);
6759 self.debug_open_id("Ply__DebugViewElementInfoPadding", &ElementDeclaration::default());
6760 {
6761 self.debug_text("{ left: ", info_text_config);
6762 self.debug_int_text(layout_config.padding.left as f32, info_text_config);
6763 self.debug_text(", right: ", info_text_config);
6764 self.debug_int_text(layout_config.padding.right as f32, info_text_config);
6765 self.debug_text(", top: ", info_text_config);
6766 self.debug_int_text(layout_config.padding.top as f32, info_text_config);
6767 self.debug_text(", bottom: ", info_text_config);
6768 self.debug_int_text(layout_config.padding.bottom as f32, info_text_config);
6769 self.debug_text(" }", info_text_config);
6770 }
6771 self.close_element();
6772
6773 self.debug_text("Child Gap", info_title_config);
6775 self.debug_int_text(layout_config.child_gap as f32, info_text_config);
6776
6777 self.debug_text("Child Alignment", info_title_config);
6779 self.debug_open(&ElementDeclaration::default());
6780 {
6781 self.debug_text("{ x: ", info_text_config);
6782 let align_x = Self::align_x_name(layout_config.child_alignment.x);
6783 self.debug_text(align_x, info_text_config);
6784 self.debug_text(", y: ", info_text_config);
6785 let align_y = Self::align_y_name(layout_config.child_alignment.y);
6786 self.debug_text(align_y, info_text_config);
6787 self.debug_text(" }", info_text_config);
6788 }
6789 self.close_element();
6790 }
6791 self.close_element(); let configs_start = self.layout_elements[layout_elem_idx].element_configs.start;
6795 let configs_len = self.layout_elements[layout_elem_idx].element_configs.length;
6796 let elem_id_string = selected_item.element_id.string_id.clone();
6797
6798 let mut shared_bg_color: Option<Color> = None;
6800 let mut shared_corner_radius: Option<CornerRadius> = None;
6801 for ci in 0..configs_len {
6802 let ec = self.element_configs[configs_start + ci as usize];
6803 if ec.config_type == ElementConfigType::Shared {
6804 let shared = self.shared_element_configs[ec.config_index];
6805 shared_bg_color = Some(shared.background_color);
6806 shared_corner_radius = Some(shared.corner_radius);
6807 }
6808 }
6809
6810 let shape_rot = self.element_shape_rotations.get(layout_elem_idx).copied().flatten();
6812 let visual_rot = self.element_visual_rotations.get(layout_elem_idx).cloned().flatten();
6813 let effects = self.element_effects.get(layout_elem_idx).cloned().unwrap_or_default();
6814 let shaders = self.element_shaders.get(layout_elem_idx).cloned().unwrap_or_default();
6815
6816 let has_color = shared_bg_color.map_or(false, |c| c.a > 0.0);
6818 if has_color {
6819 let color_label_color = Color::rgba(243.0, 134.0, 48.0, 255.0);
6820 self.render_debug_view_category_header("Color", color_label_color, elem_id_string.clone());
6821 self.debug_open(&ElementDeclaration {
6822 layout: LayoutConfig {
6823 padding: attr_padding,
6824 child_gap: 8,
6825 layout_direction: LayoutDirection::TopToBottom,
6826 ..Default::default()
6827 },
6828 ..Default::default()
6829 });
6830 {
6831 self.debug_text("Background Color", info_title_config);
6832 self.render_debug_view_color(shared_bg_color.unwrap(), info_text_config);
6833 }
6834 self.close_element();
6835 }
6836
6837 let has_corner_radius = shared_corner_radius.map_or(false, |cr| !cr.is_zero());
6839 let has_shape_rot = shape_rot.is_some();
6840 if has_corner_radius || has_shape_rot {
6841 let shape_label_color = Color::rgba(26.0, 188.0, 156.0, 255.0);
6842 self.render_debug_view_category_header("Shape", shape_label_color, elem_id_string.clone());
6843 self.debug_open(&ElementDeclaration {
6844 layout: LayoutConfig {
6845 padding: attr_padding,
6846 child_gap: 8,
6847 layout_direction: LayoutDirection::TopToBottom,
6848 ..Default::default()
6849 },
6850 ..Default::default()
6851 });
6852 {
6853 if let Some(cr) = shared_corner_radius {
6854 if !cr.is_zero() {
6855 self.debug_text("Corner Radius", info_title_config);
6856 self.render_debug_view_corner_radius(cr, info_text_config);
6857 }
6858 }
6859 if let Some(sr) = shape_rot {
6860 self.debug_text("Shape Rotation", info_title_config);
6861 self.debug_open(&ElementDeclaration::default());
6862 {
6863 self.debug_text("angle: ", info_text_config);
6864 self.debug_float_text(sr.rotation_radians, info_text_config);
6865 self.debug_text(" rad", info_text_config);
6866 }
6867 self.close_element();
6868 self.debug_open(&ElementDeclaration::default());
6869 {
6870 self.debug_text("flip_x: ", info_text_config);
6871 self.debug_text(if sr.flip_x { "true" } else { "false" }, info_text_config);
6872 self.debug_text(", flip_y: ", info_text_config);
6873 self.debug_text(if sr.flip_y { "true" } else { "false" }, info_text_config);
6874 }
6875 self.close_element();
6876 }
6877 }
6878 self.close_element();
6879 }
6880
6881 for ci in 0..configs_len {
6883 let ec = self.element_configs[configs_start + ci as usize];
6884 match ec.config_type {
6885 ElementConfigType::Shared => {} ElementConfigType::Text => {
6887 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
6888 let text_config = self.text_element_configs[ec.config_index].clone();
6889 self.debug_open(&ElementDeclaration {
6890 layout: LayoutConfig {
6891 padding: attr_padding,
6892 child_gap: 8,
6893 layout_direction: LayoutDirection::TopToBottom,
6894 ..Default::default()
6895 },
6896 ..Default::default()
6897 });
6898 {
6899 self.debug_text("Font Size", info_title_config);
6900 self.debug_int_text(text_config.font_size as f32, info_text_config);
6901 self.debug_text("Font", info_title_config);
6902 {
6903 let label = if let Some(asset) = text_config.font_asset {
6904 asset.key().to_string()
6905 } else {
6906 format!("default ({})", self.default_font_key)
6907 };
6908 self.open_text_element(&label, info_text_config);
6909 }
6910 self.debug_text("Line Height", info_title_config);
6911 if text_config.line_height == 0 {
6912 self.debug_text("auto", info_text_config);
6913 } else {
6914 self.debug_int_text(text_config.line_height as f32, info_text_config);
6915 }
6916 self.debug_text("Letter Spacing", info_title_config);
6917 self.debug_int_text(text_config.letter_spacing as f32, info_text_config);
6918 self.debug_text("Wrap Mode", info_title_config);
6919 let wrap = match text_config.wrap_mode {
6920 WrapMode::None => "NONE",
6921 WrapMode::Newline => "NEWLINES",
6922 _ => "WORDS",
6923 };
6924 self.debug_text(wrap, info_text_config);
6925 self.debug_text("Text Alignment", info_title_config);
6926 let align = match text_config.alignment {
6927 AlignX::CenterX => "CENTER",
6928 AlignX::Right => "RIGHT",
6929 _ => "LEFT",
6930 };
6931 self.debug_text(align, info_text_config);
6932 self.debug_text("Text Color", info_title_config);
6933 self.render_debug_view_color(text_config.color, info_text_config);
6934 }
6935 self.close_element();
6936 }
6937 ElementConfigType::Image => {
6938 let image_label_color = Color::rgba(121.0, 189.0, 154.0, 255.0);
6939 self.render_debug_view_category_header("Image", image_label_color, elem_id_string.clone());
6940 let image_data = self.image_element_configs[ec.config_index].clone();
6941 self.debug_open(&ElementDeclaration {
6942 layout: LayoutConfig {
6943 padding: attr_padding,
6944 child_gap: 8,
6945 layout_direction: LayoutDirection::TopToBottom,
6946 ..Default::default()
6947 },
6948 ..Default::default()
6949 });
6950 {
6951 self.debug_text("Source", info_title_config);
6952 let name = image_data.get_name();
6953 self.debug_raw_text(name, info_text_config);
6954 }
6955 self.close_element();
6956 }
6957 ElementConfigType::Clip => {
6958 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
6959 let clip_config = self.clip_element_configs[ec.config_index];
6960 self.debug_open(&ElementDeclaration {
6961 layout: LayoutConfig {
6962 padding: attr_padding,
6963 child_gap: 8,
6964 layout_direction: LayoutDirection::TopToBottom,
6965 ..Default::default()
6966 },
6967 ..Default::default()
6968 });
6969 {
6970 self.debug_text("Overflow", info_title_config);
6971 self.debug_open(&ElementDeclaration::default());
6972 {
6973 let x_label = if clip_config.scroll_x {
6974 "SCROLL"
6975 } else if clip_config.horizontal {
6976 "CLIP"
6977 } else {
6978 "OVERFLOW"
6979 };
6980 let y_label = if clip_config.scroll_y {
6981 "SCROLL"
6982 } else if clip_config.vertical {
6983 "CLIP"
6984 } else {
6985 "OVERFLOW"
6986 };
6987 self.debug_text("{ x: ", info_text_config);
6988 self.debug_text(x_label, info_text_config);
6989 self.debug_text(", y: ", info_text_config);
6990 self.debug_text(y_label, info_text_config);
6991 self.debug_text(" }", info_text_config);
6992 }
6993 self.close_element();
6994 }
6995 self.close_element();
6996 }
6997 ElementConfigType::Floating => {
6998 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
6999 let float_config = self.floating_element_configs[ec.config_index];
7000 self.debug_open(&ElementDeclaration {
7001 layout: LayoutConfig {
7002 padding: attr_padding,
7003 child_gap: 8,
7004 layout_direction: LayoutDirection::TopToBottom,
7005 ..Default::default()
7006 },
7007 ..Default::default()
7008 });
7009 {
7010 self.debug_text("Offset", info_title_config);
7011 self.debug_open(&ElementDeclaration::default());
7012 {
7013 self.debug_text("{ x: ", info_text_config);
7014 self.debug_int_text(float_config.offset.x, info_text_config);
7015 self.debug_text(", y: ", info_text_config);
7016 self.debug_int_text(float_config.offset.y, info_text_config);
7017 self.debug_text(" }", info_text_config);
7018 }
7019 self.close_element();
7020
7021 self.debug_text("z-index", info_title_config);
7022 self.debug_int_text(float_config.z_index as f32, info_text_config);
7023
7024 self.debug_text("Parent", info_title_config);
7025 let parent_name = self.layout_element_map
7026 .get(&float_config.parent_id)
7027 .map(|item| item.element_id.string_id.clone())
7028 .unwrap_or(StringId::empty());
7029 if !parent_name.is_empty() {
7030 self.debug_raw_text(parent_name.as_str(), info_text_config);
7031 }
7032
7033 self.debug_text("Attach Points", info_title_config);
7034 self.debug_open(&ElementDeclaration::default());
7035 {
7036 self.debug_text("{ element: (", info_text_config);
7037 self.debug_text(Self::align_x_name(float_config.attach_points.element_x), info_text_config);
7038 self.debug_text(", ", info_text_config);
7039 self.debug_text(Self::align_y_name(float_config.attach_points.element_y), info_text_config);
7040 self.debug_text("), parent: (", info_text_config);
7041 self.debug_text(Self::align_x_name(float_config.attach_points.parent_x), info_text_config);
7042 self.debug_text(", ", info_text_config);
7043 self.debug_text(Self::align_y_name(float_config.attach_points.parent_y), info_text_config);
7044 self.debug_text(") }", info_text_config);
7045 }
7046 self.close_element();
7047
7048 self.debug_text("Pointer Capture Mode", info_title_config);
7049 let pcm = if float_config.pointer_capture_mode == PointerCaptureMode::Passthrough {
7050 "PASSTHROUGH"
7051 } else {
7052 "NONE"
7053 };
7054 self.debug_text(pcm, info_text_config);
7055
7056 self.debug_text("Attach To", info_title_config);
7057 let at = match float_config.attach_to {
7058 FloatingAttachToElement::Parent => "PARENT",
7059 FloatingAttachToElement::ElementWithId => "ELEMENT_WITH_ID",
7060 FloatingAttachToElement::Root => "ROOT",
7061 _ => "NONE",
7062 };
7063 self.debug_text(at, info_text_config);
7064
7065 self.debug_text("Clip To", info_title_config);
7066 let ct = if float_config.clip_to == FloatingClipToElement::None {
7067 "NONE"
7068 } else {
7069 "ATTACHED_PARENT"
7070 };
7071 self.debug_text(ct, info_text_config);
7072 }
7073 self.close_element();
7074 }
7075 ElementConfigType::Border => {
7076 self.render_debug_view_element_config_header(elem_id_string.clone(), ec.config_type, info_title_config);
7077 let border_config = self.border_element_configs[ec.config_index];
7078 self.debug_open_id("Ply__DebugViewElementInfoBorderBody", &ElementDeclaration {
7079 layout: LayoutConfig {
7080 padding: attr_padding,
7081 child_gap: 8,
7082 layout_direction: LayoutDirection::TopToBottom,
7083 ..Default::default()
7084 },
7085 ..Default::default()
7086 });
7087 {
7088 self.debug_text("Border Widths", info_title_config);
7089 self.debug_open(&ElementDeclaration::default());
7090 {
7091 self.debug_text("{ left: ", info_text_config);
7092 self.debug_int_text(border_config.width.left as f32, info_text_config);
7093 self.debug_text(", right: ", info_text_config);
7094 self.debug_int_text(border_config.width.right as f32, info_text_config);
7095 self.debug_text(", top: ", info_text_config);
7096 self.debug_int_text(border_config.width.top as f32, info_text_config);
7097 self.debug_text(", bottom: ", info_text_config);
7098 self.debug_int_text(border_config.width.bottom as f32, info_text_config);
7099 self.debug_text(" }", info_text_config);
7100 }
7101 self.close_element();
7102 self.debug_text("Border Color", info_title_config);
7103 self.render_debug_view_color(border_config.color, info_text_config);
7104 }
7105 self.close_element();
7106 }
7107 ElementConfigType::TextInput => {
7108 let input_label_color = Color::rgba(52.0, 152.0, 219.0, 255.0);
7110 self.render_debug_view_category_header("Input", input_label_color, elem_id_string.clone());
7111 let ti_cfg = self.text_input_configs[ec.config_index].clone();
7112 self.debug_open(&ElementDeclaration {
7113 layout: LayoutConfig {
7114 padding: attr_padding,
7115 child_gap: 8,
7116 layout_direction: LayoutDirection::TopToBottom,
7117 ..Default::default()
7118 },
7119 ..Default::default()
7120 });
7121 {
7122 if !ti_cfg.placeholder.is_empty() {
7123 self.debug_text("Placeholder", info_title_config);
7124 self.debug_raw_text(&ti_cfg.placeholder, info_text_config);
7125 }
7126 self.debug_text("Max Length", info_title_config);
7127 if let Some(max_len) = ti_cfg.max_length {
7128 self.debug_int_text(max_len as f32, info_text_config);
7129 } else {
7130 self.debug_text("unlimited", info_text_config);
7131 }
7132 self.debug_text("Password", info_title_config);
7133 self.debug_text(if ti_cfg.is_password { "true" } else { "false" }, info_text_config);
7134 self.debug_text("Multiline", info_title_config);
7135 self.debug_text(if ti_cfg.is_multiline { "true" } else { "false" }, info_text_config);
7136 self.debug_text("Font", info_title_config);
7137 self.debug_open(&ElementDeclaration::default());
7138 {
7139 let label = if let Some(asset) = ti_cfg.font_asset {
7140 asset.key().to_string()
7141 } else {
7142 format!("default ({})", self.default_font_key)
7143 };
7144 self.open_text_element(&label, info_text_config);
7145 self.debug_text(", size: ", info_text_config);
7146 self.debug_int_text(ti_cfg.font_size as f32, info_text_config);
7147 }
7148 self.close_element();
7149 self.debug_text("Text Color", info_title_config);
7150 self.render_debug_view_color(ti_cfg.text_color, info_text_config);
7151 self.debug_text("Cursor Color", info_title_config);
7152 self.render_debug_view_color(ti_cfg.cursor_color, info_text_config);
7153 self.debug_text("Selection Color", info_title_config);
7154 self.render_debug_view_color(ti_cfg.selection_color, info_text_config);
7155 let state_data = self.text_edit_states.get(&selected_id)
7157 .map(|s| (s.text.clone(), s.cursor_pos));
7158 if let Some((text_val, cursor_pos)) = state_data {
7159 self.debug_text("Value", info_title_config);
7160 let preview = if text_val.len() > 40 {
7161 let mut end = 40;
7162 while !text_val.is_char_boundary(end) { end -= 1; }
7163 format!("\"{}...\"", &text_val[..end])
7164 } else {
7165 format!("\"{}\"", &text_val)
7166 };
7167 self.debug_raw_text(&preview, info_text_config);
7168 self.debug_text("Cursor Position", info_title_config);
7169 self.debug_int_text(cursor_pos as f32, info_text_config);
7170 }
7171 }
7172 self.close_element();
7173 }
7174 _ => {}
7175 }
7176 }
7177
7178 let has_visual_rot = visual_rot.is_some();
7180 let has_effects = !effects.is_empty();
7181 let has_shaders = !shaders.is_empty();
7182 if has_visual_rot || has_effects || has_shaders {
7183 let effects_label_color = Color::rgba(155.0, 89.0, 182.0, 255.0);
7184 self.render_debug_view_category_header("Effects", effects_label_color, elem_id_string.clone());
7185 self.debug_open(&ElementDeclaration {
7186 layout: LayoutConfig {
7187 padding: attr_padding,
7188 child_gap: 8,
7189 layout_direction: LayoutDirection::TopToBottom,
7190 ..Default::default()
7191 },
7192 ..Default::default()
7193 });
7194 {
7195 if let Some(vr) = visual_rot {
7196 self.debug_text("Visual Rotation", info_title_config);
7197 self.debug_open(&ElementDeclaration::default());
7198 {
7199 self.debug_text("angle: ", info_text_config);
7200 self.debug_float_text(vr.rotation_radians, info_text_config);
7201 self.debug_text(" rad", info_text_config);
7202 }
7203 self.close_element();
7204 self.debug_open(&ElementDeclaration::default());
7205 {
7206 self.debug_text("pivot: (", info_text_config);
7207 self.debug_float_text(vr.pivot_x, info_text_config);
7208 self.debug_text(", ", info_text_config);
7209 self.debug_float_text(vr.pivot_y, info_text_config);
7210 self.debug_text(")", info_text_config);
7211 }
7212 self.close_element();
7213 self.debug_open(&ElementDeclaration::default());
7214 {
7215 self.debug_text("flip_x: ", info_text_config);
7216 self.debug_text(if vr.flip_x { "true" } else { "false" }, info_text_config);
7217 self.debug_text(", flip_y: ", info_text_config);
7218 self.debug_text(if vr.flip_y { "true" } else { "false" }, info_text_config);
7219 }
7220 self.close_element();
7221 }
7222 for (i, effect) in effects.iter().enumerate() {
7223 let label = format!("Effect {}", i + 1);
7224 self.debug_text("Effect", info_title_config);
7225 self.debug_open(&ElementDeclaration::default());
7226 {
7227 self.debug_raw_text(&label, info_text_config);
7228 self.debug_text(": ", info_text_config);
7229 self.debug_raw_text(&effect.name, info_text_config);
7230 }
7231 self.close_element();
7232 for uniform in &effect.uniforms {
7233 self.debug_open(&ElementDeclaration::default());
7234 {
7235 self.debug_text(" ", info_text_config);
7236 self.debug_raw_text(&uniform.name, info_text_config);
7237 self.debug_text(": ", info_text_config);
7238 self.render_debug_shader_uniform_value(&uniform.value, info_text_config);
7239 }
7240 self.close_element();
7241 }
7242 }
7243 for (i, shader) in shaders.iter().enumerate() {
7244 let label = format!("Shader {}", i + 1);
7245 self.debug_text("Shader", info_title_config);
7246 self.debug_open(&ElementDeclaration::default());
7247 {
7248 self.debug_raw_text(&label, info_text_config);
7249 self.debug_text(": ", info_text_config);
7250 self.debug_raw_text(&shader.name, info_text_config);
7251 }
7252 self.close_element();
7253 for uniform in &shader.uniforms {
7254 self.debug_open(&ElementDeclaration::default());
7255 {
7256 self.debug_text(" ", info_text_config);
7257 self.debug_raw_text(&uniform.name, info_text_config);
7258 self.debug_text(": ", info_text_config);
7259 self.render_debug_shader_uniform_value(&uniform.value, info_text_config);
7260 }
7261 self.close_element();
7262 }
7263 }
7264 }
7265 self.close_element();
7266 }
7267 }
7268 self.close_element(); }
7270
7271 fn align_x_name(value: AlignX) -> &'static str {
7272 match value {
7273 AlignX::Left => "LEFT",
7274 AlignX::CenterX => "CENTER",
7275 AlignX::Right => "RIGHT",
7276 }
7277 }
7278
7279 fn align_y_name(value: AlignY) -> &'static str {
7280 match value {
7281 AlignY::Top => "TOP",
7282 AlignY::CenterY => "CENTER",
7283 AlignY::Bottom => "BOTTOM",
7284 }
7285 }
7286
7287 pub fn set_max_element_count(&mut self, count: i32) {
7288 self.max_element_count = count;
7289 }
7290
7291 pub fn set_max_measure_text_cache_word_count(&mut self, count: i32) {
7292 self.max_measure_text_cache_word_count = count;
7293 }
7294
7295 pub fn set_debug_mode_enabled(&mut self, enabled: bool) {
7296 self.debug_mode_enabled = enabled;
7297 }
7298
7299 pub fn is_debug_mode_enabled(&self) -> bool {
7300 self.debug_mode_enabled
7301 }
7302
7303 pub fn set_culling_enabled(&mut self, enabled: bool) {
7304 self.culling_disabled = !enabled;
7305 }
7306
7307 pub fn set_measure_text_function(
7308 &mut self,
7309 f: Box<dyn Fn(&str, &TextConfig) -> Dimensions>,
7310 ) {
7311 self.measure_text_fn = Some(f);
7312 self.font_height_cache.clear();
7314 }
7315}