1use crate::core::alignment::{self, Alignment};
23use crate::core::border::{self, Border};
24use crate::core::event::{self, Event};
25use crate::core::gradient::{self, Gradient};
26use crate::core::layout;
27use crate::core::mouse;
28use crate::core::overlay;
29use crate::core::renderer;
30use crate::core::widget::tree::{self, Tree};
31use crate::core::widget::{self, Id, Operation};
32use crate::core::{
33 self, color, Background, Clipboard, Color, Element, Layout, Length,
34 Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
35 Widget,
36};
37use crate::runtime::task::{self, Task};
38
39#[allow(missing_debug_implementations)]
61pub struct Container<
62 'a,
63 Message,
64 Theme = crate::Theme,
65 Renderer = crate::Renderer,
66> where
67 Theme: Catalog,
68 Renderer: core::Renderer,
69{
70 padding: Padding,
71 width: Length,
72 height: Length,
73 max_width: f32,
74 max_height: f32,
75 horizontal_alignment: alignment::Horizontal,
76 vertical_alignment: alignment::Vertical,
77 clip: bool,
78 content: Element<'a, Message, Theme, Renderer>,
79 class: Theme::Class<'a>,
80}
81
82impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer>
83where
84 Theme: Catalog,
85 Renderer: core::Renderer,
86{
87 pub fn new(
89 content: impl Into<Element<'a, Message, Theme, Renderer>>,
90 ) -> Self {
91 let content = content.into();
92 let size = content.as_widget().size_hint();
93
94 Container {
95 padding: Padding::ZERO,
96 width: size.width.fluid(),
97 height: size.height.fluid(),
98 max_width: f32::INFINITY,
99 max_height: f32::INFINITY,
100 horizontal_alignment: alignment::Horizontal::Left,
101 vertical_alignment: alignment::Vertical::Top,
102 clip: false,
103 class: Theme::default(),
104 content,
105 }
106 }
107
108 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
110 self.padding = padding.into();
111 self
112 }
113
114 pub fn width(mut self, width: impl Into<Length>) -> Self {
116 self.width = width.into();
117 self
118 }
119
120 pub fn height(mut self, height: impl Into<Length>) -> Self {
122 self.height = height.into();
123 self
124 }
125
126 pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
128 self.max_width = max_width.into().0;
129 self
130 }
131
132 pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
134 self.max_height = max_height.into().0;
135 self
136 }
137
138 pub fn center_x(self, width: impl Into<Length>) -> Self {
140 self.width(width).align_x(alignment::Horizontal::Center)
141 }
142
143 pub fn center_y(self, height: impl Into<Length>) -> Self {
145 self.height(height).align_y(alignment::Vertical::Center)
146 }
147
148 pub fn center(self, length: impl Into<Length>) -> Self {
156 let length = length.into();
157
158 self.center_x(length).center_y(length)
159 }
160
161 pub fn align_left(self, width: impl Into<Length>) -> Self {
163 self.width(width).align_x(alignment::Horizontal::Left)
164 }
165
166 pub fn align_right(self, width: impl Into<Length>) -> Self {
168 self.width(width).align_x(alignment::Horizontal::Right)
169 }
170
171 pub fn align_top(self, height: impl Into<Length>) -> Self {
173 self.height(height).align_y(alignment::Vertical::Top)
174 }
175
176 pub fn align_bottom(self, height: impl Into<Length>) -> Self {
178 self.height(height).align_y(alignment::Vertical::Bottom)
179 }
180
181 pub fn align_x(
183 mut self,
184 alignment: impl Into<alignment::Horizontal>,
185 ) -> Self {
186 self.horizontal_alignment = alignment.into();
187 self
188 }
189
190 pub fn align_y(
192 mut self,
193 alignment: impl Into<alignment::Vertical>,
194 ) -> Self {
195 self.vertical_alignment = alignment.into();
196 self
197 }
198
199 pub fn clip(mut self, clip: bool) -> Self {
202 self.clip = clip;
203 self
204 }
205
206 #[must_use]
208 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
209 where
210 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
211 {
212 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
213 self
214 }
215
216 #[must_use]
218 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
219 self.class = class.into();
220 self
221 }
222}
223
224impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
225 for Container<'a, Message, Theme, Renderer>
226where
227 Theme: Catalog,
228 Renderer: core::Renderer,
229{
230 fn tag(&self) -> tree::Tag {
231 self.content.as_widget().tag()
232 }
233
234 fn state(&self) -> tree::State {
235 self.content.as_widget().state()
236 }
237
238 fn children(&self) -> Vec<Tree> {
239 self.content.as_widget().children()
240 }
241
242 fn diff(&mut self, tree: &mut Tree) {
243 self.content.as_widget_mut().diff(tree);
244 }
245
246 fn size(&self) -> Size<Length> {
247 Size {
248 width: self.width,
249 height: self.height,
250 }
251 }
252
253 fn layout(
254 &self,
255 tree: &mut Tree,
256 renderer: &Renderer,
257 limits: &layout::Limits,
258 ) -> layout::Node {
259 layout(
260 limits,
261 self.width,
262 self.height,
263 self.max_width,
264 self.max_height,
265 self.padding,
266 self.horizontal_alignment,
267 self.vertical_alignment,
268 |limits| self.content.as_widget().layout(tree, renderer, limits),
269 )
270 }
271
272 fn operate(
273 &self,
274 tree: &mut Tree,
275 layout: Layout<'_>,
276 renderer: &Renderer,
277 operation: &mut dyn Operation,
278 ) {
279 operation.container(
280 self.content.as_widget().id().as_ref(),
281 layout.bounds(),
282 &mut |operation| {
283 self.content.as_widget().operate(
284 tree,
285 layout
286 .children()
287 .next()
288 .unwrap()
289 .with_virtual_offset(layout.virtual_offset()),
290 renderer,
291 operation,
292 );
293 },
294 );
295 }
296
297 fn on_event(
298 &mut self,
299 tree: &mut Tree,
300 event: Event,
301 layout: Layout<'_>,
302 cursor: mouse::Cursor,
303 renderer: &Renderer,
304 clipboard: &mut dyn Clipboard,
305 shell: &mut Shell<'_, Message>,
306 viewport: &Rectangle,
307 ) -> event::Status {
308 self.content.as_widget_mut().on_event(
309 tree,
310 event,
311 layout
312 .children()
313 .next()
314 .unwrap()
315 .with_virtual_offset(layout.virtual_offset()),
316 cursor,
317 renderer,
318 clipboard,
319 shell,
320 viewport,
321 )
322 }
323
324 fn mouse_interaction(
325 &self,
326 tree: &Tree,
327 layout: Layout<'_>,
328 cursor: mouse::Cursor,
329 viewport: &Rectangle,
330 renderer: &Renderer,
331 ) -> mouse::Interaction {
332 self.content.as_widget().mouse_interaction(
333 tree,
334 layout
335 .children()
336 .next()
337 .unwrap()
338 .with_virtual_offset(layout.virtual_offset()),
339 cursor,
340 viewport,
341 renderer,
342 )
343 }
344
345 fn draw(
346 &self,
347 tree: &Tree,
348 renderer: &mut Renderer,
349 theme: &Theme,
350 renderer_style: &renderer::Style,
351 layout: Layout<'_>,
352 cursor: mouse::Cursor,
353 viewport: &Rectangle,
354 ) {
355 let bounds = layout.bounds();
356 let style = theme.style(&self.class);
357
358 if let Some(clipped_viewport) = bounds.intersection(viewport) {
359 draw_background(renderer, &style, bounds);
360
361 self.content.as_widget().draw(
362 tree,
363 renderer,
364 theme,
365 &renderer::Style {
366 icon_color: style
367 .icon_color
368 .unwrap_or(renderer_style.icon_color),
369 text_color: style
370 .text_color
371 .unwrap_or(renderer_style.text_color),
372 scale_factor: renderer_style.scale_factor,
373 },
374 layout
375 .children()
376 .next()
377 .unwrap()
378 .with_virtual_offset(layout.virtual_offset()),
379 cursor,
380 if self.clip {
381 &clipped_viewport
382 } else {
383 viewport
384 },
385 );
386 }
387 }
388
389 fn overlay<'b>(
390 &'b mut self,
391 tree: &'b mut Tree,
392 layout: Layout<'_>,
393 renderer: &Renderer,
394 translation: Vector,
395 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
396 self.content.as_widget_mut().overlay(
397 tree,
398 layout
399 .children()
400 .next()
401 .unwrap()
402 .with_virtual_offset(layout.virtual_offset()),
403 renderer,
404 translation,
405 )
406 }
407
408 #[cfg(feature = "a11y")]
409 fn a11y_nodes(
411 &self,
412 layout: Layout<'_>,
413 state: &Tree,
414 cursor: mouse::Cursor,
415 ) -> iced_accessibility::A11yTree {
416 let c_layout = layout.children().next().unwrap();
417
418 self.content.as_widget().a11y_nodes(
419 c_layout.with_virtual_offset(layout.virtual_offset()),
420 state,
421 cursor,
422 )
423 }
424
425 fn drag_destinations(
426 &self,
427 state: &Tree,
428 layout: Layout<'_>,
429 renderer: &Renderer,
430 dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles,
431 ) {
432 if let Some(l) = layout.children().next() {
433 self.content.as_widget().drag_destinations(
434 state,
435 l.with_virtual_offset(layout.virtual_offset()),
436 renderer,
437 dnd_rectangles,
438 );
439 }
440 }
441
442 fn id(&self) -> Option<Id> {
443 self.content.as_widget().id().clone()
444 }
445
446 fn set_id(&mut self, id: Id) {
447 self.content.as_widget_mut().set_id(id);
448 }
449}
450
451impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
452 for Element<'a, Message, Theme, Renderer>
453where
454 Message: 'a,
455 Theme: Catalog + 'a,
456 Renderer: core::Renderer + 'a,
457{
458 fn from(
459 column: Container<'a, Message, Theme, Renderer>,
460 ) -> Element<'a, Message, Theme, Renderer> {
461 Element::new(column)
462 }
463}
464
465pub fn layout(
467 limits: &layout::Limits,
468 width: Length,
469 height: Length,
470 max_width: f32,
471 max_height: f32,
472 padding: Padding,
473 horizontal_alignment: alignment::Horizontal,
474 vertical_alignment: alignment::Vertical,
475 layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
476) -> layout::Node {
477 layout::positioned(
478 &limits.max_width(max_width).max_height(max_height),
479 width,
480 height,
481 padding,
482 |limits| layout_content(&limits.loose()),
483 |content, size| {
484 content.align(
485 Alignment::from(horizontal_alignment),
486 Alignment::from(vertical_alignment),
487 size,
488 )
489 },
490 )
491}
492
493pub fn draw_background<Renderer>(
495 renderer: &mut Renderer,
496 style: &Style,
497 bounds: Rectangle,
498) where
499 Renderer: core::Renderer,
500{
501 if style.background.is_some()
502 || style.border.width > 0.0
503 || style.shadow.color.a > 0.0
504 {
505 renderer.fill_quad(
506 renderer::Quad {
507 bounds,
508 border: style.border,
509 shadow: style.shadow,
510 },
511 style
512 .background
513 .unwrap_or(Background::Color(Color::TRANSPARENT)),
514 );
515 }
516}
517
518pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
521 struct VisibleBounds {
522 target: widget::Id,
523 depth: usize,
524 scrollables: Vec<(Vector, Rectangle, usize)>,
525 bounds: Option<Rectangle>,
526 }
527
528 impl Operation<Option<Rectangle>> for VisibleBounds {
529 fn scrollable(
530 &mut self,
531 _state: &mut dyn widget::operation::Scrollable,
532 _id: Option<&widget::Id>,
533 bounds: Rectangle,
534 _content_bounds: Rectangle,
535 translation: Vector,
536 ) {
537 match self.scrollables.last() {
538 Some((last_translation, last_viewport, _depth)) => {
539 let viewport = last_viewport
540 .intersection(&(bounds - *last_translation))
541 .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO));
542
543 self.scrollables.push((
544 translation + *last_translation,
545 viewport,
546 self.depth,
547 ));
548 }
549 None => {
550 self.scrollables.push((translation, bounds, self.depth));
551 }
552 }
553 }
554
555 fn container(
556 &mut self,
557 id: Option<&widget::Id>,
558 bounds: Rectangle,
559 operate_on_children: &mut dyn FnMut(
560 &mut dyn Operation<Option<Rectangle>>,
561 ),
562 ) {
563 if self.bounds.is_some() {
564 return;
565 }
566
567 if id == Some(&self.target) {
568 match self.scrollables.last() {
569 Some((translation, viewport, _)) => {
570 self.bounds =
571 viewport.intersection(&(bounds - *translation));
572 }
573 None => {
574 self.bounds = Some(bounds);
575 }
576 }
577
578 return;
579 }
580
581 self.depth += 1;
582
583 operate_on_children(self);
584
585 self.depth -= 1;
586
587 match self.scrollables.last() {
588 Some((_, _, depth)) if self.depth == *depth => {
589 let _ = self.scrollables.pop();
590 }
591 _ => {}
592 }
593 }
594
595 fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> {
596 widget::operation::Outcome::Some(self.bounds)
597 }
598 }
599
600 task::widget(VisibleBounds {
601 target: id,
602 depth: 0,
603 scrollables: Vec::new(),
604 bounds: None,
605 })
606}
607
608#[derive(Debug, Clone, Copy, PartialEq, Default)]
610pub struct Style {
611 pub icon_color: Option<Color>,
613 pub text_color: Option<Color>,
615 pub background: Option<Background>,
617 pub border: Border,
619 pub shadow: Shadow,
621}
622
623impl Style {
624 pub fn color(self, color: impl Into<Color>) -> Self {
626 Self {
627 text_color: Some(color.into()),
628 ..self
629 }
630 }
631
632 pub fn border(self, border: impl Into<Border>) -> Self {
634 Self {
635 border: border.into(),
636 ..self
637 }
638 }
639
640 pub fn background(self, background: impl Into<Background>) -> Self {
642 Self {
643 background: Some(background.into()),
644 icon_color: None,
645 text_color: None,
646 ..self
647 }
648 }
649
650 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
652 Self {
653 shadow: shadow.into(),
654 ..self
655 }
656 }
657}
658
659impl From<Color> for Style {
660 fn from(color: Color) -> Self {
661 Self::default().background(color)
662 }
663}
664
665impl From<Gradient> for Style {
666 fn from(gradient: Gradient) -> Self {
667 Self::default().background(gradient)
668 }
669}
670
671impl From<gradient::Linear> for Style {
672 fn from(gradient: gradient::Linear) -> Self {
673 Self::default().background(gradient)
674 }
675}
676
677pub trait Catalog {
679 type Class<'a>;
681
682 fn default<'a>() -> Self::Class<'a>;
684
685 fn style(&self, class: &Self::Class<'_>) -> Style;
687}
688
689pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
691
692impl<'a, Theme> From<Style> for StyleFn<'a, Theme> {
693 fn from(style: Style) -> Self {
694 Box::new(move |_theme| style)
695 }
696}
697
698impl Catalog for Theme {
699 type Class<'a> = StyleFn<'a, Self>;
700
701 fn default<'a>() -> Self::Class<'a> {
702 Box::new(transparent)
703 }
704
705 fn style(&self, class: &Self::Class<'_>) -> Style {
706 class(self)
707 }
708}
709
710pub fn transparent<Theme>(_theme: &Theme) -> Style {
712 Style::default()
713}
714
715pub fn background(background: impl Into<Background>) -> Style {
717 Style::default().background(background)
718}
719
720pub fn rounded_box(theme: &Theme) -> Style {
722 let palette = theme.extended_palette();
723
724 Style {
725 icon_color: None,
726 background: Some(palette.background.weak.color.into()),
727 border: border::rounded(2),
728 ..Style::default()
729 }
730}
731
732pub fn bordered_box(theme: &Theme) -> Style {
734 let palette = theme.extended_palette();
735
736 Style {
737 background: Some(palette.background.weak.color.into()),
738 border: Border {
739 width: 1.0,
740 radius: 0.0.into(),
741 color: palette.background.strong.color,
742 },
743 ..Style::default()
744 }
745}
746
747pub fn dark(_theme: &Theme) -> Style {
749 Style {
750 background: Some(color!(0x111111).into()),
751 text_color: Some(Color::WHITE),
752 border: border::rounded(2),
753 ..Style::default()
754 }
755}