From b25162635a073b0bd992aeecd1ead9fa0520077a Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 16 Apr 2020 03:26:06 -0400 Subject: [PATCH] Revamp BackgroundContainer with scale specific padding, and use in main menu attempt --- ...anner_bottom.png => banner_bottom_png.png} | 0 .../frames/{banner.png => banner_png.png} | 0 voxygen/src/menu/main/ui.rs | 43 ++- voxygen/src/ui/ice/mod.rs | 6 +- .../ui/ice/renderer/background_container.rs | 20 +- .../src/ui/ice/renderer/compound_graphic.rs | 4 +- .../src/ui/ice/widget/background_container.rs | 265 ++++++++++++++---- voxygen/src/ui/ice/widget/compound_graphic.rs | 98 +++++-- voxygen/src/ui/ice/widget/image.rs | 26 ++ 9 files changed, 350 insertions(+), 112 deletions(-) rename assets/voxygen/element/frames/{banner_bottom.png => banner_bottom_png.png} (100%) rename assets/voxygen/element/frames/{banner.png => banner_png.png} (100%) diff --git a/assets/voxygen/element/frames/banner_bottom.png b/assets/voxygen/element/frames/banner_bottom_png.png similarity index 100% rename from assets/voxygen/element/frames/banner_bottom.png rename to assets/voxygen/element/frames/banner_bottom_png.png diff --git a/assets/voxygen/element/frames/banner.png b/assets/voxygen/element/frames/banner_png.png similarity index 100% rename from assets/voxygen/element/frames/banner.png rename to assets/voxygen/element/frames/banner_png.png diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 185d4006dc..2c6d02b128 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -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", 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 { 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) { diff --git a/voxygen/src/ui/ice/mod.rs b/voxygen/src/ui/ice/mod.rs index 1cbeb9f4be..ed3ac1418e 100644 --- a/voxygen/src/ui/ice/mod.rs +++ b/voxygen/src/ui/ice/mod.rs @@ -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::{ diff --git a/voxygen/src/ui/ice/renderer/background_container.rs b/voxygen/src/ui/ice/renderer/background_container.rs index 2a31ab763a..599afb7806 100644 --- a/voxygen/src/ui/ice/renderer/background_container.rs +++ b/voxygen/src/ui/ice/renderer/background_container.rs @@ -5,22 +5,26 @@ use super::{ use iced::{Element, Layout, Point}; impl background_container::Renderer for IcedRenderer { - fn draw( + fn draw( &mut self, defaults: &Self::Defaults, - layout: Layout<'_>, - cursor_position: Point, - background: image::Handle, - color: vek::Rgba, + 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, + { + 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, ) diff --git a/voxygen/src/ui/ice/renderer/compound_graphic.rs b/voxygen/src/ui/ice/renderer/compound_graphic.rs index d6bb89e6c0..b3b43ce8da 100644 --- a/voxygen/src/ui/ice/renderer/compound_graphic.rs +++ b/voxygen/src/ui/ice/renderer/compound_graphic.rs @@ -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 }, }) diff --git a/voxygen/src/ui/ice/widget/background_container.rs b/voxygen/src/ui/ice/widget/background_container.rs index 34db33c629..7072e33d38 100644 --- a/voxygen/src/ui/ice/widget/background_container.rs +++ b/voxygen/src/ui/ice/widget/background_container.rs @@ -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, +#[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>) -> 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: 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> { + //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, +{ + pub fn new(background: B, content: impl Into>) -> 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) -> Self { self.color = color; self - } + }*/ } -impl<'a, M, R> Widget for BackgroundContainer<'a, M, R> +impl<'a, M, R, B> Widget for BackgroundContainer<'a, M, R, B> where R: self::Renderer, + B: Background, { - 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::().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( +pub trait Renderer: iced::Renderer { + fn draw( &mut self, defaults: &Self::Defaults, - layout: Layout<'_>, - cursor_position: Point, - background: super::image::Handle, - color: Rgba, + background: &B, + background_layout: Layout<'_>, content: &Element<'_, M, Self>, content_layout: Layout<'_>, - ) -> Self::Output; + cursor_position: Point, + ) -> Self::Output + where + B: Background; } // They got to live ¯\_(ツ)_/¯ -impl<'a, M: 'a, R: 'a> From> for Element<'a, M, R> +impl<'a, M: 'a, R: 'a, B> From> for Element<'a, M, R> where R: self::Renderer, + B: 'a + Background, { - 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) } } diff --git a/voxygen/src/ui/ice/widget/compound_graphic.rs b/voxygen/src/ui/ice/widget/compound_graphic.rs index 48b6f759d4..40798b2dab 100644 --- a/voxygen/src/ui/ice/widget/compound_graphic.rs +++ b/voxygen/src/ui/ice/widget/compound_graphic.rs @@ -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), Color(Rgba), } @@ -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) -> 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, 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( + &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 Widget 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 super::background_container::Background 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) + } +} diff --git a/voxygen/src/ui/ice/widget/image.rs b/voxygen/src/ui/ice/widget/image.rs index a5f5df9f11..22eb73e15d 100644 --- a/voxygen/src/ui/ice/widget/image.rs +++ b/voxygen/src/ui/ice/widget/image.rs @@ -112,3 +112,29 @@ where { fn from(image: Image) -> Element<'a, M, R> { Element::new(image) } } + +impl super::background_container::Background 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) + } +}