1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
use iced::{
    layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::hash::Hash;

// TODO: decouple from image/compound graphic widgets (they could still use
// common types, but the info we want here for the background is a subset of
// what a fullblown widget might need)

// Note: it might be more efficient to make this generic over the content type

// Note: maybe we could just use the container styling for this (not really with
// the aspect ratio stuff)

#[derive(Copy, Clone, Hash)]
pub struct Padding {
    pub top: u16,
    pub bottom: u16,
    pub right: u16,
    pub left: u16,
}

impl Padding {
    pub fn new() -> Self {
        Padding {
            top: 0,
            bottom: 0,
            right: 0,
            left: 0,
        }
    }

    #[must_use]
    pub fn top(mut self, pad: u16) -> Self {
        self.top = pad;
        self
    }

    #[must_use]
    pub fn bottom(mut self, pad: u16) -> Self {
        self.bottom = pad;
        self
    }

    #[must_use]
    pub fn right(mut self, pad: u16) -> Self {
        self.right = pad;
        self
    }

    #[must_use]
    pub fn left(mut self, pad: u16) -> Self {
        self.left = pad;
        self
    }

    #[must_use]
    pub fn vertical(mut self, pad: u16) -> Self {
        self.top = pad;
        self.bottom = pad;
        self
    }

    #[must_use]
    pub fn horizontal(mut self, pad: u16) -> Self {
        self.left = pad;
        self.right = pad;
        self
    }
}

impl Default for Padding {
    fn default() -> Self { Self::new() }
}

pub trait Background<R: iced::Renderer>: Sized {
    // The intended implementors already store the state accessed in the three
    // functions below
    fn width(&self) -> Length;
    fn height(&self) -> Length;
    fn aspect_ratio_fixed(&self) -> bool;
    fn pixel_dims(&self, renderer: &R) -> (u16, u16);
    fn draw(
        &self,
        renderer: &mut R,
        defaults: &R::Defaults,
        layout: Layout<'_>,
        cursor_position: Point,
        viewport: &Rectangle,
    ) -> R::Output;
}

/// This widget is displays a background image behind it's content
pub struct BackgroundContainer<'a, M, R: Renderer, B: Background<R>> {
    max_width: u32,
    max_height: u32,
    background: B,
    // Padding in same pixel units as background image
    // Scaled relative to the background's scaling
    padding: Padding,
    content: Element<'a, M, R>,
}

impl<'a, M, R, B> BackgroundContainer<'a, M, R, B>
where
    R: Renderer,
    B: Background<R>,
{
    pub fn new(background: B, content: impl Into<Element<'a, M, R>>) -> Self {
        Self {
            max_width: u32::MAX,
            max_height: u32::MAX,
            background,
            padding: Padding::new(),
            content: content.into(),
        }
    }

    #[must_use]
    pub fn padding(mut self, padding: Padding) -> Self {
        self.padding = padding;
        self
    }

    #[must_use]
    pub fn max_width(mut self, max_width: u32) -> Self {
        self.max_width = max_width;
        self
    }

    #[must_use]
    pub fn max_height(mut self, max_height: u32) -> Self {
        self.max_height = max_height;
        self
    }
}

