Begin implementing container widget with an image background

This commit is contained in:
Imbris 2020-03-18 12:36:06 -04:00
parent 6a4b7b70c2
commit b5d31f6cba
12 changed files with 399 additions and 30 deletions

View File

@ -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.
_ => {},
}

View File

@ -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<Message> {
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<VoxygenLocalization>,
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);
}
}
}

View File

@ -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();

View File

@ -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::{

View File

@ -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<Primitive>,
},
Image {
handle: widget::image::Handle,
handle: (widget::image::Handle, Rotation),
bounds: iced::Rectangle,
color: Rgba<u8>,
},
}
// 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<u16>) -> Vec2<f32> {
// 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

View File

@ -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<M>(
&mut self,
defaults: &Self::Defaults,
layout: Layout<'_>,
cursor_position: Point,
background: image::Handle,
color: vek::Rgba<u8>,
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,
)
}
}

View File

@ -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<M>(
&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)
}
}

View File

@ -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<u8>,
layout: iced::Layout<'_>,
) -> Self::Output {
(
Primitive::Image {
handle,
handle: (handle, Rotation::None),
bounds: layout.bounds(),
color,
},
MouseCursor::OutOfBounds,
)

View File

@ -0,0 +1,34 @@
use super::{IcedRenderer, Primitive};
use iced::{row, Element, Layout, MouseCursor, Point};
impl row::Renderer for IcedRenderer {
fn draw<M>(
&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,
)
}
}

View File

@ -1 +1,2 @@
pub mod background_container;
pub mod image;

View File

@ -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<u8>,
}
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),
}
}
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<u8>) -> Self {
self.color = color;
self
}
}
impl<'a, M, R> Widget<M, R> 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<M>(
&mut self,
defaults: &Self::Defaults,
layout: Layout<'_>,
cursor_position: Point,
background: super::image::Handle,
color: Rgba<u8>,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output;
}
// They got to live ¯\_(ツ)_/¯
impl<'a, M: 'a, R: 'a> From<BackgroundContainer<'a, M, R>> 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)
}
}

View File

@ -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<u8>,
}
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<u8>) -> Self {
self.color = color;
self
}
}
impl<M, R> Widget<M, R> 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<u8>, layout: Layout<'_>) -> Self::Output;
}
impl<'a, M, R> From<Image> for Element<'a, M, R>