1use figures::units::UPx;
4use figures::{IntoSigned, Rect, Round, ScreenScale, Size, Zero};
5
6use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, Trackable};
7use crate::styles::components::IntrinsicPadding;
8use crate::styles::FlexibleDimension;
9use crate::value::{Generation, IntoValue, Value};
10use crate::widget::{ChildrenSyncChange, MountedWidget, Widget, WidgetList, WidgetRef};
11use crate::widgets::grid::{GridDimension, GridLayout, Orientation};
12use crate::widgets::{Expand, Resize};
13use crate::ConstraintLimit;
14
15#[derive(Debug)]
18pub struct Stack {
19 orientation: Orientation,
20 pub children: Value<WidgetList>,
22 pub gutter: Value<FlexibleDimension>,
24 layout: GridLayout,
25 layout_generation: Option<Generation>,
26 synced_children: Vec<MountedWidget>,
27}
28
29impl Stack {
30 pub fn new(orientation: Orientation, widgets: impl IntoValue<WidgetList>) -> Self {
32 Self {
33 orientation,
34 children: widgets.into_value(),
35 gutter: Value::Constant(FlexibleDimension::Auto),
36 layout: GridLayout::new(orientation),
37 layout_generation: None,
38 synced_children: Vec::new(),
39 }
40 }
41
42 pub fn columns(widgets: impl IntoValue<WidgetList>) -> Self {
44 Self::new(Orientation::Column, widgets)
45 }
46
47 pub fn rows(widgets: impl IntoValue<WidgetList>) -> Self {
49 Self::new(Orientation::Row, widgets)
50 }
51
52 #[must_use]
54 pub fn gutter(mut self, gutter: impl IntoValue<FlexibleDimension>) -> Self {
55 self.gutter = gutter.into_value();
56 self
57 }
58
59 fn synchronize_children(&mut self, context: &mut EventContext<'_>) {
60 let current_generation = self.children.generation();
61 self.children.invalidate_when_changed(context);
62 if current_generation.map_or_else(
63 || self.children.map(WidgetList::len) != self.layout.len(),
64 |gen| Some(gen) != self.layout_generation,
65 ) {
66 self.layout_generation = self.children.generation();
67 self.children.map(|children| {
68 children.synchronize_with(
69 &mut self.synced_children,
70 |this, index| this.get(index).map(MountedWidget::instance),
71 |this, change| match change {
72 ChildrenSyncChange::Insert(index, widget) => {
73 let guard = widget.lock();
75 let (mut widget, dimension) = if let Some((weight, expand)) =
76 guard.downcast_ref::<Expand>().and_then(|expand| {
77 expand
78 .weight(self.orientation == Orientation::Row)
79 .map(|weight| (weight, expand))
80 }) {
81 (expand.child().clone(), GridDimension::Fractional { weight })
82 } else if let Some((child, size)) =
83 guard.downcast_ref::<Resize>().and_then(|r| {
84 let range = match self.layout.orientation {
85 Orientation::Row => r.height,
86 Orientation::Column => r.width,
87 };
88 range.minimum().map(|size| {
89 (r.child().clone(), GridDimension::Measured { size })
90 })
91 })
92 {
93 (child, size)
94 } else {
95 (WidgetRef::new(widget.clone()), GridDimension::FitContent)
96 };
97 drop(guard);
98 this.insert(index, widget.mounted(context));
99
100 self.layout
101 .insert(index, dimension, context.kludgine.scale());
102 }
103 ChildrenSyncChange::Swap(a, b) => {
104 this.swap(a, b);
105 self.layout.swap(a, b);
106 }
107 ChildrenSyncChange::Truncate(length) => {
108 for removed in this.drain(length..) {
109 context.remove_child(&removed);
110 }
111 self.layout.truncate(length);
112 }
113 },
114 );
115 });
116 }
117 }
118}
119
120impl Widget for Stack {
121 fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
122 for (layout, child) in self.layout.iter().zip(&self.synced_children) {
123 if layout.size > 0 {
124 context.for_other(child).redraw();
125 }
126 }
127 }
128
129 fn mounted(&mut self, context: &mut EventContext<'_>) {
130 for child in &mut self.synced_children {
131 child.remount_if_needed(context);
132 }
133 }
134
135 fn layout(
136 &mut self,
137 available_space: Size<ConstraintLimit>,
138 context: &mut LayoutContext<'_, '_, '_, '_>,
139 ) -> Size<UPx> {
140 self.synchronize_children(&mut context.as_event_context());
141
142 self.gutter.invalidate_when_changed(context);
143 let gutter = match self.gutter.get() {
144 FlexibleDimension::Auto => context.get(&IntrinsicPadding),
145 FlexibleDimension::Dimension(dimension) => dimension,
146 }
147 .into_upx(context.gfx.scale())
148 .round();
149
150 let content_size = self.layout.update(
151 available_space,
152 gutter,
153 context.gfx.scale(),
154 |child_index, _element, constraints, persist| {
155 let mut context = context.for_other(&self.synced_children[child_index]);
156 if !persist {
157 context = context.as_temporary();
158 }
159 context.layout(constraints)
160 },
161 );
162
163 for (layout, child) in self.layout.iter().zip(&self.synced_children) {
164 if layout.size > 0 {
165 context.set_child_layout(
166 child,
167 Rect::new(
168 self.layout
169 .orientation
170 .make_point(layout.offset, UPx::ZERO)
171 .into_signed(),
172 self.layout
173 .orientation
174 .make_size(layout.size, self.layout.others[0])
175 .into_signed(),
176 ),
177 );
178 }
179 }
180
181 content_size
182 }
183
184 fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 fmt.debug_struct("Stack")
186 .field("orientation", &self.layout.orientation)
187 .field("children", &self.children)
188 .finish()
189 }
190}