impl<'a, M, R, B> Widget<M, R> for BackgroundContainer<'a, M, R, B>
where
    R: Renderer,
    B: Background<R>,
{
    // Uses the width and height from the background
    fn width(&self) -> Length { self.background.width() }

    fn height(&self) -> Length { self.background.height() }

    fn layout(&self, renderer: &R, limits: &layout::Limits) -> layout::Node {
        let limits = limits
            .loose() // why does iced's container do this?
            .max_width(self.max_width)
            .max_height(self.max_height)
            .width(self.width())
            .height(self.height());

        let (pixel_w, pixel_h) = self.background.pixel_dims(renderer);
        let (horizontal_pad_frac, vertical_pad_frac, top_pad_frac, left_pad_frac) = {
            let Padding {
                top,
                bottom,
                right,
                left,
            } = self.padding;
            // Just in case
            // could convert to gracefully handling
            debug_assert!(pixel_w != 0);
            debug_assert!(pixel_h != 0);
            debug_assert!(top + bottom < pixel_h);
            debug_assert!(right + left < pixel_w);
            (
                (right + left) as f32 / pixel_w as f32,
                (top + bottom) as f32 / pixel_h as f32,
                top as f32 / pixel_h as f32,
                left as f32 / pixel_w as f32,
            )
        };

        let (size, content) = if self.background.aspect_ratio_fixed() {
            // To fix the aspect ratio we have to have a separate layout from the content
            // because we can't force the content to have a specific aspect ratio
            let aspect_ratio = pixel_w as f32 / pixel_h as f32;

            // To do this we need to figure out the max width/height of the limits
            // and then adjust one down to meet the aspect ratio
            let max_size = limits.max();
            let (max_width, max_height) = (max_size.width, max_size.height);
            let max_aspect_ratio = max_width / max_height;
            let limits = if max_aspect_ratio > aspect_ratio {
                limits.max_width((max_height * aspect_ratio) as u32)
            } else {
                limits.max_height((max_width / aspect_ratio) as u32)
            };
            // Account for padding at max size in the limits for the children
            let limits = limits.shrink({
                let max = limits.max();
                Size::new(
                    max.width * horizontal_pad_frac,
                    max.height * vertical_pad_frac,
                )
            });

            // Get content size
            // again, why is loose() used here?
            let mut content = self.content.layout(renderer, &limits.loose());

            // TODO: handle cases where self and/or children are not Length::Fill
            // If fill use max_size

            // This time we need to adjust up to meet the aspect ratio
            // so that the container is larger than the contents
            let mut content_size = content.size();
            // Add minimum padding to content size (this works to ensure we have enough
            // space for padding because the available space can only increase)
            content_size.width /= 1.0 - horizontal_pad_frac;
            content_size.height /= 1.0 - vertical_pad_frac;
            let content_aspect_ratio = content_size.width / content_size.height;
            let size = if content_aspect_ratio > aspect_ratio {
                Size::new(content_size.width, content_size.width / aspect_ratio)
            } else {
                Size::new(content_size.height * aspect_ratio, content_size.height)
            };

            // Move content to account for padding
            content.move_to(Point::new(
                left_pad_frac * size.width,
                top_pad_frac * size.height,
            ));

            (size, content)
        } else {
            // Account for padding at max size in the limits for the children
            let limits = limits
                .shrink({
                    let max = limits.max();
                    Size::new(
                        max.width * horizontal_pad_frac,
                        max.height * vertical_pad_frac,
                    )
                })
                .loose(); // again, why is loose() used here?

            let mut content = self.content.layout(renderer, &limits);

            let mut size = limits.resolve(content.size());
            // Add padding back
            size.width /= 1.0 - horizontal_pad_frac;
            size.height /= 1.0 - vertical_pad_frac;

            // Move to account for padding
            content.move_to(Point::new(
                left_pad_frac * size.width,
                top_pad_frac * size.height,
            ));
            // No aligning since child is currently assumed to be fill

            (size, content)
        };

        layout::Node::with_children(size, vec![content])
    }

    fn draw(
        &self,
        renderer: &mut R,
        defaults: &R::Defaults,
        layout: Layout<'_>,
        cursor_position: Point,
        viewport: &Rectangle,
    ) -> R::Output {
        renderer.draw(
            defaults,
            &self.background,
            layout,
            viewport,
            &self.content,
            layout.children().next().unwrap(),
            cursor_position,
        )
    }

    fn hash_layout(&self, state: &mut Hasher) {
        struct Marker;
        std::any::TypeId::of::<Marker>().hash(state);

        self.width().hash(state);
        self.height().hash(state);
        self.max_width.hash(state);
        self.max_height.hash(state);
        self.background.aspect_ratio_fixed().hash(state);
        self.padding.hash(state);
        // TODO: add pixel dims (need renderer)

        self.content.hash_layout(state);
    }

    fn on_event(
        &mut self,
        event: Event,
        layout: Layout<'_>,
        cursor_position: Point,
        renderer: &R,
        clipboard: &mut dyn Clipboard,
        messages: &mut Vec<M>,
    ) -> iced::event::Status {
        self.content.on_event(
            event,
            layout.children().next().unwrap(),
            cursor_position,
            renderer,
            clipboard,
            messages,
        )
    }

    fn overlay(&mut self, layout: Layout<'_>) -> Option<iced::overlay::Element<'_, M, R>> {
        self.content.overlay(layout.children().next().unwrap())
    }
}

pub trait Renderer: iced::Renderer {
    fn draw<M, B>(
        &mut self,
        defaults: &Self::Defaults,
        background: &B,
        background_layout: Layout<'_>,
        viewport: &Rectangle,
        content: &Element<'_, M, Self>,
        content_layout: Layout<'_>,
        cursor_position: Point,
    ) -> Self::Output
    where
        B: Background<Self>;
}

// They got to live ¯\_(ツ)_/¯
impl<'a, M: 'a, R: 'a, B> From<BackgroundContainer<'a, M, R, B>> for Element<'a, M, R>
where
    R: Renderer,
    B: 'a + Background<R>,
{
    fn from(background_container: BackgroundContainer<'a, M, R, B>) -> Element<'a, M, R> {
        Element::new(background_container)
    }
}