use iced::{
layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::hash::Hash;
#[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 {
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;
}
pub struct BackgroundContainer<'a, M, R: Renderer, B: Background<R>> {
max_width: u32,
max_height: u32,
background: B,
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>,
{
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() .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;
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() {
let aspect_ratio = pixel_w as f32 / pixel_h as f32;
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)
};
let limits = limits.shrink({
let max = limits.max();
Size::new(
max.width * horizontal_pad_frac,
max.height * vertical_pad_frac,
)
});
let mut content = self.content.layout(renderer, &limits.loose());
let mut content_size = content.size();
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)
};
content.move_to(Point::new(
left_pad_frac * size.width,
top_pad_frac * size.height,
));
(size, content)
} else {
let limits = limits
.shrink({
let max = limits.max();
Size::new(
max.width * horizontal_pad_frac,
max.height * vertical_pad_frac,
)
})
.loose(); let mut content = self.content.layout(renderer, &limits);
let mut size = limits.resolve(content.size());
size.width /= 1.0 - horizontal_pad_frac;
size.height /= 1.0 - vertical_pad_frac;
content.move_to(Point::new(
left_pad_frac * size.width,
top_pad_frac * size.height,
));
(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);
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>;
}
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)
}
}