fret_ui_kit/declarative/
active_descendant.rs1use fret_core::{NodeId, Point, Px, Rect};
2use fret_ui::elements::GlobalElementId;
3use fret_ui::scroll::ScrollHandle;
4use fret_ui::{ElementContext, UiHost};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct ActiveOption {
8 pub element: GlobalElementId,
9 pub node: NodeId,
10}
11
12pub fn active_element_for_index(
13 elements: &[GlobalElementId],
14 active_index: Option<usize>,
15) -> Option<GlobalElementId> {
16 active_index.and_then(|idx| elements.get(idx).copied())
17}
18
19pub fn active_descendant_for_index<H: UiHost>(
25 cx: &mut ElementContext<'_, H>,
26 elements: &[GlobalElementId],
27 active_index: Option<usize>,
28) -> Option<NodeId> {
29 active_option_for_index(cx, elements, active_index).map(|opt| opt.node)
30}
31
32pub fn active_option_for_index<H: UiHost>(
33 cx: &mut ElementContext<'_, H>,
34 elements: &[GlobalElementId],
35 active_index: Option<usize>,
36) -> Option<ActiveOption> {
37 let element = active_element_for_index(elements, active_index)?;
38 let node = cx.live_node_for_element(element)?;
39 Some(ActiveOption { element, node })
40}
41
42pub fn scroll_handle_into_view_y(handle: &ScrollHandle, viewport: Rect, child: Rect) -> bool {
45 let viewport_h = viewport.size.height.0.max(0.0);
46 if viewport_h <= 0.0 {
47 return false;
48 }
49
50 let view_top = viewport.origin.y.0;
51 let view_bottom = view_top + viewport_h;
52 let child_top = child.origin.y.0;
53 let child_h = child.size.height.0.max(0.0);
54 let child_bottom = child_top + child_h;
55
56 if child_h >= viewport_h - 0.01 {
60 let delta = child_top - view_top;
61 if delta.abs() <= 0.01 {
62 return false;
63 }
64
65 let prev = handle.offset();
66 handle.set_offset(Point::new(prev.x, Px(prev.y.0 + delta)));
67
68 let next = handle.offset();
69 return (prev.y.0 - next.y.0).abs() > 0.01;
70 }
71
72 let delta = if child_top < view_top {
73 child_top - view_top
74 } else if child_bottom > view_bottom {
75 child_bottom - view_bottom
76 } else {
77 0.0
78 };
79
80 if delta.abs() <= 0.01 {
81 return false;
82 }
83
84 let prev = handle.offset();
85 handle.set_offset(Point::new(prev.x, Px(prev.y.0 + delta)));
86
87 let next = handle.offset();
88 (prev.y.0 - next.y.0).abs() > 0.01
89}
90
91pub fn scroll_handle_align_top_y(handle: &ScrollHandle, viewport: Rect, child: Rect) -> bool {
96 let viewport_h = viewport.size.height.0.max(0.0);
97 if viewport_h <= 0.0 {
98 return false;
99 }
100
101 let view_top = viewport.origin.y.0;
102 let child_top = child.origin.y.0;
103 let delta = child_top - view_top;
104
105 if delta.abs() <= 0.01 {
106 return false;
107 }
108
109 let prev = handle.offset();
110 handle.set_offset(Point::new(prev.x, Px(prev.y.0 + delta)));
111
112 let next = handle.offset();
113 (prev.y.0 - next.y.0).abs() > 0.01
114}
115
116pub fn scroll_active_element_into_view_y<H: UiHost>(
121 cx: &mut ElementContext<'_, H>,
122 handle: &ScrollHandle,
123 viewport_element: GlobalElementId,
124 active_element: GlobalElementId,
125) -> bool {
126 let Some(viewport) = cx.last_bounds_for_element(viewport_element) else {
127 return false;
128 };
129 let Some(child) = cx.last_bounds_for_element(active_element) else {
130 return false;
131 };
132
133 scroll_handle_into_view_y(handle, viewport, child)
134}
135
136pub fn scroll_active_element_align_top_y<H: UiHost>(
141 cx: &mut ElementContext<'_, H>,
142 handle: &ScrollHandle,
143 viewport_element: GlobalElementId,
144 active_element: GlobalElementId,
145) -> bool {
146 let Some(viewport) = cx.last_bounds_for_element(viewport_element) else {
147 return false;
148 };
149 let Some(child) = cx.last_bounds_for_element(active_element) else {
150 return false;
151 };
152
153 scroll_handle_align_top_y(handle, viewport, child)
154}