Revamp BackgroundContainer with scale specific padding, and use in main menu attempt

This commit is contained in:
Imbris 2020-04-16 03:26:06 -04:00
parent 5428feb0df
commit b25162635a
9 changed files with 350 additions and 112 deletions

View File

@ -130,12 +130,12 @@ image_ids_ice! {
v_logo: "voxygen.element.v_logo",
info_frame: "voxygen.element.frames.info_frame_2",
banner: "voxygen.element.frames.banner",
banner_bottom: "voxygen.element.frames.banner_bottom",
//banner: "voxygen.element.frames.banner",
<ImageGraphic>
bg: "voxygen.background.bg_main",
banner: "voxygen.element.frames.banner_png",
banner_bottom: "voxygen.element.frames.banner_bottom_png",
banner_top: "voxygen.element.frames.banner_top",
button: "voxygen.element.buttons.button",
button_hover: "voxygen.element.buttons.button_hover",
@ -194,7 +194,10 @@ pub type Message = Event;
impl IcedState {
pub fn view(&mut self) -> Element<Message> {
use iced::{Align, Column, Container, Length, Row};
use ui::ice::Image;
use ui::ice::{
compound_graphic::{CompoundGraphic, Graphic},
BackgroundContainer, Image, Padding,
};
use vek::*;
let buttons = Column::with_children(vec![
@ -214,14 +217,23 @@ impl IcedState {
.padding(20);
let banner_content =
Column::with_children(vec![Image::new(self.imgs.v_logo).fix_aspect_ratio().into()])
.padding(15);
Column::with_children(vec![Image::new(self.imgs.v_logo).fix_aspect_ratio().into()]);
//.padding(15);
let banner = ui::ice::BackgroundContainer::new(self.imgs.banner, banner_content)
.color(Rgba::new(255, 255, 255, 230))
let banner = BackgroundContainer::new(
CompoundGraphic::with_graphics(vec![
Graphic::image(self.imgs.banner_top, [138, 17], [0, 0]),
Graphic::rect(Rgba::new(0, 0, 0, 230), [130, 175], [4, 17]),
// Might need floats here
Graphic::image(self.imgs.banner, [130, 15], [4, 192])
.color(Rgba::new(255, 255, 255, 230)),
])
.fix_aspect_ratio()
.max_width(300)
.height(Length::Fill);
.height(Length::Fill),
banner_content,
)
.padding(Padding::new().horizontal(16).top(21).bottom(4))
.max_width(330);
let central_column = Container::new(banner)
.width(Length::Fill)
@ -237,10 +249,13 @@ impl IcedState {
.height(Length::Fill)
.spacing(10);
ui::ice::BackgroundContainer::new(self.imgs.bg, content)
.width(Length::Fill)
.height(Length::Fill)
.into()
BackgroundContainer::new(
Image::new(self.imgs.bg)
.width(Length::Fill)
.height(Length::Fill),
content,
)
.into()
}
pub fn update(message: Message) {

View File

@ -7,7 +7,11 @@ mod winit_conversion;
pub use graphic::{Id, Rotation};
pub use iced::Event;
pub use renderer::IcedRenderer;
pub use widget::{background_container::BackgroundContainer, image::Image};
pub use widget::{
background_container::{BackgroundContainer, Padding},
compound_graphic,
image::Image,
};
pub use winit_conversion::window_event;
use super::{

View File

@ -5,22 +5,26 @@ use super::{
use iced::{Element, Layout, Point};
impl background_container::Renderer for IcedRenderer {
fn draw<M>(
fn draw<M, B>(
&mut self,
defaults: &Self::Defaults,
layout: Layout<'_>,
cursor_position: Point,
background: image::Handle,
color: vek::Rgba<u8>,
background: &B,
background_layout: Layout<'_>,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output {
let image_primitive = image::Renderer::draw(self, background, color, layout).0;
cursor_position: Point,
) -> Self::Output
where
B: background_container::Background<Self>,
{
let back_primitive = background
.draw(self, defaults, background_layout, cursor_position)
.0;
let (content_primitive, mouse_cursor) =
content.draw(self, defaults, content_layout, cursor_position);
(
Primitive::Group {
primitives: vec![image_primitive, content_primitive],
primitives: vec![back_primitive, content_primitive],
},
mouse_cursor,
)

View File

@ -20,10 +20,10 @@ impl compound_graphic::Renderer for IcedRenderer {
Primitive::Group {
primitives: graphics
.map(|(bounds, kind)| match kind {
GraphicKind::Image(handle) => Primitive::Image {
GraphicKind::Image(handle, color) => Primitive::Image {
handle: (handle, Rotation::None),
bounds,
color: Rgba::broadcast(255),
color,
},
GraphicKind::Color(color) => Primitive::Rectangle { bounds, color },
})

View File

@ -1,41 +1,114 @@
use iced::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
use std::{hash::Hash, u32};
use vek::Rgba;
// 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
// Note: maybe we could just use the container styling for this (not really with
// the aspect ratio stuff)
/// This widget is displays a background image behind it's content
pub struct BackgroundContainer<'a, M, R: self::Renderer> {
width: Length,
height: Length,
max_width: u32,
max_height: u32,
background: super::image::Handle,
fix_aspect_ratio: bool,
content: Element<'a, M, R>,
color: Rgba<u8>,
#[derive(Copy, Clone, Hash)]
pub struct Padding {
pub top: u16,
pub bottom: u16,
pub right: u16,
pub left: u16,
}
impl<'a, M, R> BackgroundContainer<'a, M, R>
where
R: self::Renderer,
{
pub fn new(background: super::image::Handle, content: impl Into<Element<'a, M, R>>) -> Self {
Self {
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
background,
fix_aspect_ratio: false,
content: content.into(),
color: Rgba::broadcast(255),
impl Padding {
pub fn new() -> Self {
Padding {
top: 0,
bottom: 0,
right: 0,
left: 0,
}
}
pub fn width(mut self, width: Length) -> Self {
pub fn top(mut self, pad: u16) -> Self {
self.top = pad;
self
}
pub fn bottom(mut self, pad: u16) -> Self {
self.bottom = pad;
self
}
pub fn right(mut self, pad: u16) -> Self {
self.right = pad;
self
}
pub fn left(mut self, pad: u16) -> Self {
self.left = pad;
self
}
pub fn vertical(mut self, pad: u16) -> Self {
self.top = pad;
self.bottom = pad;
self
}
pub fn horizontal(mut self, pad: u16) -> Self {
self.left = pad;
self.right = pad;
self
}
}
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; 2];
fn draw(
&self,
renderer: &mut R,
defaults: &R::Defaults,
layout: Layout<'_>,
cursor_position: Point,
) -> R::Output;
}
/// This widget is displays a background image behind it's content
pub struct BackgroundContainer<'a, M, R: self::Renderer, B: Background<R>> {
//width: Length,
//height: Length,
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: self::Renderer,
B: Background<R>,
{
pub fn new(background: B, content: impl Into<Element<'a, M, R>>) -> Self {
Self {
//width: Length::Shrink,
//height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
background,
padding: Padding::new(),
content: content.into(),
}
}
pub fn padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self
}
/*pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
@ -43,7 +116,7 @@ where
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
}*/
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
@ -55,7 +128,8 @@ where
self
}
pub fn fix_aspect_ratio(mut self) -> Self {
// Consider having these wire into underlying background
/*pub fn fix_aspect_ratio(mut self) -> Self {
self.fix_aspect_ratio = true;
self
}
@ -63,30 +137,54 @@ where
pub fn color(mut self, color: Rgba<u8>) -> Self {
self.color = color;
self
}
}*/
}
impl<'a, M, R> Widget<M, R> for BackgroundContainer<'a, M, R>
impl<'a, M, R, B> Widget<M, R> for BackgroundContainer<'a, M, R, B>
where
R: self::Renderer,
B: Background<R>,
{
fn width(&self) -> Length { self.width }
// Uses the width and height from the background
fn width(&self) -> Length { self.background.width() }
fn height(&self) -> Length { self.height }
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);
.width(self.width())
.height(self.height());
let (size, content) = if self.fix_aspect_ratio {
let (w, h) = renderer.dimensions(self.background);
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 = w as f32 / h as f32;
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();
@ -97,12 +195,28 @@ where
} else {
limits.max_height((max_width / aspect_ratio) as u32)
};
limits;
// 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,
)
});
limits;
// Get content size
// again, why is loose() used here?
let content = self.content.layout(renderer, &limits.loose());
let mut content = self.content.layout(renderer, &limits.loose());
// This time we need to adjust up to meet the aspect ratio
// so that the container is larger than the contents
let content_size = content.size();
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 as f32 / content_size.height as f32;
let size = if content_aspect_ratio > aspect_ratio {
Size::new(content_size.width, content_size.width / aspect_ratio)
@ -110,12 +224,39 @@ where
Size::new(content_size.height * aspect_ratio, content_size.width)
};
// Move content to account for padding
content.move_to(Point::new(
left_pad_frac * size.width,
top_pad_frac * size.height,
));
size;
(size, content)
} else {
// again, why is loose() used here?
let content = self.content.layout(renderer, &limits.loose());
let size = limits.resolve(content.size());
//self.content.layout(renderer, limits)
// 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)
};
@ -130,15 +271,13 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> R::Output {
self::Renderer::draw(
renderer,
renderer.draw(
defaults,
&self.background,
layout,
cursor_position,
self.background,
self.color,
&self.content,
layout.children().next().unwrap(),
cursor_position,
)
}
@ -146,35 +285,39 @@ where
struct Marker;
std::any::TypeId::of::<Marker>().hash(state);
self.width.hash(state);
self.height.hash(state);
self.width().hash(state);
self.height().hash(state);
self.max_width.hash(state);
self.max_height.hash(state);
self.fix_aspect_ratio.hash(state);
self.background.aspect_ratio_fixed().hash(state);
self.padding.hash(state);
// TODO: add pixel dims (need renderer)
self.content.hash_layout(state);
}
}
pub trait Renderer: iced::Renderer + super::image::Renderer {
fn draw<M>(
pub trait Renderer: iced::Renderer {
fn draw<M, B>(
&mut self,
defaults: &Self::Defaults,
layout: Layout<'_>,
cursor_position: Point,
background: super::image::Handle,
color: Rgba<u8>,
background: &B,
background_layout: Layout<'_>,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output;
cursor_position: Point,
) -> Self::Output
where
B: Background<Self>;
}
// They got to live ¯\_(ツ)_/¯
impl<'a, M: 'a, R: 'a> From<BackgroundContainer<'a, M, R>> for Element<'a, M, R>
impl<'a, M: 'a, R: 'a, B> From<BackgroundContainer<'a, M, R, B>> for Element<'a, M, R>
where
R: self::Renderer,
B: 'a + Background<R>,
{
fn from(background_container: BackgroundContainer<'a, M, R>) -> Element<'a, M, R> {
fn from(background_container: BackgroundContainer<'a, M, R, B>) -> Element<'a, M, R> {
Element::new(background_container)
}
}

View File

@ -12,7 +12,7 @@ use vek::{Aabr, Rgba, Vec2};
#[derive(Copy, Clone)]
pub enum GraphicKind {
// TODO: if there is a use case, allow coloring individual images
Image(Handle),
Image(Handle, Rgba<u8>),
Color(Rgba<u8>),
}
@ -37,9 +37,22 @@ impl Graphic {
}
pub fn image(handle: Handle, size: [u16; 2], offset: [u16; 2]) -> Self {
Self::new(GraphicKind::Image(handle), size, offset)
Self::new(
GraphicKind::Image(handle, Rgba::broadcast(255)),
size,
offset,
)
}
pub fn color(mut self, color: Rgba<u8>) -> Self {
match &mut self.kind {
GraphicKind::Image(_, c) => *c = color,
GraphicKind::Color(c) => *c = color,
}
self
}
// TODO: consider removing color here
pub fn rect(color: Rgba<u8>, size: [u16; 2], offset: [u16; 2]) -> Self {
Self::new(GraphicKind::Color(color), size, offset)
}
@ -95,6 +108,37 @@ impl CompoundGraphic {
// self.color = color;
// self
//}
fn draw<R: self::Renderer>(
&self,
renderer: &mut R,
_defaults: &R::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
) -> R::Output {
let [pixel_w, pixel_h] = self.graphics_size;
let bounds = layout.bounds();
let scale = Vec2::new(
bounds.width / pixel_w as f32,
bounds.height / pixel_h as f32,
);
let graphics = self.graphics.iter().map(|graphic| {
let bounds = {
let Aabr { min, max } = graphic.aabr.map(|e| e as f32);
let min = min * scale;
let size = max * scale - min;
Rectangle {
x: min.x + bounds.x,
y: min.y + bounds.y,
width: size.x,
height: size.y,
}
};
(bounds, graphic.kind)
});
renderer.draw(graphics, /* self.color, */ layout)
}
}
impl<M, R> Widget<M, R> for CompoundGraphic
@ -129,32 +173,11 @@ where
fn draw(
&self,
renderer: &mut R,
_defaults: &R::Defaults,
defaults: &R::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
cursor_position: Point,
) -> R::Output {
let [pixel_w, pixel_h] = self.graphics_size;
let bounds = layout.bounds();
let scale = Vec2::new(
pixel_w as f32 / bounds.width,
pixel_h as f32 / bounds.height,
);
let graphics = self.graphics.iter().map(|graphic| {
let bounds = {
let Aabr { min, max } = graphic.aabr.map(|e| e as f32);
let min = min * scale;
let size = max * scale - min;
Rectangle {
x: min.x + bounds.x,
y: min.y + bounds.y,
width: size.x,
height: size.y,
}
};
(bounds, graphic.kind)
});
renderer.draw(graphics, /* self.color, */ layout)
Self::draw(self, renderer, defaults, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -188,3 +211,26 @@ where
Element::new(compound_graphic)
}
}
impl<R> super::background_container::Background<R> for CompoundGraphic
where
R: self::Renderer,
{
fn width(&self) -> Length { self.width }
fn height(&self) -> Length { self.height }
fn aspect_ratio_fixed(&self) -> bool { self.fix_aspect_ratio }
fn pixel_dims(&self, _renderer: &R) -> [u16; 2] { self.graphics_size }
fn draw(
&self,
renderer: &mut R,
defaults: &R::Defaults,
layout: Layout<'_>,
cursor_position: Point,
) -> R::Output {
Self::draw(self, renderer, defaults, layout, cursor_position)
}
}

View File

@ -112,3 +112,29 @@ where
{
fn from(image: Image) -> Element<'a, M, R> { Element::new(image) }
}
impl<R> super::background_container::Background<R> for Image
where
R: self::Renderer,
{
fn width(&self) -> Length { self.width }
fn height(&self) -> Length { self.height }
fn aspect_ratio_fixed(&self) -> bool { self.fix_aspect_ratio }
fn pixel_dims(&self, renderer: &R) -> [u16; 2] {
let (w, h) = renderer.dimensions(self.handle);
[w as u16, h as u16]
}
fn draw(
&self,
renderer: &mut R,
_defaults: &R::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
) -> R::Output {
renderer.draw(self.handle, self.color, layout)
}
}