From b5d31f6cba2b97a1cfb49bd85d475d19c84c10bc Mon Sep 17 00:00:00 2001 From: Imbris Date: Wed, 18 Mar 2020 12:36:06 -0400 Subject: [PATCH] Begin implementing container widget with an image background --- voxygen/src/menu/main/mod.rs | 7 + voxygen/src/menu/main/ui.rs | 48 ++++- voxygen/src/ui/graphic/mod.rs | 21 +++ voxygen/src/ui/ice/mod.rs | 2 +- voxygen/src/ui/ice/renderer.rs | 25 ++- .../ui/ice/renderer/background_container.rs | 28 +++ voxygen/src/ui/ice/renderer/container.rs | 22 +++ voxygen/src/ui/ice/renderer/image.rs | 24 ++- voxygen/src/ui/ice/renderer/row.rs | 34 ++++ voxygen/src/ui/ice/widget.rs | 1 + .../src/ui/ice/widget/background_container.rs | 169 ++++++++++++++++++ voxygen/src/ui/ice/widget/image.rs | 48 ++++- 12 files changed, 399 insertions(+), 30 deletions(-) create mode 100644 voxygen/src/ui/ice/renderer/background_container.rs create mode 100644 voxygen/src/ui/ice/renderer/container.rs create mode 100644 voxygen/src/ui/ice/renderer/row.rs create mode 100644 voxygen/src/ui/ice/widget/background_container.rs diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 7f4bb43bd0..b2d685b92d 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -87,6 +87,13 @@ impl PlayState for MainMenuState { Event::Ui(event) => { self.main_menu_ui.handle_event(event); }, + // Pass events to iced ui. + Event::IcedUi(event) => { + self.main_menu_ui.handle_iced_event(event); + }, + Event::InputUpdate(crate::window::GameInput::Jump, true) => { + self.main_menu_ui.show_iced ^= true; + }, // Ignore all other events. _ => {}, } diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 9c498f7d52..cb633e5a09 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -19,7 +19,6 @@ use conrod_core::{ widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; -use iced::{Column, Row}; use image::DynamicImage; use rand::{seq::SliceRandom, thread_rng, Rng}; use std::time::Duration; @@ -194,17 +193,44 @@ struct IcedState { pub type Message = Event; impl IcedState { pub fn view(&mut self) -> Element { - use iced::Length; + use iced::{Align, Column, Container, Length, Row}; + use ui::ice::Image; + use vek::*; - let image1 = ui::ice::Image::new((self.imgs.bg, ui::Rotation::None)); - let image2 = ui::ice::Image::new((self.imgs.bg, ui::Rotation::None)); - let image3 = ui::ice::Image::new((self.imgs.bg, ui::Rotation::None)); + let buttons = Column::with_children(vec![ + Image::new(self.imgs.button).fix_aspect_ratio().into(), + Image::new(self.imgs.button).fix_aspect_ratio().into(), + Image::new(self.imgs.button).fix_aspect_ratio().into(), + ]) + .width(Length::Fill) + .max_width(200) + .spacing(5) + .padding(10); - Row::with_children(vec![image1.into(), image2.into(), image3.into()]) + let buttons = Container::new(buttons) + .width(Length::Fill) + .height(Length::Fill) + .align_y(Align::End) + .padding(20); + + let banner_content = + Column::with_children(vec![Image::new(self.imgs.v_logo).fix_aspect_ratio().into()]); + let banner = ui::ice::BackgroundContainer::new(self.imgs.banner, banner_content) + .color(Rgba::new(255, 255, 255, 230)) + .fix_aspect_ratio() + .width(Length::Fill) + .height(Length::Fill); + + let image3 = Image::new(self.imgs.banner_bottom).fix_aspect_ratio(); + + let content = Row::with_children(vec![buttons.into(), banner.into(), image3.into()]) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10); + + ui::ice::BackgroundContainer::new(self.imgs.bg, content) .width(Length::Fill) .height(Length::Fill) - .spacing(20) - .padding(20) .into() } @@ -236,6 +262,7 @@ pub struct MainMenuUi { voxygen_i18n: std::sync::Arc, fonts: ConrodVoxygenFonts, tip_no: u16, + pub show_iced: bool, } impl<'a> MainMenuUi { @@ -314,6 +341,7 @@ impl<'a> MainMenuUi { voxygen_i18n, fonts, tip_no: 0, + show_iced: false, } } @@ -975,6 +1003,8 @@ impl<'a> MainMenuUi { pub fn render(&self, renderer: &mut Renderer) { self.ui.render(renderer, None); - self.ice_ui.render(renderer); + if self.show_iced { + self.ice_ui.render(renderer); + } } } diff --git a/voxygen/src/ui/graphic/mod.rs b/voxygen/src/ui/graphic/mod.rs index b89a9ac9ae..b4e52c4b5c 100644 --- a/voxygen/src/ui/graphic/mod.rs +++ b/voxygen/src/ui/graphic/mod.rs @@ -187,6 +187,27 @@ impl GraphicCache { self.textures.get(id.0).expect("Invalid TexId used") } + pub fn get_graphic_dims(&self, (id, rot): (Id, Rotation)) -> Option<(u32, u32)> { + use image::GenericImageView; + self.get_graphic(id) + .and_then(|graphic| match graphic { + Graphic::Image(image) => Some(image.dimensions()), + Graphic::Voxel(segment, _, _) => { + use common::vol::SizedVol; + let size = segment.size(); + // TODO: HACK because they can be rotated arbitrarily, remove + Some((size.x, size.z)) + }, + Graphic::Blank => None, + }) + .and_then(|(w, h)| match rot { + Rotation::None | Rotation::Cw180 => Some((w, h)), + Rotation::Cw90 | Rotation::Cw270 => Some((h, w)), + // TODO: need dims for these? + Rotation::SourceNorth | Rotation::TargetNorth => None, + }) + } + pub fn clear_cache(&mut self, renderer: &mut Renderer) { self.cache_map.clear(); diff --git a/voxygen/src/ui/ice/mod.rs b/voxygen/src/ui/ice/mod.rs index 231d112c30..1cbeb9f4be 100644 --- a/voxygen/src/ui/ice/mod.rs +++ b/voxygen/src/ui/ice/mod.rs @@ -7,7 +7,7 @@ mod winit_conversion; pub use graphic::{Id, Rotation}; pub use iced::Event; pub use renderer::IcedRenderer; -pub use widget::image::Image; +pub use widget::{background_container::BackgroundContainer, image::Image}; pub use winit_conversion::window_event; use super::{ diff --git a/voxygen/src/ui/ice/renderer.rs b/voxygen/src/ui/ice/renderer.rs index 94a35bb74a..138f9526ec 100644 --- a/voxygen/src/ui/ice/renderer.rs +++ b/voxygen/src/ui/ice/renderer.rs @@ -1,4 +1,6 @@ +mod background_container; mod column; +mod container; mod image; mod row; @@ -7,7 +9,7 @@ use super::{ cache::Cache, graphic::{self, Graphic, TexId}, }, - widget, + widget, Rotation, }; use crate::{ render::{ @@ -15,6 +17,7 @@ use crate::{ }, Error, }; +use common::util::srgba_to_linear; //use log::warn; use std::ops::Range; use vek::*; @@ -56,11 +59,16 @@ pub enum Primitive { primitives: Vec, }, Image { - handle: widget::image::Handle, + handle: (widget::image::Handle, Rotation), bounds: iced::Rectangle, + color: Rgba, }, } +// Optimization idea inspired by what I think iced wgpu renderer may be doing +// Could have layers of things which don't intersect and thus can be reordered +// arbitrarily + pub struct IcedRenderer { //image_map: Map<(Image, Rotation)>, cache: Cache, @@ -241,7 +249,11 @@ impl IcedRenderer { .into_iter() .for_each(|p| self.draw_primitive(p, renderer)); }, - Primitive::Image { handle, bounds } => { + Primitive::Image { + handle, + bounds, + color, + } => { let (graphic_id, rotation) = handle; let gl_aabr = self.gl_aabr(bounds); @@ -252,11 +264,7 @@ impl IcedRenderer { _ => {}, } - // TODO provide color with the image - // let color = - // srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa(). - // into()); - let color = Rgba::from([1.0, 0.0, 1.0, 0.5]); + let color = srgba_to_linear(color.map(|e| e as f32 / 255.0)); let resolution = Vec2::new( (gl_aabr.size().w * self.half_res.x).round() as u16, @@ -359,6 +367,7 @@ impl IcedRenderer { // offsets from the center of the sceen to pixels #[inline(always)] fn align(res: Vec2) -> Vec2 { + // TODO: does this logic still apply in iced's coordinate system? // If the resolution is odd then the center of the screen will be within the // middle of a pixel so we need to offset by 0.5 pixels to be on the edge of // a pixel diff --git a/voxygen/src/ui/ice/renderer/background_container.rs b/voxygen/src/ui/ice/renderer/background_container.rs new file mode 100644 index 0000000000..2a31ab763a --- /dev/null +++ b/voxygen/src/ui/ice/renderer/background_container.rs @@ -0,0 +1,28 @@ +use super::{ + super::widget::{background_container, image}, + IcedRenderer, Primitive, +}; +use iced::{Element, Layout, Point}; + +impl background_container::Renderer for IcedRenderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + layout: Layout<'_>, + cursor_position: Point, + background: image::Handle, + color: vek::Rgba, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let image_primitive = image::Renderer::draw(self, background, color, layout).0; + let (content_primitive, mouse_cursor) = + content.draw(self, defaults, content_layout, cursor_position); + ( + Primitive::Group { + primitives: vec![image_primitive, content_primitive], + }, + mouse_cursor, + ) + } +} diff --git a/voxygen/src/ui/ice/renderer/container.rs b/voxygen/src/ui/ice/renderer/container.rs new file mode 100644 index 0000000000..4a4eaad9ab --- /dev/null +++ b/voxygen/src/ui/ice/renderer/container.rs @@ -0,0 +1,22 @@ +use super::{IcedRenderer, Primitive}; +use iced::{container, Element, Layout, MouseCursor, Point, Rectangle}; + +impl container::Renderer for IcedRenderer { + type Style = (); + + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + cursor_position: Point, + _style_sheet: &Self::Style, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let (content, mouse_cursor) = content.draw(self, defaults, content_layout, cursor_position); + + // We may have more stuff here if styles are used + + (content, mouse_cursor) + } +} diff --git a/voxygen/src/ui/ice/renderer/image.rs b/voxygen/src/ui/ice/renderer/image.rs index 7f25595774..22b3f8a953 100644 --- a/voxygen/src/ui/ice/renderer/image.rs +++ b/voxygen/src/ui/ice/renderer/image.rs @@ -1,12 +1,30 @@ -use super::{super::widget::image, IcedRenderer, Primitive}; +use super::{ + super::{widget::image, Rotation}, + IcedRenderer, Primitive, +}; use iced::MouseCursor; +use vek::Rgba; impl image::Renderer for IcedRenderer { - fn draw(&mut self, handle: image::Handle, layout: iced::Layout<'_>) -> Self::Output { + fn dimensions(&self, handle: image::Handle) -> (u32, u32) { + self.cache + .graphic_cache() + .get_graphic_dims((handle, Rotation::None)) + // TODO: don't unwrap + .unwrap() + } + + fn draw( + &mut self, + handle: image::Handle, + color: Rgba, + layout: iced::Layout<'_>, + ) -> Self::Output { ( Primitive::Image { - handle, + handle: (handle, Rotation::None), bounds: layout.bounds(), + color, }, MouseCursor::OutOfBounds, ) diff --git a/voxygen/src/ui/ice/renderer/row.rs b/voxygen/src/ui/ice/renderer/row.rs new file mode 100644 index 0000000000..47d22004f4 --- /dev/null +++ b/voxygen/src/ui/ice/renderer/row.rs @@ -0,0 +1,34 @@ +use super::{IcedRenderer, Primitive}; +use iced::{row, Element, Layout, MouseCursor, Point}; + +impl row::Renderer for IcedRenderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + children: &[Element<'_, M, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_cursor) = + child.draw(self, defaults, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) + } +} diff --git a/voxygen/src/ui/ice/widget.rs b/voxygen/src/ui/ice/widget.rs index 14995d4228..7d9718b424 100644 --- a/voxygen/src/ui/ice/widget.rs +++ b/voxygen/src/ui/ice/widget.rs @@ -1 +1,2 @@ +pub mod background_container; pub mod image; diff --git a/voxygen/src/ui/ice/widget/background_container.rs b/voxygen/src/ui/ice/widget/background_container.rs new file mode 100644 index 0000000000..6bacbcdcc1 --- /dev/null +++ b/voxygen/src/ui/ice/widget/background_container.rs @@ -0,0 +1,169 @@ +use iced::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; +use std::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 + +/// 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, +} + +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), + } + } + + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + 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; + self + } + + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + pub fn fix_aspect_ratio(mut self) -> Self { + self.fix_aspect_ratio = true; + self + } + + pub fn color(mut self, color: Rgba) -> Self { + self.color = color; + self + } +} + +impl<'a, M, R> Widget for BackgroundContainer<'a, M, R> +where + R: self::Renderer, +{ + fn width(&self) -> Length { self.width } + + fn height(&self) -> Length { self.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 (size, content) = if self.fix_aspect_ratio { + let (w, h) = renderer.dimensions(self.background); + // 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; + // 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 as f32, max_size.height as f32); + 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) + }; + // Get content size + // again, why is loose() used here? + let 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 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) + } else { + Size::new(content_size.height * aspect_ratio, content_size.width) + }; + + (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) + + (size, content) + }; + + layout::Node::with_children(size, vec![content]) + } + + fn draw( + &self, + renderer: &mut R, + defaults: &R::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> R::Output { + self::Renderer::draw( + renderer, + defaults, + layout, + cursor_position, + self.background, + self.color, + &self.content, + layout.children().next().unwrap(), + ) + } + + fn hash_layout(&self, state: &mut Hasher) { self.content.hash_layout(state); } +} + +pub trait Renderer: iced::Renderer + super::image::Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + layout: Layout<'_>, + cursor_position: Point, + background: super::image::Handle, + color: Rgba, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output; +} + +// They got to live ¯\_(ツ)_/¯ +impl<'a, M: 'a, R: 'a> From> for Element<'a, M, R> +where + R: self::Renderer, +{ + fn from(background_container: BackgroundContainer<'a, M, R>) -> Element<'a, M, R> { + Element::new(background_container) + } +} diff --git a/voxygen/src/ui/ice/widget/image.rs b/voxygen/src/ui/ice/widget/image.rs index 15830839a8..b793399cd5 100644 --- a/voxygen/src/ui/ice/widget/image.rs +++ b/voxygen/src/ui/ice/widget/image.rs @@ -1,17 +1,20 @@ -use super::super::super::graphic::{self, Rotation}; -use iced::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; +use super::super::graphic; +use iced::{layout, Element, Hasher, Layout, Length, Point, Widget}; use std::hash::Hash; +use vek::Rgba; // TODO: consider iced's approach to images and caching image data // Also `Graphic` might be a better name for this is it wasn't already in use // elsewhere -pub type Handle = (graphic::Id, Rotation); +pub type Handle = graphic::Id; pub struct Image { handle: Handle, width: Length, height: Length, + fix_aspect_ratio: bool, + color: Rgba, } impl Image { @@ -22,6 +25,8 @@ impl Image { handle, width, height, + fix_aspect_ratio: false, + color: Rgba::broadcast(255), } } @@ -34,6 +39,16 @@ impl Image { self.height = height; self } + + pub fn fix_aspect_ratio(mut self) -> Self { + self.fix_aspect_ratio = true; + self + } + + pub fn color(mut self, color: Rgba) -> Self { + self.color = color; + self + } } impl Widget for Image @@ -44,9 +59,23 @@ where fn height(&self) -> Length { self.height } - fn layout(&self, _renderer: &R, limits: &layout::Limits) -> layout::Node { - // We don't care about aspect ratios here :p - let size = limits.width(self.width).height(self.height).max(); + fn layout(&self, renderer: &R, limits: &layout::Limits) -> layout::Node { + let mut size = limits.width(self.width).height(self.height).max(); + + if self.fix_aspect_ratio { + let aspect_ratio = { + let (w, h) = renderer.dimensions(self.handle); + w as f32 / h as f32 + }; + + let max_aspect_ratio = size.width / size.height; + + if max_aspect_ratio > aspect_ratio { + size.width = size.height * aspect_ratio; + } else { + size.height = size.width / aspect_ratio; + } + } layout::Node::new(size) } @@ -54,11 +83,11 @@ where fn draw( &self, renderer: &mut R, - _defaults: &R::Defaults, + defaults: &R::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> R::Output { - renderer.draw(self.handle, layout) + renderer.draw(self.handle, self.color, layout) } fn hash_layout(&self, state: &mut Hasher) { @@ -68,7 +97,8 @@ where } pub trait Renderer: iced::Renderer { - fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output; + fn dimensions(&self, handle: Handle) -> (u32, u32); + fn draw(&mut self, handle: Handle, color: Rgba, layout: Layout<'_>) -> Self::Output; } impl<'a, M, R> From for Element<'a, M, R>