From 5428feb0df03a670d448868fa645ef0f7a73357d Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 16 Apr 2020 00:27:25 -0400 Subject: [PATCH] Add Stack and CompoundGraphic widgets --- assets/voxygen/element/frames/banner_top.png | 4 +- voxygen/src/menu/main/ui.rs | 21 +- voxygen/src/ui/ice/renderer.rs | 67 ++++-- .../src/ui/ice/renderer/compound_graphic.rs | 35 ++++ voxygen/src/ui/ice/renderer/container.rs | 6 +- voxygen/src/ui/ice/renderer/stack.rs | 34 ++++ voxygen/src/ui/ice/widget.rs | 2 + .../src/ui/ice/widget/background_container.rs | 15 +- voxygen/src/ui/ice/widget/compound_graphic.rs | 190 ++++++++++++++++++ voxygen/src/ui/ice/widget/image.rs | 7 +- voxygen/src/ui/ice/widget/stack.rs | 93 +++++++++ voxygen/src/ui/mod.rs | 2 +- 12 files changed, 441 insertions(+), 35 deletions(-) create mode 100644 voxygen/src/ui/ice/renderer/compound_graphic.rs create mode 100644 voxygen/src/ui/ice/renderer/stack.rs create mode 100644 voxygen/src/ui/ice/widget/compound_graphic.rs create mode 100644 voxygen/src/ui/ice/widget/stack.rs diff --git a/assets/voxygen/element/frames/banner_top.png b/assets/voxygen/element/frames/banner_top.png index a8f9c1a224..2b9df1746e 100644 --- a/assets/voxygen/element/frames/banner_top.png +++ b/assets/voxygen/element/frames/banner_top.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f03ab3f90d7abe2ccd67de23b7a7f1e026cb97c8e202a60165397d61b8f9dce -size 4881 +oid sha256:50ccd34b74b2c94259cca931dbb67f07a1972027f490e448916ab10feab08e80 +size 5125 diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index cb633e5a09..185d4006dc 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -214,19 +214,28 @@ impl IcedState { .padding(20); let banner_content = - Column::with_children(vec![Image::new(self.imgs.v_logo).fix_aspect_ratio().into()]); + 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)) .fix_aspect_ratio() - .width(Length::Fill) + .max_width(300) .height(Length::Fill); + let central_column = Container::new(banner) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Align::Center) + .align_y(Align::Center); + 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); + let content = + Row::with_children(vec![buttons.into(), central_column.into(), image3.into()]) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10); ui::ice::BackgroundContainer::new(self.imgs.bg, content) .width(Length::Fill) diff --git a/voxygen/src/ui/ice/renderer.rs b/voxygen/src/ui/ice/renderer.rs index 138f9526ec..feb8580499 100644 --- a/voxygen/src/ui/ice/renderer.rs +++ b/voxygen/src/ui/ice/renderer.rs @@ -1,5 +1,6 @@ mod background_container; mod column; +mod compound_graphic; mod container; mod image; mod row; @@ -48,6 +49,7 @@ impl DrawCommand { } } +#[derive(PartialEq)] enum State { Image(TexId), Plain, @@ -63,6 +65,10 @@ pub enum Primitive { bounds: iced::Rectangle, color: Rgba, }, + Rectangle { + bounds: iced::Rectangle, + color: Rgba, + }, } // Optimization idea inspired by what I think iced wgpu renderer may be doing @@ -265,6 +271,10 @@ impl IcedRenderer { } let color = srgba_to_linear(color.map(|e| e as f32 / 255.0)); + // Don't draw a transparent image. + if color[3] == 0.0 { + return; + } let resolution = Vec2::new( (gl_aabr.size().w * self.half_res.x).round() as u16, @@ -311,30 +321,47 @@ impl IcedRenderer { None => return, }; - match self.current_state { - // Switch to the image state if we are not in it already. - State::Plain => { - self.draw_commands - .push(DrawCommand::plain(self.start..self.mesh.vertices().len())); - self.start = self.mesh.vertices().len(); - self.current_state = State::Image(tex_id); - }, - // If the image is cached in a different texture switch to the new one - State::Image(id) => { - if id != tex_id { - self.draw_commands.push(DrawCommand::image( - self.start..self.mesh.vertices().len(), - id, - )); - self.start = self.mesh.vertices().len(); - self.current_state = State::Image(tex_id); - } - }, - } + // Switch to the image state if we are not in it already or if a different + // texture id was being used. + self.switch_state(State::Image(tex_id)); self.mesh .push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image)); }, + Primitive::Rectangle { bounds, color } => { + let color = srgba_to_linear(color.map(|e| e as f32 / 255.0)); + // Don't draw a transparent rectangle. + if color[3] == 0.0 { + return; + } + + self.switch_state(State::Plain); + + self.mesh.push_quad(create_ui_quad( + self.gl_aabr(bounds), + Aabr { + min: Vec2::zero(), + max: Vec2::zero(), + }, + color, + UiMode::Geometry, + )); + }, + } + } + + // Switches to the specified state if not already in it + // If switch occurs current state is converted into a draw command + fn switch_state(&mut self, state: State) { + if self.current_state != state { + let vert_range = self.start..self.mesh.vertices().len(); + let draw_command = match self.current_state { + State::Plain => DrawCommand::plain(vert_range), + State::Image(id) => DrawCommand::image(vert_range, id), + }; + self.draw_commands.push(draw_command); + self.start = self.mesh.vertices().len(); + self.current_state = state; } } diff --git a/voxygen/src/ui/ice/renderer/compound_graphic.rs b/voxygen/src/ui/ice/renderer/compound_graphic.rs new file mode 100644 index 0000000000..d6bb89e6c0 --- /dev/null +++ b/voxygen/src/ui/ice/renderer/compound_graphic.rs @@ -0,0 +1,35 @@ +use super::{ + super::{widget::compound_graphic, Rotation}, + IcedRenderer, Primitive, +}; +use compound_graphic::GraphicKind; +use iced::{MouseCursor, Rectangle}; +use vek::Rgba; + +impl compound_graphic::Renderer for IcedRenderer { + fn draw( + &mut self, + graphics: I, + //color: Rgba, + _layout: iced::Layout<'_>, + ) -> Self::Output + where + I: Iterator, + { + ( + Primitive::Group { + primitives: graphics + .map(|(bounds, kind)| match kind { + GraphicKind::Image(handle) => Primitive::Image { + handle: (handle, Rotation::None), + bounds, + color: Rgba::broadcast(255), + }, + GraphicKind::Color(color) => Primitive::Rectangle { bounds, color }, + }) + .collect(), + }, + MouseCursor::OutOfBounds, + ) + } +} diff --git a/voxygen/src/ui/ice/renderer/container.rs b/voxygen/src/ui/ice/renderer/container.rs index 4a4eaad9ab..fd144f66ce 100644 --- a/voxygen/src/ui/ice/renderer/container.rs +++ b/voxygen/src/ui/ice/renderer/container.rs @@ -1,5 +1,5 @@ -use super::{IcedRenderer, Primitive}; -use iced::{container, Element, Layout, MouseCursor, Point, Rectangle}; +use super::IcedRenderer; +use iced::{container, Element, Layout, Point, Rectangle}; impl container::Renderer for IcedRenderer { type Style = (); @@ -7,7 +7,7 @@ impl container::Renderer for IcedRenderer { fn draw( &mut self, defaults: &Self::Defaults, - bounds: Rectangle, + _bounds: Rectangle, cursor_position: Point, _style_sheet: &Self::Style, content: &Element<'_, M, Self>, diff --git a/voxygen/src/ui/ice/renderer/stack.rs b/voxygen/src/ui/ice/renderer/stack.rs new file mode 100644 index 0000000000..3eb77d5ad7 --- /dev/null +++ b/voxygen/src/ui/ice/renderer/stack.rs @@ -0,0 +1,34 @@ +use super::{super::widget::stack, IcedRenderer, Primitive}; +use iced::{Element, Layout, MouseCursor, Point}; + +impl stack::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 7d9718b424..ebf8ee659b 100644 --- a/voxygen/src/ui/ice/widget.rs +++ b/voxygen/src/ui/ice/widget.rs @@ -1,2 +1,4 @@ pub mod background_container; +pub mod compound_graphic; pub mod image; +pub mod stack; diff --git a/voxygen/src/ui/ice/widget/background_container.rs b/voxygen/src/ui/ice/widget/background_container.rs index 6bacbcdcc1..34db33c629 100644 --- a/voxygen/src/ui/ice/widget/background_container.rs +++ b/voxygen/src/ui/ice/widget/background_container.rs @@ -1,5 +1,5 @@ use iced::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; -use std::u32; +use std::{hash::Hash, u32}; use vek::Rgba; // Note: it might be more efficient to make this generic over the content type @@ -142,7 +142,18 @@ where ) } - fn hash_layout(&self, state: &mut Hasher) { self.content.hash_layout(state); } + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().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.content.hash_layout(state); + } } pub trait Renderer: iced::Renderer + super::image::Renderer { diff --git a/voxygen/src/ui/ice/widget/compound_graphic.rs b/voxygen/src/ui/ice/widget/compound_graphic.rs new file mode 100644 index 0000000000..48b6f759d4 --- /dev/null +++ b/voxygen/src/ui/ice/widget/compound_graphic.rs @@ -0,0 +1,190 @@ +use super::image::Handle; +use iced::{layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget}; +use std::hash::Hash; +use vek::{Aabr, Rgba, Vec2}; + +// TODO: this widget combines multiple images in precise ways, they may or may +// nor overlap and it would be helpful for optimising the renderer by telling it +// if there is no overlap (i.e. draw calls can be reordered freely), we don't +// need to do this yet since the renderer isn't that advanced + +// TODO: design trait to interface with background container +#[derive(Copy, Clone)] +pub enum GraphicKind { + // TODO: if there is a use case, allow coloring individual images + Image(Handle), + Color(Rgba), +} + +// TODO: consider faculties for composing compound graphics (if a use case pops +// up) +pub struct Graphic { + aabr: Aabr, + kind: GraphicKind, +} + +impl Graphic { + fn new(kind: GraphicKind, size: [u16; 2], offset: [u16; 2]) -> Self { + let size = Vec2::from(size); + let offset = Vec2::from(offset); + Self { + aabr: Aabr { + min: offset, + max: offset + size, + }, + kind, + } + } + + pub fn image(handle: Handle, size: [u16; 2], offset: [u16; 2]) -> Self { + Self::new(GraphicKind::Image(handle), size, offset) + } + + pub fn rect(color: Rgba, size: [u16; 2], offset: [u16; 2]) -> Self { + Self::new(GraphicKind::Color(color), size, offset) + } +} + +pub struct CompoundGraphic { + graphics: Vec, + // move into option inside fix_aspect_ratio? + graphics_size: [u16; 2], + width: Length, + height: Length, + fix_aspect_ratio: bool, + /* TODO: allow coloring the widget as a whole (if there is a use case) + *color: Rgba, */ +} + +impl CompoundGraphic { + pub fn with_graphics(graphics: Vec) -> Self { + let width = Length::Fill; + let height = Length::Fill; + let graphics_size = graphics + .iter() + .fold(Vec2::zero(), |size, graphic| { + Vec2::max(size, graphic.aabr.max) + }) + .into_array(); + Self { + graphics, + graphics_size, + width, + height, + fix_aspect_ratio: false, + //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 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 CompoundGraphic +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 mut size = limits.width(self.width).height(self.height).max(); + + if self.fix_aspect_ratio { + let aspect_ratio = { + let [w, h] = self.graphics_size; + 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) + } + + 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( + 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) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + if self.fix_aspect_ratio { + self.graphics_size.hash(state); + } + } +} + +pub trait Renderer: iced::Renderer { + fn draw( + &mut self, + graphics: I, + //color: Rgba, + layout: Layout<'_>, + ) -> Self::Output + where + I: Iterator; +} + +impl<'a, M, R> From for Element<'a, M, R> +where + R: self::Renderer, +{ + fn from(compound_graphic: CompoundGraphic) -> Element<'a, M, R> { + Element::new(compound_graphic) + } +} diff --git a/voxygen/src/ui/ice/widget/image.rs b/voxygen/src/ui/ice/widget/image.rs index b793399cd5..a5f5df9f11 100644 --- a/voxygen/src/ui/ice/widget/image.rs +++ b/voxygen/src/ui/ice/widget/image.rs @@ -83,7 +83,7 @@ where fn draw( &self, renderer: &mut R, - defaults: &R::Defaults, + _defaults: &R::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> R::Output { @@ -91,8 +91,13 @@ where } fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + self.width.hash(state); self.height.hash(state); + self.fix_aspect_ratio.hash(state); + // TODO: also depends on dims but we have no way to access } } diff --git a/voxygen/src/ui/ice/widget/stack.rs b/voxygen/src/ui/ice/widget/stack.rs new file mode 100644 index 0000000000..dd215dfae1 --- /dev/null +++ b/voxygen/src/ui/ice/widget/stack.rs @@ -0,0 +1,93 @@ +use iced::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; +use std::hash::Hash; + +/// Stack up some widgets +pub struct Stack<'a, M, R> { + // Add these if it is useful + /* + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + horizontal_alignment: Align, + vertical_alignment: Align + align_items: Align, + */ + children: Vec>, +} + +impl<'a, M, R> Stack<'a, M, R> +where + R: self::Renderer, +{ + pub fn with_children(children: Vec>) -> Self { Self { children } } +} + +impl<'a, M, R> Widget for Stack<'a, M, R> +where + R: self::Renderer, +{ + fn width(&self) -> Length { Length::Fill } + + fn height(&self) -> Length { Length::Fill } + + fn layout(&self, renderer: &R, limits: &layout::Limits) -> layout::Node { + let limits = limits.width(Length::Fill).height(Length::Fill); + + let loosed_limits = limits.loose(); + + let (max_size, nodes) = self.children.iter().fold( + (Size::ZERO, Vec::with_capacity(self.children.len())), + |(mut max_size, mut nodes), child| { + let node = child.layout(renderer, &loosed_limits); + let size = node.size(); + nodes.push(node); + max_size.width = max_size.width.max(size.width); + max_size.height = max_size.height.max(size.height); + (max_size, nodes) + }, + ); + + let size = limits.resolve(max_size); + + layout::Node::with_children(size, nodes) + } + + fn draw( + &self, + renderer: &mut R, + defaults: &R::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> R::Output { + renderer.draw(defaults, &self.children, layout, cursor_position) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.children + .iter() + .for_each(|child| child.hash_layout(state)); + } +} + +pub trait Renderer: iced::Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + children: &[Element<'_, M, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output; +} + +impl<'a, M, R> From> for Element<'a, M, R> +where + R: 'a + self::Renderer, + M: 'a, +{ + fn from(stack: Stack<'a, M, R>) -> Element<'a, M, R> { Element::new(stack) } +} diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index e5140145f9..158bcd31d4 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -45,7 +45,7 @@ use conrod_core::{ Rect, Scalar, UiBuilder, UiCell, }; use core::{convert::TryInto, f32, f64, ops::Range}; -use graphic::{Rotation, TexId}; +use graphic::TexId; use hashbrown::hash_map::Entry; use std::{ fs::File,