1use crate::iced;
3use iced::core::alignment::{self, Alignment};
4use iced::core::event::{self, Event};
5use iced::core::widget::{Operation, Tree};
6use iced::core::{
7 Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
8 layout, mouse, overlay, renderer, widget,
9};
10
11#[allow(missing_debug_implementations)]
12#[must_use]
13pub struct MenuColumn<'a, Message, Renderer = iced::Renderer> {
14 spacing: f32,
15 padding: Padding,
16 width: Length,
17 height: Length,
18 max_width: f32,
19 align: Alignment,
20 clip: bool,
21 children: Vec<Element<'a, Message, crate::Theme, Renderer>>,
22}
23
24impl<'a, Message, Renderer> MenuColumn<'a, Message, Renderer>
25where
26 Renderer: iced::core::Renderer,
27{
28 pub fn new() -> Self {
30 Self::from_vec(Vec::new())
31 }
32
33 pub fn with_capacity(capacity: usize) -> Self {
35 Self::from_vec(Vec::with_capacity(capacity))
36 }
37
38 pub fn with_children(
40 children: impl IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>,
41 ) -> Self {
42 let iterator = children.into_iter();
43
44 Self::with_capacity(iterator.size_hint().0).extend(iterator)
45 }
46
47 pub fn from_vec(children: Vec<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
55 Self {
56 spacing: 0.0,
57 padding: Padding::ZERO,
58 width: Length::Shrink,
59 height: Length::Shrink,
60 max_width: f32::INFINITY,
61 align: Alignment::Start,
62 clip: false,
63 children,
64 }
65 }
66
67 pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
73 self.spacing = amount.into().0;
74 self
75 }
76
77 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
79 self.padding = padding.into();
80 self
81 }
82
83 pub fn width(mut self, width: impl Into<Length>) -> Self {
85 self.width = width.into();
86 self
87 }
88
89 pub fn height(mut self, height: impl Into<Length>) -> Self {
91 self.height = height.into();
92 self
93 }
94
95 pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
97 self.max_width = max_width.into().0;
98 self
99 }
100
101 pub fn align_x(mut self, align: impl Into<alignment::Horizontal>) -> Self {
103 self.align = Alignment::from(align.into());
104 self
105 }
106
107 pub fn clip(mut self, clip: bool) -> Self {
110 self.clip = clip;
111 self
112 }
113
114 pub fn push(mut self, child: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
116 let child = child.into();
117 let child_size = child.as_widget().size_hint();
118
119 self.width = self.width.enclose(child_size.width);
120 self.height = self.height.enclose(child_size.height);
121
122 self.children.push(child);
123 self
124 }
125
126 #[must_use]
128 pub fn push_maybe(
129 self,
130 child: Option<impl Into<Element<'a, Message, crate::Theme, Renderer>>>,
131 ) -> Self {
132 if let Some(child) = child {
133 self.push(child)
134 } else {
135 self
136 }
137 }
138
139 pub fn extend(
141 self,
142 children: impl IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>,
143 ) -> Self {
144 children.into_iter().fold(self, Self::push)
145 }
146}
147
148impl<Message, Renderer> Default for MenuColumn<'_, Message, Renderer>
149where
150 Renderer: iced::core::Renderer,
151{
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157impl<'a, Message, Renderer: iced::core::Renderer>
158 FromIterator<Element<'a, Message, crate::Theme, Renderer>>
159 for MenuColumn<'a, Message, Renderer>
160{
161 fn from_iter<T: IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>>(
162 iter: T,
163 ) -> Self {
164 Self::with_children(iter)
165 }
166}
167
168impl<Message, Renderer> Widget<Message, crate::Theme, Renderer>
169 for MenuColumn<'_, Message, Renderer>
170where
171 Renderer: iced::core::Renderer,
172{
173 fn children(&self) -> Vec<Tree> {
174 self.children.iter().map(Tree::new).collect()
175 }
176
177 fn diff(&mut self, tree: &mut Tree) {
178 tree.diff_children(self.children.as_mut_slice());
179 }
180
181 fn size(&self) -> Size<Length> {
182 Size {
183 width: self.width,
184 height: self.height,
185 }
186 }
187
188 fn layout(
189 &mut self,
190 tree: &mut Tree,
191 renderer: &Renderer,
192 limits: &layout::Limits,
193 ) -> layout::Node {
194 let limits = limits.max_width(self.max_width);
195
196 layout::flex::resolve(
197 layout::flex::Axis::Vertical,
198 renderer,
199 &limits,
200 self.width,
201 self.height,
202 self.padding,
203 self.spacing,
204 self.align,
205 &mut self.children,
206 &mut tree.children,
207 )
208 }
209
210 fn operate(
211 &mut self,
212 tree: &mut Tree,
213 layout: Layout<'_>,
214 renderer: &Renderer,
215 operation: &mut dyn Operation,
216 ) {
217 operation.container(None, layout.bounds());
218 operation.traverse(&mut |operation| {
219 self.children
220 .iter_mut()
221 .zip(&mut tree.children)
222 .zip(layout.children())
223 .for_each(|((child, state), c_layout)| {
224 child.as_widget_mut().operate(
225 state,
226 c_layout.with_virtual_offset(layout.virtual_offset()),
227 renderer,
228 operation,
229 );
230 });
231 });
232 }
233
234 fn update(
235 &mut self,
236 tree: &mut Tree,
237 event: &Event,
238 layout: Layout<'_>,
239 cursor: mouse::Cursor,
240 renderer: &Renderer,
241 clipboard: &mut dyn Clipboard,
242 shell: &mut Shell<'_, Message>,
243 viewport: &Rectangle,
244 ) {
245 for (((i, child), state), c_layout) in self
246 .children
247 .iter_mut()
248 .enumerate()
249 .zip(&mut tree.children)
250 .zip(layout.children())
251 {
252 child.as_widget_mut().update(
253 state,
254 &event,
255 c_layout.with_virtual_offset(layout.virtual_offset()),
256 cursor,
257 renderer,
258 clipboard,
259 shell,
260 viewport,
261 );
262 }
263 }
264
265 fn mouse_interaction(
266 &self,
267 tree: &Tree,
268 layout: Layout<'_>,
269 cursor: mouse::Cursor,
270 viewport: &Rectangle,
271 renderer: &Renderer,
272 ) -> mouse::Interaction {
273 self.children
274 .iter()
275 .zip(&tree.children)
276 .zip(layout.children())
277 .map(|((child, state), c_layout)| {
278 child.as_widget().mouse_interaction(
279 state,
280 c_layout.with_virtual_offset(layout.virtual_offset()),
281 cursor,
282 viewport,
283 renderer,
284 )
285 })
286 .max()
287 .unwrap_or_default()
288 }
289
290 fn draw(
291 &self,
292 tree: &Tree,
293 renderer: &mut Renderer,
294 theme: &crate::Theme,
295 style: &renderer::Style,
296 layout: Layout<'_>,
297 cursor: mouse::Cursor,
298 viewport: &Rectangle,
299 ) {
300 if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
301 let viewport = if self.clip {
302 &clipped_viewport
303 } else {
304 viewport
305 };
306
307 for (i, ((child, state), c_layout)) in self
308 .children
309 .iter()
310 .zip(&tree.children)
311 .zip(layout.children())
312 .filter(|(_, layout)| layout.bounds().intersects(viewport))
313 .enumerate()
314 {
315 let t = theme.with_list_item_position(if self.children.len() == 1 {
316 Some((Alignment::Center, i))
317 } else if 0 == i {
318 Some((Alignment::Start, i))
319 } else if i == self.children.len() - 1 {
320 Some((Alignment::End, i))
321 } else {
322 None
323 });
324 child.as_widget().draw(
325 state,
326 renderer,
327 &t,
328 style,
329 c_layout.with_virtual_offset(layout.virtual_offset()),
330 cursor,
331 viewport,
332 );
333 }
334 }
335 }
336
337 fn overlay<'b>(
338 &'b mut self,
339 tree: &'b mut Tree,
340 layout: Layout<'b>,
341 renderer: &Renderer,
342 viewport: &Rectangle,
343 translation: Vector,
344 ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
345 overlay::from_children(
346 &mut self.children,
347 tree,
348 layout,
349 renderer,
350 viewport,
351 translation,
352 )
353 }
354
355 #[cfg(feature = "a11y")]
356 fn a11y_nodes(
358 &self,
359 layout: Layout<'_>,
360 state: &Tree,
361 cursor: mouse::Cursor,
362 ) -> iced_accessibility::A11yTree {
363 use iced_accessibility::A11yTree;
364 A11yTree::join(
365 self.children
366 .iter()
367 .zip(layout.children())
368 .zip(state.children.iter())
369 .map(|((c, c_layout), state)| {
370 c.as_widget().a11y_nodes(
371 c_layout.with_virtual_offset(layout.virtual_offset()),
372 state,
373 cursor,
374 )
375 }),
376 )
377 }
378
379 fn drag_destinations(
380 &self,
381 state: &Tree,
382 layout: Layout<'_>,
383 renderer: &Renderer,
384 dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles,
385 ) {
386 for ((e, c_layout), state) in self
387 .children
388 .iter()
389 .zip(layout.children())
390 .zip(state.children.iter())
391 {
392 e.as_widget().drag_destinations(
393 state,
394 c_layout.with_virtual_offset(layout.virtual_offset()),
395 renderer,
396 dnd_rectangles,
397 );
398 }
399 }
400}
401
402impl<'a, Message, Renderer> From<MenuColumn<'a, Message, Renderer>>
403 for Element<'a, Message, crate::Theme, Renderer>
404where
405 Message: 'a,
406 Renderer: iced::core::Renderer + 'a,
407{
408 fn from(column: MenuColumn<'a, Message, Renderer>) -> Self {
409 Self::new(column)
410 }
411}