Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/flex/mod.rs
1mod wrap;
2 
3pub use wrap::*;
4 
5use crate::{
6 event::DispatchedEvent,
7 text::{word_boundaries::WordBoundariesPolicy, IsRect, SelectionDirection, SelectionType},
8};
9 
10use super::{
11 AfterLayoutContext, AppContext, Axis, AxisOrientation, Element, EventContext, LayoutContext,
12 PaintContext, Point, SelectableElement, Selection, SelectionFragment, SizeConstraint,
13 Vector2FExt,
14};
15use pathfinder_geometry::rect::RectF;
16use pathfinder_geometry::vector::{vec2f, Vector2F};
17use std::any::Any;
18 
19pub struct Flex {
20 axis: Axis,
21 orientation: AxisOrientation,
22 children: Vec<Box<dyn Element>>,
23 size: Option<Vector2F>,
24 origin: Option<Point>,
25 main_axis_size: MainAxisSize,
26 main_axis_alignment: MainAxisAlignment,
27 cross_axis_alignment: CrossAxisAlignment,
28 spacing: f32,
29 layout_state: Option<LayoutState>,
30 constrain_horizontal_bounds_to_parent: bool,
31 #[cfg(debug_assertions)]
32 container_constructor_location: Option<&'static std::panic::Location<'static>>,
33 #[cfg(debug_assertions)]
34 child_locations: Vec<&'static std::panic::Location<'static>>,
35}
36 
37#[derive(Debug)]
38struct LayoutState {
39 /// Space between each child element.
40 between_space: Vector2F,
41 /// Space between the first and last element.
42 leading_space: Vector2F,
43}
44 
45impl LayoutState {
46 fn compute(
47 children_count: usize,
48 spacing: f32,
49 remaining_space: f32,
50 axis_alignment: MainAxisAlignment,
51 axis: Axis,
52 ) -> Self {
53 let (between_space, leading_space) = match axis_alignment {
54 MainAxisAlignment::Start => (0., 0.),
55 MainAxisAlignment::SpaceBetween => {
56 if children_count <= 1 {
57 (0., 0.)
58 } else {
59 let between_space = remaining_space / ((children_count - 1) as f32);
60 (between_space, 0.)
61 }
62 }
63 MainAxisAlignment::SpaceEvenly => {
64 if children_count == 0 {
65 (0., 0.)
66 } else {
67 // Divide the remaining space between all the gaps between the children
68 // (`children_count - 1`) plus the beginning and end.
69 let even_space = remaining_space / ((children_count + 1) as f32);
70 (even_space, even_space)
71 }
72 }
73 MainAxisAlignment::Center => (0.0, remaining_space / 2.0),
74 MainAxisAlignment::End => (0.0, remaining_space),
75 };
76 
77 Self {
78 between_space: size_along_axis(spacing + between_space, axis),
79 leading_space: size_along_axis(leading_space, axis),
80 }
81 }
82}
83 
84impl Flex {
85 pub fn new(axis: Axis) -> Self {
86 Self {
87 axis,
88 orientation: AxisOrientation::Normal,
89 children: Vec::new(),
90 size: None,
91 origin: None,
92 main_axis_size: MainAxisSize::Min,
93 main_axis_alignment: MainAxisAlignment::Start,
94 cross_axis_alignment: CrossAxisAlignment::Start,
95 spacing: 0.0,
96 layout_state: None,
97 constrain_horizontal_bounds_to_parent: false,
98 #[cfg(debug_assertions)]
99 container_constructor_location: None,
100 #[cfg(debug_assertions)]
101 child_locations: Vec::new(),
102 }
103 }
104 
105 pub fn row() -> Self {
106 Self::new(Axis::Horizontal)
107 }
108 
109 pub fn column() -> Self {
110 Self::new(Axis::Vertical)
111 }
112 
113 pub fn with_reverse_orientation(mut self) -> Self {
114 self.orientation = AxisOrientation::Reverse;
115 self
116 }
117 
118 fn child_flex(child: &dyn Element) -> Option<f32> {
119 child
120 .parent_data()
121 .and_then(|d| d.downcast_ref::<FlexParentData>())
122 .map(|data| data.flex)
123 }
124 
125 fn child_flex_fit(child: &dyn Element) -> Option<FlexFit> {
126 child
127 .parent_data()
128 .and_then(|d| d.downcast_ref::<FlexParentData>())
129 .map(|data| data.fit)
130 }
131 
132 pub fn with_main_axis_size(mut self, main_axis_size: MainAxisSize) -> Self {
133 self.main_axis_size = main_axis_size;
134 self
135 }
136 
137 pub fn with_main_axis_alignment(mut self, alignment: MainAxisAlignment) -> Self {
138 self.main_axis_alignment = alignment;
139 self
140 }
141 
142 pub fn with_cross_axis_alignment(mut self, alignment: CrossAxisAlignment) -> Self {
143 self.cross_axis_alignment = alignment;
144 self
145 }
146 
147 pub fn with_spacing(mut self, spacing: f32) -> Self {
148 self.spacing = spacing;
149 self
150 }
151 
152 pub fn with_constrain_horizontal_bounds_to_parent(
153 mut self,
154 constrain_horizontal_bounds_to_parent: bool,
155 ) -> Self {
156 self.constrain_horizontal_bounds_to_parent = constrain_horizontal_bounds_to_parent;
157 self
158 }
159 
160 pub fn is_empty(&self) -> bool {
161 self.children.is_empty()
162 }
163}
164 
165impl Extend<Box<dyn Element>> for Flex {
166 #[cfg_attr(debug_assertions, track_caller)]
167 fn extend<T: IntoIterator<Item = Box<dyn Element>>>(&mut self, children: T) {
168 #[cfg(debug_assertions)]
169 {
170 let children: Vec<_> = children.into_iter().collect();
171 let count = children.len();
172 let location = std::panic::Location::caller();
173 for _ in 0..count {
174 self.child_locations.push(location);
175 }
176 self.children.extend(children);
177 }
178 #[cfg(not(debug_assertions))]
179 self.children.extend(children);
180 }
181}
182 
183impl Element for Flex {
184 fn layout(
185 &mut self,
186 constraint: SizeConstraint,
187 ctx: &mut LayoutContext,
188 app: &AppContext,
189 ) -> Vector2F {
190 if self.main_axis_size == MainAxisSize::Max {
191 // See https://www.notion.so/warpdev/Debugging-Flex-acc03383be5644a8af29d9c52b1142bd?pvs=4#fff43263616d8008b3e3efe280686886
192 #[cfg(debug_assertions)]
193 let location_info = self
194 .container_constructor_location
195 .map(|loc| {
196 format!(
197 " (flex created at {}:{}:{})",
198 loc.file(),
199 loc.line(),
200 loc.column()
201 )
202 })
203 .unwrap_or_default();
204 #[cfg(not(debug_assertions))]
205 let location_info = "";
206 
207 debug_assert!(
208 constraint.max_along(self.axis).is_finite(),
209 "A flex that should expand to a max space can't be rendered in an infinite max constraint\n{location_info}
210See https://www.notion.so/warpdev/Debugging-Flex-acc03383be5644a8af29d9c52b1142bd?pvs=4#fff43263616d8008b3e3efe280686886 for troubleshooting steps"
211 );
212 if constraint.max_along(self.axis).is_infinite() {
213 log::error!("A flex that should expand to a max space can't be rendered in an infinite max constraint\n{location_info}");
214 }
215 }
216 
217 let mut total_flex = 0.0;
218 let mut fixed_space = self.spacing * (self.children.len().saturating_sub(1)) as f32;
219 
220 let cross_axis = self.axis.invert();
221 let mut cross_axis_max: f32 = 0.0;
222 // Follow the algorithm specified in Flutter to layout flex elements: https://api.flutter.dev/flutter/widgets/Flex-class.html.
223 // At a high level, we take all non-flexible children and render them at an unbounded size
224 // along the main axis (keeping the size constraint the cross axis). We use the remaining
225 // space to fill all the `Shrinkable` and `Expanded` elements, respecting the `flex` to determine
226 // how much space each element should take up. For example, if there's 60 pixels of
227 // remaining space, and two `Expanded` elements (one with `flex` of 1.0 and the other with
228 // 2.0), we would render first element with a size of 20 pixels and the second with a size
229 // of 40 pixels.
230 for child in &mut self.children {
231 // If the element is flexible add to the total flex but don't lay out the child.
232 if let Some(flex) = Self::child_flex(child.as_ref()) {
233 total_flex += flex;
234 } else {
235 // The child is not flexible. In this case, we want the main axis size constraint
236 // going from 0 to infinity, and:
237 let child_constraint = if self.cross_axis_alignment == CrossAxisAlignment::Stretch {
238 // The cross axis constraint should be exactly the Flex's width or height.
239 SizeConstraint::tight_on_cross_axis(self.axis, constraint)
240 } else {
241 // The cross axis constraint should be inherited from the Flex.
242 SizeConstraint::child_constraint_along_axis(self.axis, constraint)
243 };
244 
245 let size = child.layout(child_constraint, ctx, app);
246 fixed_space += size.along(self.axis);
247 let cross_axis_size = size.along(cross_axis);
248 if cross_axis_size.is_finite() {
249 cross_axis_max = cross_axis_max.max(size.along(cross_axis));
250 }
251 }
252 }
253 
254 let mut size = if total_flex > 0.0 {
255 // See https://www.notion.so/warpdev/Debugging-Flex-acc03383be5644a8af29d9c52b1142bd?pvs=4#057b1e4ba7b844f7ad2e69433b295363
256 #[cfg(debug_assertions)]
257 let location_info = self
258 .container_constructor_location
259 .map(|loc| {
260 format!(
261 " (flex created at {}:{}:{})",
262 loc.file(),
263 loc.line(),
264 loc.column()
265 )
266 })
267 .unwrap_or_default();
268 #[cfg(not(debug_assertions))]
269 let location_info = "";
270 
271 debug_assert!(
272 constraint.max_along(self.axis).is_finite(),
273 "flex contains flexible children but has an infinite constraint along the flex axis{location_info}
274See https://www.notion.so/warpdev/Debugging-Flex-acc03383be5644a8af29d9c52b1142bd?pvs=4#057b1e4ba7b844f7ad2e69433b295363 for troubleshooting steps"
275 );
276 if constraint.max_along(self.axis).is_infinite() {
277 log::error!("flex contains flexible children but has an infinite constraint along the flex axis{location_info}");
278 }
279 
280 let mut remaining_space = (constraint.max_along(self.axis) - fixed_space).max(0.);
281 let mut remaining_flex = total_flex;
282 for child in &mut self.children {
283 let space_per_flex = remaining_space / remaining_flex;
284 if let Some(flex) = Self::child_flex(child.as_ref()) {
285 let child_max = space_per_flex * flex;
286 let child_min = match Self::child_flex_fit(child.as_ref()) {
287 Some(FlexFit::Loose) | None => 0.0,
288 Some(FlexFit::Tight) => child_max,
289 };
290 
291 let child_constraint = match self.axis {
292 Axis::Horizontal => SizeConstraint::new(
293 vec2f(child_min, constraint.min.y()),
294 vec2f(child_max, constraint.max.y()),
295 ),
296 Axis::Vertical => SizeConstraint::new(
297 vec2f(constraint.min.x(), child_min),
298 vec2f(constraint.max.x(), child_max),
299 ),
300 };
301 let child_size = child.layout(child_constraint, ctx, app);
302 remaining_space -= child_size.along(self.axis);
303 remaining_flex -= flex;
304 
305 let cross_axis_size = child_size.along(cross_axis);
306 if cross_axis_size.is_finite() {
307 cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
308 }
309 }
310 }
311 
312 // If children should stretch along the cross axis, perform another
313 // layout pass on any which ended up having an infinite size along
314 // the cross axis (so that they can stretch to the size of the
315 // largest finite child).
316 if self.cross_axis_alignment == CrossAxisAlignment::Stretch {
317 let mut constraint = constraint;
318 match cross_axis {
319 Axis::Horizontal => constraint.max.set_x(cross_axis_max),
320 Axis::Vertical => constraint.max.set_y(cross_axis_max),
321 }
322 for child in &mut self.children {
323 if let Some(size) = child.size() {
324 if size.along(cross_axis).is_infinite() {
325 child.layout(
326 SizeConstraint::tight_on_cross_axis(self.axis, constraint),
327 ctx,
328 app,
329 );
330 }
331 }
332 }
333 }
334 
335 match self.axis {
336 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
337 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
338 }
339 } else {
340 match (self.axis, self.constrain_horizontal_bounds_to_parent) {
341 (Axis::Horizontal, true) => {
342 vec2f(constraint.max.x().min(fixed_space), cross_axis_max)
343 }
344 (Axis::Horizontal, false) => vec2f(fixed_space, cross_axis_max),
345 (Axis::Vertical, _) => vec2f(cross_axis_max, fixed_space),
346 }
347 };
348 
349 let max_constraint_size = constraint.max.along(self.axis);
350 let allocated_size = size.along(self.axis);
351 
352 // Expand out the size of the element to the max constraint size iff the max constraint is
353 // finite since we need an actual size to compute how elements should be laid out.
354 let actual_size =
355 if max_constraint_size.is_finite() && self.main_axis_size == MainAxisSize::Max {
356 max_constraint_size
357 } else {
358 allocated_size
359 };
360 
361 let remaining_space = (actual_size - allocated_size).max(0.);
362 
363 // If the axis size is set to max--ensure the flex takes up the max possible size it can
364 // while still respecting size constraints.
365 if self.main_axis_size == MainAxisSize::Max {
366 match self.axis {
367 Axis::Horizontal => size.set_x(max_constraint_size),
368 Axis::Vertical => size.set_y(max_constraint_size),
369 }
370 }
371 
372 if constraint.min.x().is_finite() {
373 size.set_x(size.x().max(constraint.min.x()));
374 }
375 if constraint.min.y().is_finite() {
376 size.set_y(size.y().max(constraint.min.y()));
377 }
378 
379 self.layout_state = Some(LayoutState::compute(
380 self.children.len(),
381 self.spacing,
382 remaining_space,
383 self.main_axis_alignment,
384 self.axis,
385 ));
386 
387 self.size = Some(size);
388 size
389 }
390 
391 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
392 for child in &mut self.children {
393 child.after_layout(ctx, app);
394 }
395 }
396 
397 fn paint(&mut self, mut origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
398 #[cfg(debug_assertions)]
399 let location_info = self
400 .container_constructor_location
401 .map(|loc| {
402 format!(
403 " (flex created at {}:{}:{})",
404 loc.file(),
405 loc.line(),
406 loc.column()
407 )
408 })
409 .unwrap_or_default();
410 #[cfg(not(debug_assertions))]
411 let location_info = "";
412 
413 let layout_state = self
414 .layout_state
415 .as_ref()
416 .unwrap_or_else(|| panic!("layout state should exist at paint time{location_info}"));
417 
418 self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index()));
419 
420 // If the axis is reversed, offset the origin position by the length of the flex along its main axis,
421 match self.orientation {
422 AxisOrientation::Normal => {
423 origin += layout_state.leading_space;
424 }
425 AxisOrientation::Reverse => {
426 let size_shift = size_along_axis(
427 main_axis_size(
428 self.size.unwrap_or_else(|| {
429 panic!("size should exist at paint time{location_info}")
430 }),
431 self.axis,
432 ),
433 self.axis,
434 );
435 origin += size_shift - layout_state.leading_space;
436 }
437 };
438 
439 let parent_cross_size = cross_axis_size(
440 self.size
441 .unwrap_or_else(|| panic!("size should exist at paint time{location_info}")),
442 self.axis,
443 );
444 
445 for (child_idx, child) in self.children.iter_mut().enumerate() {
446 #[cfg(debug_assertions)]
447 let child_location_info = self
448 .child_locations
449 .get(child_idx)
450 .map(|loc| {
451 format!(
452 " (child {} added at {}:{}:{})",
453 child_idx,
454 loc.file(),
455 loc.line(),
456 loc.column()
457 )
458 })
459 .unwrap_or_else(|| format!(" (flex container at {location_info})"));
460 #[cfg(not(debug_assertions))]
461 let (child_location_info, _) = ("", child_idx);
462 
463 let child_size = child.size().unwrap_or_else(|| {
464 panic!("child size should exist at paint time{child_location_info}")
465 });
466 let child_cross_size = cross_axis_size(child_size, self.axis);
467 
468 let child_cross_size = match self.cross_axis_alignment {
469 CrossAxisAlignment::Center => parent_cross_size / 2. - child_cross_size / 2.,
470 CrossAxisAlignment::Start => 0.,
471 CrossAxisAlignment::End => parent_cross_size - child_cross_size,
472 CrossAxisAlignment::Stretch => 0.,
473 };
474 
475 match self.orientation {
476 AxisOrientation::Normal => {
477 child.paint(
478 origin + size_along_axis(child_cross_size, self.axis.invert()),
479 ctx,
480 app,
481 );
482 origin += size_along_axis(main_axis_size(child_size, self.axis), self.axis);
483 origin += layout_state.between_space;
484 }
485 AxisOrientation::Reverse => {
486 origin -= size_along_axis(main_axis_size(child_size, self.axis), self.axis);
487 child.paint(
488 origin + size_along_axis(child_cross_size, self.axis.invert()),
489 ctx,
490 app,
491 );
492 origin -= layout_state.between_space;
493 }
494 }
495 }
496 }
497 
498 fn dispatch_event(
499 &mut self,
500 event: &DispatchedEvent,
501 ctx: &mut EventContext,
502 app: &AppContext,
503 ) -> bool {
504 let mut handled = false;
505 for child in &mut self.children {
506 let child_dispatch = child.dispatch_event(event, ctx, app);
507 handled |= child_dispatch;
508 }
509 handled
510 }
511 
512 fn size(&self) -> Option<Vector2F> {
513 self.size
514 }
515 
516 fn origin(&self) -> Option<Point> {
517 self.origin
518 }
519 
520 fn as_selectable_element(&self) -> Option<&dyn SelectableElement> {
521 Some(self as &dyn SelectableElement)
522 }
523 
524 #[cfg_attr(debug_assertions, track_caller)]
525 fn finish(self) -> Box<dyn Element>
526 where
527 Self: 'static + Sized,
528 {
529 #[cfg(debug_assertions)]
530 {
531 let mut s = self;
532 s.container_constructor_location = Some(std::panic::Location::caller());
533 Box::new(s)
534 }
535 #[cfg(not(debug_assertions))]
536 Box::new(self)
537 }
538 
539 #[cfg(any(test, feature = "test-util"))]
540 fn debug_text_content(&self) -> Option<String> {
541 let texts: Vec<String> = self
542 .children
543 .iter()
544 .filter_map(|child| child.debug_text_content())
545 .collect();
546 if texts.is_empty() {
547 None
548 } else {
549 let separator = if self.axis == Axis::Vertical {
550 "\n"
551 } else {
552 " "
553 };
554 Some(texts.join(separator))
555 }
556 }
557}
558 
559impl SelectableElement for Flex {
560 fn get_selection(
561 &self,
562 selection_start: Vector2F,
563 selection_end: Vector2F,
564 is_rect: IsRect,
565 ) -> Option<Vec<SelectionFragment>> {
566 let mut selection_fragments: Vec<SelectionFragment> = Vec::new();
567 for child in self.children.iter() {
568 if let Some(selectable_child) = child.as_selectable_element() {
569 if let Some(child_fragments) =
570 selectable_child.get_selection(selection_start, selection_end, is_rect)
571 {
572 // If we're adding new selection fragments from a new child in a Flex,
573 // add a separator between the previous child's and this child's selected text.
574 if let Some(last_fragment) = selection_fragments.last() {
575 let separator = if self.axis == Axis::Vertical {
576 "\n"
577 } else {
578 " "
579 };
580 selection_fragments.push(SelectionFragment {
581 text: separator.to_string(),
582 origin: last_fragment.origin,
583 });
584 }
585 selection_fragments.extend(child_fragments);
586 }
587 }
588 }
589 if !selection_fragments.is_empty() {
590 return Some(selection_fragments);
591 }
592 None
593 }
594 
595 fn expand_selection(
596 &self,
597 point: Vector2F,
598 direction: SelectionDirection,
599 unit: SelectionType,
600 word_boundaries_policy: &WordBoundariesPolicy,
601 ) -> Option<Vector2F> {
602 let mut expanded_selection = None;
603 for child in self.children.iter() {
604 if let Some(selectable_child) = child.as_selectable_element() {
605 if let Some(selection) = selectable_child.expand_selection(
606 point,
607 direction,
608 unit,
609 word_boundaries_policy,
610 ) {
611 match direction {
612 // If we're expanding backward, take the first child's expansion.
613 SelectionDirection::Backward => return Some(selection),
614 // Otherwise if we're expanding forward, take the last child's expansion.
615 SelectionDirection::Forward => {
616 expanded_selection = Some(selection);
617 }
618 }
619 }
620 }
621 }
622 expanded_selection
623 }
624 
625 fn is_point_semantically_before(
626 &self,
627 absolute_point: Vector2F,
628 absolute_point_other: Vector2F,
629 ) -> Option<bool> {
630 for child in self.children.iter() {
631 if let Some(selectable_child) = child.as_selectable_element() {
632 if let Some(is_point_semantically_before) = selectable_child
633 .is_point_semantically_before(absolute_point, absolute_point_other)
634 {
635 return Some(is_point_semantically_before);
636 }
637 }
638 }
639 None
640 }
641 
642 fn smart_select(
643 &self,
644 absolute_point: Vector2F,
645 smart_select_fn: crate::elements::SmartSelectFn,
646 ) -> Option<(Vector2F, Vector2F)> {
647 for child in self.children.iter() {
648 if let Some(selectable_child) = child.as_selectable_element() {
649 if let Some(selection) =
650 selectable_child.smart_select(absolute_point, smart_select_fn)
651 {
652 return Some(selection);
653 }
654 }
655 }
656 None
657 }
658 
659 fn calculate_clickable_bounds(&self, current_selection: Option<Selection>) -> Vec<RectF> {
660 let mut clickable_bounds = Vec::new();
661 for child in self.children.iter() {
662 if let Some(selectable_child) = child.as_selectable_element() {
663 clickable_bounds
664 .append(&mut selectable_child.calculate_clickable_bounds(current_selection));
665 }
666 }
667 clickable_bounds
668 }
669}
670 
671fn cross_axis_size(size: Vector2F, axis: Axis) -> f32 {
672 match axis {
673 Axis::Horizontal => size.y(),
674 Axis::Vertical => size.x(),
675 }
676}
677 
678fn main_axis_size(size: Vector2F, axis: Axis) -> f32 {
679 match axis {
680 Axis::Horizontal => size.x(),
681 Axis::Vertical => size.y(),
682 }
683}
684 
685/// Converts a coordinate to a `Vector2F` point given the axis on which the coordinate lies.
686fn size_along_axis(coordinate: f32, axis: Axis) -> Vector2F {
687 match axis {
688 Axis::Horizontal => vec2f(coordinate, 0.),
689 Axis::Vertical => vec2f(0., coordinate),
690 }
691}
692 
693struct FlexParentData {
694 flex: f32,
695 fit: FlexFit,
696}
697 
698#[derive(Debug, Clone, Copy)]
699enum FlexFit {
700 Tight,
701 Loose,
702}
703 
704/// Strategies to render children within a Flex when there is remaining space. This is _heavily_
705/// inspired from Flutter, see https://api.flutter.dev/flutter/widgets/Flex/mainAxisAlignment.html.
706#[derive(Copy, Clone, Debug, PartialEq, Eq)]
707pub enum MainAxisAlignment {
708 /// Place elements as close to the start of the Flex as possible.
709 Start,
710 /// Place the free space evenly between the children.
711 SpaceBetween,
712 /// Place the free space evenly between the children, as well as before and after the first and
713 /// last child.
714 SpaceEvenly,
715 /// Place as close the center of the Flex as possible.
716 Center,
717 /// Place elements as close to the end of the Flex as possible.
718 End,
719}
720 
721/// How much space a Flex element should occupy along the main axis.
722#[derive(Copy, Clone, Debug, PartialEq, Eq)]
723pub enum MainAxisSize {
724 /// Minimize the amount of free space along the main axis, subject to incoming size constraints.
725 Min,
726 /// Maximize the amount of free space along the main axis, subject to the incoming size
727 /// constraints. If there is any remaining space within the element, `MainAxisAlignment` is used
728 /// to determine how free space is distributed within the element.
729 Max,
730}
731 
732/// Where children should be placed along the cross axis in a Flex.
733#[derive(Copy, Clone, Debug, PartialEq, Eq)]
734pub enum CrossAxisAlignment {
735 /// Place the children so that their centers align with the middle of the cross axis.
736 Center,
737 /// Place the children with their start edge aligned with the start side of the cross axis.
738 Start,
739 /// Place the children as close to the end of the cross axis as possible.
740 End,
741 /// Require the children to fill the cross axis.
742 Stretch,
743}
744 
745pub struct Shrinkable {
746 parent_data: FlexParentData,
747 child: Box<dyn Element>,
748}
749 
750impl Shrinkable {
751 pub fn new(flex: f32, child: Box<dyn Element>) -> Self {
752 Shrinkable {
753 parent_data: FlexParentData {
754 flex,
755 fit: FlexFit::Loose,
756 },
757 child,
758 }
759 }
760}
761 
762impl Element for Shrinkable {
763 fn layout(
764 &mut self,
765 constraint: SizeConstraint,
766 ctx: &mut LayoutContext,
767 app: &AppContext,
768 ) -> Vector2F {
769 self.child.layout(constraint, ctx, app)
770 }
771 
772 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
773 self.child.after_layout(ctx, app);
774 }
775 
776 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
777 self.child.paint(origin, ctx, app);
778 }
779 
780 fn dispatch_event(
781 &mut self,
782 event: &DispatchedEvent,
783 ctx: &mut EventContext,
784 app: &AppContext,
785 ) -> bool {
786 self.child.dispatch_event(event, ctx, app)
787 }
788 
789 fn size(&self) -> Option<Vector2F> {
790 self.child.size()
791 }
792 
793 fn parent_data(&self) -> Option<&dyn Any> {
794 Some(&self.parent_data)
795 }
796 
797 fn origin(&self) -> Option<Point> {
798 self.child.origin()
799 }
800 
801 fn as_selectable_element(&self) -> Option<&dyn SelectableElement> {
802 Some(self as &dyn SelectableElement)
803 }
804 
805 #[cfg(any(test, feature = "test-util"))]
806 fn debug_text_content(&self) -> Option<String> {
807 self.child.debug_text_content()
808 }
809}
810 
811impl SelectableElement for Shrinkable {
812 fn get_selection(
813 &self,
814 selection_start: Vector2F,
815 selection_end: Vector2F,
816 is_rect: IsRect,
817 ) -> Option<Vec<SelectionFragment>> {
818 self.child
819 .as_selectable_element()
820 .and_then(|selectable_child| {
821 selectable_child.get_selection(selection_start, selection_end, is_rect)
822 })
823 }
824 
825 fn expand_selection(
826 &self,
827 point: Vector2F,
828 direction: SelectionDirection,
829 unit: SelectionType,
830 word_boundaries_policy: &WordBoundariesPolicy,
831 ) -> Option<Vector2F> {
832 self.child
833 .as_selectable_element()
834 .and_then(|selectable_child| {
835 selectable_child.expand_selection(point, direction, unit, word_boundaries_policy)
836 })
837 }
838 
839 fn is_point_semantically_before(
840 &self,
841 absolute_point: Vector2F,
842 absolute_point_other: Vector2F,
843 ) -> Option<bool> {
844 self.child
845 .as_selectable_element()
846 .and_then(|selectable_child| {
847 selectable_child.is_point_semantically_before(absolute_point, absolute_point_other)
848 })
849 }
850 
851 fn smart_select(
852 &self,
853 absolute_point: Vector2F,
854 smart_select_fn: crate::elements::SmartSelectFn,
855 ) -> Option<(Vector2F, Vector2F)> {
856 self.child
857 .as_selectable_element()
858 .and_then(|selectable_child| {
859 selectable_child.smart_select(absolute_point, smart_select_fn)
860 })
861 }
862 
863 fn calculate_clickable_bounds(&self, current_selection: Option<Selection>) -> Vec<RectF> {
864 self.child
865 .as_selectable_element()
866 .map(|selectable_child| selectable_child.calculate_clickable_bounds(current_selection))
867 .unwrap_or_default()
868 }
869}
870 
871/// A flexible child that will take up all available space in a `Flex`, regardless of whether its contained element
872/// wants to grow. Behaves identically to a `Shrinkable` containing an element with infinite width.
873pub struct Expanded {
874 parent_data: FlexParentData,
875 child: Box<dyn Element>,
876}
877 
878impl Expanded {
879 pub fn new(flex: f32, child: Box<dyn Element>) -> Self {
880 Expanded {
881 parent_data: FlexParentData {
882 flex,
883 fit: FlexFit::Tight,
884 },
885 child,
886 }
887 }
888}
889 
890impl Element for Expanded {
891 fn layout(
892 &mut self,
893 constraint: SizeConstraint,
894 ctx: &mut LayoutContext,
895 app: &AppContext,
896 ) -> Vector2F {
897 self.child.layout(constraint, ctx, app)
898 }
899 
900 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
901 self.child.after_layout(ctx, app);
902 }
903 
904 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
905 self.child.paint(origin, ctx, app);
906 }
907 
908 fn dispatch_event(
909 &mut self,
910 event: &DispatchedEvent,
911 ctx: &mut EventContext,
912 app: &AppContext,
913 ) -> bool {
914 self.child.dispatch_event(event, ctx, app)
915 }
916 
917 fn size(&self) -> Option<Vector2F> {
918 self.child.size()
919 }
920 
921 fn parent_data(&self) -> Option<&dyn Any> {
922 Some(&self.parent_data)
923 }
924 
925 fn origin(&self) -> Option<Point> {
926 self.child.origin()
927 }
928 
929 fn as_selectable_element(&self) -> Option<&dyn SelectableElement> {
930 Some(self as &dyn SelectableElement)
931 }
932 
933 #[cfg(any(test, feature = "test-util"))]
934 fn debug_text_content(&self) -> Option<String> {
935 self.child.debug_text_content()
936 }
937}
938 
939impl SelectableElement for Expanded {
940 fn get_selection(
941 &self,
942 selection_start: Vector2F,
943 selection_end: Vector2F,
944 is_rect: IsRect,
945 ) -> Option<Vec<SelectionFragment>> {
946 self.child
947 .as_selectable_element()
948 .and_then(|selectable_child| {
949 selectable_child.get_selection(selection_start, selection_end, is_rect)
950 })
951 }
952 
953 fn expand_selection(
954 &self,
955 point: Vector2F,
956 direction: SelectionDirection,
957 unit: SelectionType,
958 word_boundaries_policy: &WordBoundariesPolicy,
959 ) -> Option<Vector2F> {
960 self.child
961 .as_selectable_element()
962 .and_then(|selectable_child| {
963 selectable_child.expand_selection(point, direction, unit, word_boundaries_policy)
964 })
965 }
966 
967 fn is_point_semantically_before(
968 &self,
969 absolute_point: Vector2F,
970 absolute_point_other: Vector2F,
971 ) -> Option<bool> {
972 self.child
973 .as_selectable_element()
974 .and_then(|selectable_child| {
975 selectable_child.is_point_semantically_before(absolute_point, absolute_point_other)
976 })
977 }
978 
979 fn smart_select(
980 &self,
981 absolute_point: Vector2F,
982 smart_select_fn: crate::elements::SmartSelectFn,
983 ) -> Option<(Vector2F, Vector2F)> {
984 self.child
985 .as_selectable_element()
986 .and_then(|selectable_child| {
987 selectable_child.smart_select(absolute_point, smart_select_fn)
988 })
989 }
990 
991 fn calculate_clickable_bounds(&self, current_selection: Option<Selection>) -> Vec<RectF> {
992 self.child
993 .as_selectable_element()
994 .map(|selectable_child| selectable_child.calculate_clickable_bounds(current_selection))
995 .unwrap_or_default()
996 }
997}
998 
999#[cfg(test)]
1000#[path = "mod_test.rs"]
1001mod tests;
1002