1use crate::primitives::{IScrollSnapPointsInfo, SnapPointsAlignment, SnapPointsChangedHandler};
6use crate::{Control, ControlImpl, Panel, PanelImpl};
7use ferroui_base::input::{INavigableContainer, InputElement, InputElementImpl, NavigationDirection};
8use ferroui_base::interactivity::{
9 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
10};
11use ferroui_base::layout::{Layoutable, LayoutableImpl, Orientation};
12use ferroui_base::utilities::math_utilities::max;
13use ferroui_base::{
14 ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, FerroObjectImpl, FerroProperty, Rect, Ref, Size, StyledElementImpl, StyledProperty, VisualImpl,
15};
16use ferroui_base::reactive::IDisposable;
17use std::rc::Rc;
18
19#[repr(C)]
21pub struct StackPanel {
22 base: Panel,
23}
24
25ferro_class! {
26 StackPanel: Panel, virtuals StackPanelImpl: PanelImpl {
27 fn arrange_child(this, child: &Ref<Control>, rect: Rect, panel_size: Size, orientation: Orientation);
29 fn get_control_in_direction(this, direction: NavigationDirection, from: Option<&Ref<Control>>) -> Option<Ref<InputElement>>;
33 }
34}
35ferroui_base::ferro_class_info!(StackPanel { new: StackPanel::new });
36
37ferro_impl_classes!(
38 StackPanel: StyledElementImpl,
39 VisualImpl,
40 InteractiveImpl,
41 InputElementImpl,
42 ControlImpl,
43 PanelImpl
44);
45
46ferroui_base::ferro_impl_classes!(StackPanel: FerroObjectImpl);
47
48impl LayoutableImpl for StackPanel {
49 fn measure_override(this: &Self, available_size: Size) -> Size {
54 let mut stack_desired_size = Size::default();
55 let children = this.children().snapshot();
56 let mut layout_slot_size = available_size;
57 let horizontal = this.orientation() == Orientation::Horizontal;
58 let spacing = this.spacing();
59 let mut has_visible_child = false;
60
61 if horizontal {
64 layout_slot_size = layout_slot_size.with_width(f64::INFINITY);
65 } else {
66 layout_slot_size = layout_slot_size.with_height(f64::INFINITY);
67 }
68
69 for child in children.iter() {
71 child.measure(layout_slot_size);
73
74 let is_visible = child.is_visible();
75
76 if is_visible && !has_visible_child {
77 has_visible_child = true;
78 }
79
80 let child_desired_size = child.desired_size();
81
82 if horizontal {
84 stack_desired_size = stack_desired_size.with_width(
85 stack_desired_size.width + (if is_visible { spacing } else { 0.0 }) + child_desired_size.width,
86 );
87 stack_desired_size =
88 stack_desired_size.with_height(max(stack_desired_size.height, child_desired_size.height));
89 } else {
90 stack_desired_size =
91 stack_desired_size.with_width(max(stack_desired_size.width, child_desired_size.width));
92 stack_desired_size = stack_desired_size.with_height(
93 stack_desired_size.height + (if is_visible { spacing } else { 0.0 }) + child_desired_size.height,
94 );
95 }
96 }
97
98 if horizontal {
99 stack_desired_size = stack_desired_size
100 .with_width(stack_desired_size.width - (if has_visible_child { spacing } else { 0.0 }));
101 } else {
102 stack_desired_size = stack_desired_size
103 .with_height(stack_desired_size.height - (if has_visible_child { spacing } else { 0.0 }));
104 }
105
106 stack_desired_size
107 }
108
109 fn arrange_override(this: &Self, final_size: Size) -> Size {
111 let children = this.children().snapshot();
112 let horizontal = this.orientation() == Orientation::Horizontal;
113 let mut rc_child = Rect::from_size(final_size);
114 let mut previous_child_size = 0.0;
115 let spacing = this.spacing();
116
117 for child in children.iter() {
119 if !child.is_visible() {
120 continue;
121 }
122
123 if horizontal {
124 rc_child = rc_child.with_x(rc_child.x + previous_child_size);
125 previous_child_size = child.desired_size().width;
126 rc_child = rc_child.with_width(previous_child_size);
127 rc_child = rc_child.with_height(max(final_size.height, child.desired_size().height));
128 previous_child_size += spacing;
129 } else {
130 rc_child = rc_child.with_y(rc_child.y + previous_child_size);
131 previous_child_size = child.desired_size().height;
132 rc_child = rc_child.with_height(previous_child_size);
133 rc_child = rc_child.with_width(max(final_size.width, child.desired_size().width));
134 previous_child_size += spacing;
135 }
136
137 this.arrange_child(child, rc_child, final_size, this.orientation());
138 }
139
140 this.raise_snap_points_changed();
141
142 final_size
143 }
144}
145
146impl StackPanelImpl for StackPanel {
147 fn arrange_child(_this: &Self, child: &Ref<Control>, rect: Rect, _panel_size: Size, _orientation: Orientation) {
148 child.arrange(rect);
149 }
150
151 fn get_control_in_direction(
152 this: &Self,
153 direction: NavigationDirection,
154 from: Option<&Ref<Control>>,
155 ) -> Option<Ref<InputElement>> {
156 let horiz = this.orientation() == Orientation::Horizontal;
157 let children = this.children();
158 let mut index: i64 = from.and_then(|from| children.index_of(from)).map_or(-1, |index| index as i64);
159
160 match direction {
161 NavigationDirection::First => index = 0,
162 NavigationDirection::Last => index = children.count() as i64 - 1,
163 NavigationDirection::Next => index += 1,
164 NavigationDirection::Previous => {
165 if index != -1 {
166 index -= 1;
167 }
168 }
169 NavigationDirection::Left => {
170 if index != -1 {
171 index = if horiz { index - 1 } else { -1 };
172 }
173 }
174 NavigationDirection::Right => {
175 if index != -1 {
176 index = if horiz { index + 1 } else { -1 };
177 }
178 }
179 NavigationDirection::Up => {
180 if index != -1 {
181 index = if horiz { -1 } else { index - 1 };
182 }
183 }
184 NavigationDirection::Down => {
185 if index != -1 {
186 index = if horiz { -1 } else { index + 1 };
187 }
188 }
189 _ => index = -1,
190 }
191
192 if index >= 0 && (index as usize) < children.count() {
193 Some(children.get(index as usize).upcast())
194 } else {
195 None
196 }
197 }
198}
199
200impl INavigableContainer for StackPanel {
201 fn get_control(
202 &self,
203 direction: NavigationDirection,
204 from: Option<&Ref<InputElement>>,
205 wrap: bool,
206 ) -> Option<Ref<InputElement>> {
207 let from = from.and_then(|from| from.cast::<Control>());
208 let mut result = self.get_control_in_direction(direction, from.as_ref());
209
210 if result.is_none() && wrap {
211 use NavigationDirection::*;
212 if self.orientation() == Orientation::Vertical {
213 match direction {
214 Up | Previous | PageUp => result = self.get_control_in_direction(Last, None),
215 Down | Next | PageDown => result = self.get_control_in_direction(First, None),
216 _ => {}
217 }
218 } else {
219 match direction {
220 Left | Previous | PageUp => result = self.get_control_in_direction(Last, None),
221 Right | Next | PageDown => result = self.get_control_in_direction(First, None),
222 _ => {}
223 }
224 }
225 }
226
227 result
228 }
229}
230
231ferroui_base::ferro_properties! { impl StackPanel {
232 ferro_property!(
233 pub fn spacing_property() -> StyledProperty<f64> {
235 FerroProperty::register::<StackPanel, _>("Spacing", 0.0)
236 }
237 );
238
239 ferro_property!(
240 pub fn orientation_property() -> StyledProperty<Orientation> {
242 FerroProperty::register::<StackPanel, _>("Orientation", Orientation::Vertical)
243 }
244 );
245
246 ferro_property!(
247 pub fn are_horizontal_snap_points_regular_property() -> StyledProperty<bool> {
249 FerroProperty::register::<StackPanel, _>("AreHorizontalSnapPointsRegular", false)
250 }
251 );
252
253 ferro_property!(
254 pub fn are_vertical_snap_points_regular_property() -> StyledProperty<bool> {
256 FerroProperty::register::<StackPanel, _>("AreVerticalSnapPointsRegular", false)
257 }
258 );
259} }
260
261impl StackPanel {
262 ferro_routed_event!(
263 pub fn horizontal_snap_points_changed_event() -> RoutedEvent<RoutedEventArgs> {
265 RoutedEvent::register::<StackPanel, _>("HorizontalSnapPointsChanged", RoutingStrategies::BUBBLE)
266 }
267 );
268
269 ferro_routed_event!(
270 pub fn vertical_snap_points_changed_event() -> RoutedEvent<RoutedEventArgs> {
272 RoutedEvent::register::<StackPanel, _>("VerticalSnapPointsChanged", RoutingStrategies::BUBBLE)
273 }
274 );
275
276 fn static_constructor() {
277 Layoutable::affects_measure::<StackPanel>(&[Self::spacing_property().as_property()]);
278 Layoutable::affects_measure::<StackPanel>(&[Self::orientation_property().as_property()]);
279 }
280
281 pub fn construct() -> Self {
283 Self { base: Panel::construct() }
284 }
285
286 pub fn new() -> Ref<Self> {
287 instantiate(Self::construct())
288 }
289
290 pub fn spacing(&self) -> f64 {
292 self.get_value(Self::spacing_property())
293 }
294
295 pub fn set_spacing(&self, value: f64) {
296 self.set_value(Self::spacing_property(), value)
297 }
298
299 pub fn orientation(&self) -> Orientation {
301 self.get_value(Self::orientation_property())
302 }
303
304 pub fn set_orientation(&self, value: Orientation) {
305 self.set_value(Self::orientation_property(), value)
306 }
307
308 pub fn horizontal_snap_points_changed(
310 &self,
311 handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
312 ) -> RoutedEventHandlerToken {
313 self.add_handler(Self::horizontal_snap_points_changed_event(), handler)
314 }
315
316 pub fn vertical_snap_points_changed(
318 &self,
319 handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
320 ) -> RoutedEventHandlerToken {
321 self.add_handler(Self::vertical_snap_points_changed_event(), handler)
322 }
323
324 pub fn are_horizontal_snap_points_regular(&self) -> bool {
327 self.get_value(Self::are_horizontal_snap_points_regular_property())
328 }
329
330 pub fn set_are_horizontal_snap_points_regular(&self, value: bool) {
331 self.set_value(Self::are_horizontal_snap_points_regular_property(), value)
332 }
333
334 pub fn are_vertical_snap_points_regular(&self) -> bool {
337 self.get_value(Self::are_vertical_snap_points_regular_property())
338 }
339
340 pub fn set_are_vertical_snap_points_regular(&self, value: bool) {
341 self.set_value(Self::are_vertical_snap_points_regular_property(), value)
342 }
343
344 fn raise_snap_points_changed(&self) {
346 let event = if self.orientation() == Orientation::Horizontal {
347 Self::horizontal_snap_points_changed_event()
348 } else {
349 Self::vertical_snap_points_changed_event()
350 };
351 self.raise_event(&RoutedEventArgs::with_event(event));
352 }
353
354 pub fn get_irregular_snap_points(
359 &self,
360 orientation: Orientation,
361 snap_points_alignment: SnapPointsAlignment,
362 ) -> Vec<f64> {
363 let mut snap_points = Vec::new();
364
365 match orientation {
366 Orientation::Horizontal => {
367 if self.are_horizontal_snap_points_regular() {
368 panic!("The horizontal snap points are regular.");
369 }
370 if self.orientation() == Orientation::Horizontal {
371 for child in self.visual_children().snapshot().iter() {
372 let bounds = child.bounds();
373 snap_points.push(match snap_points_alignment {
374 SnapPointsAlignment::Near => bounds.left(),
375 SnapPointsAlignment::Center => bounds.center().x,
376 SnapPointsAlignment::Far => bounds.right(),
377 });
378 }
379 }
380 }
381 Orientation::Vertical => {
382 if self.are_vertical_snap_points_regular() {
383 panic!("The vertical snap points are regular.");
384 }
385 if self.orientation() == Orientation::Vertical {
386 for child in self.visual_children().snapshot().iter() {
387 let bounds = child.bounds();
388 snap_points.push(match snap_points_alignment {
389 SnapPointsAlignment::Near => bounds.top(),
390 SnapPointsAlignment::Center => bounds.center().y,
391 SnapPointsAlignment::Far => bounds.bottom(),
392 });
393 }
394 }
395 }
396 }
397
398 snap_points
399 }
400
401 pub fn get_regular_snap_points(
406 &self,
407 _orientation: Orientation,
408 snap_points_alignment: SnapPointsAlignment,
409 ) -> (f64, f64) {
410 let mut offset = 0.0;
411 let Some(first_child) = self.visual_children().try_get(0) else {
412 return (0.0, offset);
413 };
414 let bounds = first_child.bounds();
415
416 let snap_point = match self.orientation() {
417 Orientation::Horizontal => {
418 if !self.are_horizontal_snap_points_regular() {
419 panic!("The horizontal snap points are not regular.");
420 }
421
422 offset = match snap_points_alignment {
423 SnapPointsAlignment::Near => bounds.left(),
424 SnapPointsAlignment::Center => bounds.center().x,
425 SnapPointsAlignment::Far => bounds.right(),
426 };
427 bounds.width
428 }
429 Orientation::Vertical => {
430 if !self.are_vertical_snap_points_regular() {
431 panic!("The vertical snap points are not regular.");
432 }
433
434 offset = match snap_points_alignment {
435 SnapPointsAlignment::Near => bounds.top(),
436 SnapPointsAlignment::Center => bounds.center().y,
437 SnapPointsAlignment::Far => bounds.bottom(),
438 };
439 bounds.height
440 }
441 };
442
443 (snap_point + self.spacing(), offset)
444 }
445}
446
447impl IScrollSnapPointsInfo for StackPanel {
448 fn are_horizontal_snap_points_regular(&self) -> bool {
449 StackPanel::are_horizontal_snap_points_regular(self)
450 }
451
452 fn set_are_horizontal_snap_points_regular(&self, value: bool) {
453 StackPanel::set_are_horizontal_snap_points_regular(self, value)
454 }
455
456 fn are_vertical_snap_points_regular(&self) -> bool {
457 StackPanel::are_vertical_snap_points_regular(self)
458 }
459
460 fn set_are_vertical_snap_points_regular(&self, value: bool) {
461 StackPanel::set_are_vertical_snap_points_regular(self, value)
462 }
463
464 fn get_irregular_snap_points(
465 &self,
466 orientation: Orientation,
467 snap_points_alignment: SnapPointsAlignment,
468 ) -> Vec<f64> {
469 StackPanel::get_irregular_snap_points(self, orientation, snap_points_alignment)
470 }
471
472 fn get_regular_snap_points(
473 &self,
474 orientation: Orientation,
475 snap_points_alignment: SnapPointsAlignment,
476 ) -> (f64, f64) {
477 StackPanel::get_regular_snap_points(self, orientation, snap_points_alignment)
478 }
479
480 fn horizontal_snap_points_changed(&self, handler: SnapPointsChangedHandler) -> Rc<dyn IDisposable> {
481 self.add_disposable_handler(StackPanel::horizontal_snap_points_changed_event(), move |sender, e| {
482 handler(sender, e)
483 })
484 }
485
486 fn vertical_snap_points_changed(&self, handler: SnapPointsChangedHandler) -> Rc<dyn IDisposable> {
487 self.add_disposable_handler(StackPanel::vertical_snap_points_changed_event(), move |sender, e| {
488 handler(sender, e)
489 })
490 }
491}