cosmic_time/widget/
container.rs

1//! Decorate content and apply alignment.
2use iced_native::alignment::{self, Alignment};
3use iced_native::color;
4use iced_native::event::{self, Event};
5use iced_native::layout;
6use iced_native::mouse;
7use iced_native::overlay;
8use iced_native::renderer;
9use iced_native::widget::{self, Operation, Tree};
10use iced_native::{
11    Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle,
12    Shell, Widget,
13};
14
15use crate::widget::StyleType;
16
17pub use iced_style::container::{Appearance, StyleSheet};
18
19/// An element decorating some content.
20///
21/// It is normally used for alignment purposes.
22#[allow(missing_debug_implementations)]
23pub struct Container<'a, Message, Renderer>
24where
25    Renderer: iced_native::Renderer,
26    Renderer::Theme: StyleSheet,
27{
28    id: Option<Id>,
29    padding: Padding,
30    width: Length,
31    height: Length,
32    max_width: f32,
33    max_height: f32,
34    horizontal_alignment: alignment::Horizontal,
35    vertical_alignment: alignment::Vertical,
36    style: StyleType<<Renderer::Theme as StyleSheet>::Style>,
37    content: Element<'a, Message, Renderer>,
38}
39
40impl<'a, Message, Renderer> Container<'a, Message, Renderer>
41where
42    Renderer: iced_native::Renderer,
43    Renderer::Theme: StyleSheet,
44{
45    /// Creates an empty [`Container`].
46    pub fn new<T>(content: T) -> Self
47    where
48        T: Into<Element<'a, Message, Renderer>>,
49    {
50        Container {
51            id: None,
52            padding: Padding::ZERO,
53            width: Length::Shrink,
54            height: Length::Shrink,
55            max_width: f32::INFINITY,
56            max_height: f32::INFINITY,
57            horizontal_alignment: alignment::Horizontal::Left,
58            vertical_alignment: alignment::Vertical::Top,
59            style: StyleType::Static(Default::default()),
60            content: content.into(),
61        }
62    }
63
64    /// Sets the [`Id`] of the [`Container`].
65    pub fn id(mut self, id: Id) -> Self {
66        self.id = Some(id);
67        self
68    }
69
70    /// Sets the [`Padding`] of the [`Container`].
71    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
72        self.padding = padding.into();
73        self
74    }
75
76    /// Sets the width of the [`Container`].
77    pub fn width(mut self, width: impl Into<Length>) -> Self {
78        self.width = width.into();
79        self
80    }
81
82    /// Sets the height of the [`Container`].
83    pub fn height(mut self, height: impl Into<Length>) -> Self {
84        self.height = height.into();
85        self
86    }
87
88    /// Sets the maximum width of the [`Container`].
89    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
90        self.max_width = max_width.into().0;
91        self
92    }
93
94    /// Sets the maximum height of the [`Container`].
95    pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
96        self.max_height = max_height.into().0;
97        self
98    }
99
100    /// Sets the content alignment for the horizontal axis of the [`Container`].
101    pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self {
102        self.horizontal_alignment = alignment;
103        self
104    }
105
106    /// Sets the content alignment for the vertical axis of the [`Container`].
107    pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
108        self.vertical_alignment = alignment;
109        self
110    }
111
112    /// Centers the contents in the horizontal axis of the [`Container`].
113    pub fn center_x(mut self) -> Self {
114        self.horizontal_alignment = alignment::Horizontal::Center;
115        self
116    }
117
118    /// Centers the contents in the vertical axis of the [`Container`].
119    pub fn center_y(mut self) -> Self {
120        self.vertical_alignment = alignment::Vertical::Center;
121        self
122    }
123
124    /// Sets the style of the [`Container`].
125    pub fn style(mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>) -> Self {
126        self.style = StyleType::Static(style.into());
127        self
128    }
129
130    /// Sets the animatable style variant of this [`Container`].
131    pub fn blend_style(
132        mut self,
133        style1: <Renderer::Theme as StyleSheet>::Style,
134        style2: <Renderer::Theme as StyleSheet>::Style,
135        percent: f32,
136    ) -> Self {
137        self.style = StyleType::Blend(style1, style2, percent);
138        self
139    }
140}
141
142impl<'a, Message, Renderer> Widget<Message, Renderer> for Container<'a, Message, Renderer>
143where
144    Renderer: iced_native::Renderer,
145    Renderer::Theme: StyleSheet,
146{
147    fn children(&self) -> Vec<Tree> {
148        vec![Tree::new(&self.content)]
149    }
150
151    fn diff(&self, tree: &mut Tree) {
152        tree.diff_children(std::slice::from_ref(&self.content))
153    }
154
155    fn width(&self) -> Length {
156        self.width
157    }
158
159    fn height(&self) -> Length {
160        self.height
161    }
162
163    fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
164        layout(
165            renderer,
166            limits,
167            self.width,
168            self.height,
169            self.max_width,
170            self.max_height,
171            self.padding,
172            self.horizontal_alignment,
173            self.vertical_alignment,
174            |renderer, limits| self.content.as_widget().layout(renderer, limits),
175        )
176    }
177
178    fn operate(
179        &self,
180        tree: &mut Tree,
181        layout: Layout<'_>,
182        renderer: &Renderer,
183        operation: &mut dyn Operation<Message>,
184    ) {
185        operation.container(self.id.as_ref().map(|id| &id.0), &mut |operation| {
186            self.content.as_widget().operate(
187                &mut tree.children[0],
188                layout.children().next().unwrap(),
189                renderer,
190                operation,
191            );
192        });
193    }
194
195    fn on_event(
196        &mut self,
197        tree: &mut Tree,
198        event: Event,
199        layout: Layout<'_>,
200        cursor_position: Point,
201        renderer: &Renderer,
202        clipboard: &mut dyn Clipboard,
203        shell: &mut Shell<'_, Message>,
204    ) -> event::Status {
205        self.content.as_widget_mut().on_event(
206            &mut tree.children[0],
207            event,
208            layout.children().next().unwrap(),
209            cursor_position,
210            renderer,
211            clipboard,
212            shell,
213        )
214    }
215
216    fn mouse_interaction(
217        &self,
218        tree: &Tree,
219        layout: Layout<'_>,
220        cursor_position: Point,
221        viewport: &Rectangle,
222        renderer: &Renderer,
223    ) -> mouse::Interaction {
224        self.content.as_widget().mouse_interaction(
225            &tree.children[0],
226            layout.children().next().unwrap(),
227            cursor_position,
228            viewport,
229            renderer,
230        )
231    }
232
233    fn draw(
234        &self,
235        tree: &Tree,
236        renderer: &mut Renderer,
237        theme: &Renderer::Theme,
238        renderer_style: &renderer::Style,
239        layout: Layout<'_>,
240        cursor_position: Point,
241        viewport: &Rectangle,
242    ) {
243        let style = match &self.style {
244            StyleType::Static(style) => theme.appearance(style),
245            StyleType::Blend(one, two, percent) => {
246                blend_appearances(theme.appearance(one), theme.appearance(two), *percent)
247            }
248        };
249
250        draw_background(renderer, &style, layout.bounds());
251
252        self.content.as_widget().draw(
253            &tree.children[0],
254            renderer,
255            theme,
256            &renderer::Style {
257                text_color: style.text_color.unwrap_or(renderer_style.text_color),
258            },
259            layout.children().next().unwrap(),
260            cursor_position,
261            viewport,
262        );
263    }
264
265    fn overlay<'b>(
266        &'b mut self,
267        tree: &'b mut Tree,
268        layout: Layout<'_>,
269        renderer: &Renderer,
270    ) -> Option<overlay::Element<'b, Message, Renderer>> {
271        self.content.as_widget_mut().overlay(
272            &mut tree.children[0],
273            layout.children().next().unwrap(),
274            renderer,
275        )
276    }
277}
278
279impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
280    for Element<'a, Message, Renderer>
281where
282    Message: 'a,
283    Renderer: 'a + iced_native::Renderer,
284    Renderer::Theme: StyleSheet,
285{
286    fn from(column: Container<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
287        Element::new(column)
288    }
289}
290
291/// Computes the layout of a [`Container`].
292pub fn layout<Renderer>(
293    renderer: &Renderer,
294    limits: &layout::Limits,
295    width: Length,
296    height: Length,
297    max_width: f32,
298    max_height: f32,
299    padding: Padding,
300    horizontal_alignment: alignment::Horizontal,
301    vertical_alignment: alignment::Vertical,
302    layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
303) -> layout::Node {
304    let limits = limits
305        .loose()
306        .max_width(max_width)
307        .max_height(max_height)
308        .width(width)
309        .height(height);
310
311    let mut content = layout_content(renderer, &limits.pad(padding).loose());
312    let padding = padding.fit(content.size(), limits.max());
313    let size = limits.pad(padding).resolve(content.size());
314
315    content.move_to(Point::new(padding.left, padding.top));
316    content.align(
317        Alignment::from(horizontal_alignment),
318        Alignment::from(vertical_alignment),
319        size,
320    );
321
322    layout::Node::with_children(size.pad(padding), vec![content])
323}
324
325/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`.
326pub fn draw_background<Renderer>(
327    renderer: &mut Renderer,
328    appearance: &Appearance,
329    bounds: Rectangle,
330) where
331    Renderer: iced_native::Renderer,
332{
333    if appearance.background.is_some() || appearance.border_width > 0.0 {
334        renderer.fill_quad(
335            renderer::Quad {
336                bounds,
337                border_radius: appearance.border_radius.into(),
338                border_width: appearance.border_width,
339                border_color: appearance.border_color,
340            },
341            appearance
342                .background
343                .unwrap_or(Background::Color(Color::TRANSPARENT)),
344        );
345    }
346}
347
348/// The identifier of a [`Container`].
349#[derive(Debug, Clone, PartialEq, Eq, Hash)]
350pub struct Id(widget::Id);
351
352impl Id {
353    /// Creates a custom [`Id`].
354    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
355        Self(widget::Id::new(id))
356    }
357
358    /// Creates a unique [`Id`].
359    ///
360    /// This function produces a different [`Id`] every time it is called.
361    pub fn unique() -> Self {
362        Self(widget::Id::unique())
363    }
364}
365
366impl From<Id> for widget::Id {
367    fn from(id: Id) -> Self {
368        id.0
369    }
370}
371
372fn blend_appearances(
373    one: iced_style::container::Appearance,
374    mut two: iced_style::container::Appearance,
375    percent: f32,
376) -> iced_style::container::Appearance {
377    use crate::lerp;
378
379    // background
380    let background_one: Color = one
381        .background
382        .map(|b| match b {
383            Background::Color(c) => c,
384        })
385        .unwrap_or(color!(0, 0, 0));
386    let background_two: Color = two
387        .background
388        .map(|b| match b {
389            Background::Color(c) => c,
390        })
391        .unwrap_or(color!(0, 0, 0));
392    let background: [f32; 4] = background_one
393        .into_linear()
394        .iter()
395        .zip(background_two.into_linear().iter())
396        .map(|(o, t)| lerp(*o, *t, percent))
397        .collect::<Vec<f32>>()
398        .try_into()
399        .unwrap();
400    let background: Color = background.into();
401
402    // boarder color
403    let border_color: [f32; 4] = one
404        .border_color
405        .into_linear()
406        .iter()
407        .zip(two.border_color.into_linear().iter())
408        .map(|(o, t)| lerp(*o, *t, percent))
409        .collect::<Vec<f32>>()
410        .try_into()
411        .unwrap();
412
413    // text
414    let text = one
415        .text_color
416        .map(|t| {
417            let ret: [f32; 4] = t
418                .into_linear()
419                .iter()
420                .zip(two.text_color.unwrap_or(t).into_linear().iter())
421                .map(|(o, t)| lerp(*o, *t, percent))
422                .collect::<Vec<f32>>()
423                .try_into()
424                .unwrap();
425            ret
426        })
427        .map(Into::<Color>::into);
428
429    two.background = Some(background.into());
430    two.border_radius = lerp(one.border_radius, two.border_radius, percent);
431    two.border_width = lerp(one.border_width, two.border_width, percent);
432    two.border_color = border_color.into();
433    two.text_color = text;
434    two
435}