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