mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add Stack and CompoundGraphic widgets
This commit is contained in:
parent
b5d31f6cba
commit
5428feb0df
BIN
assets/voxygen/element/frames/banner_top.png
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/banner_top.png
(Stored with Git LFS)
Binary file not shown.
@ -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)
|
||||
|
@ -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<u8>,
|
||||
},
|
||||
Rectangle {
|
||||
bounds: iced::Rectangle,
|
||||
color: Rgba<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
35
voxygen/src/ui/ice/renderer/compound_graphic.rs
Normal file
35
voxygen/src/ui/ice/renderer/compound_graphic.rs
Normal file
@ -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<I>(
|
||||
&mut self,
|
||||
graphics: I,
|
||||
//color: Rgba<u8>,
|
||||
_layout: iced::Layout<'_>,
|
||||
) -> Self::Output
|
||||
where
|
||||
I: Iterator<Item = (Rectangle, GraphicKind)>,
|
||||
{
|
||||
(
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
@ -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<M>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
_bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
_style_sheet: &Self::Style,
|
||||
content: &Element<'_, M, Self>,
|
||||
|
34
voxygen/src/ui/ice/renderer/stack.rs
Normal file
34
voxygen/src/ui/ice/renderer/stack.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use super::{super::widget::stack, IcedRenderer, Primitive};
|
||||
use iced::{Element, Layout, MouseCursor, Point};
|
||||
|
||||
impl stack::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,
|
||||
)
|
||||
}
|
||||
}
|
@ -1,2 +1,4 @@
|
||||
pub mod background_container;
|
||||
pub mod compound_graphic;
|
||||
pub mod image;
|
||||
pub mod stack;
|
||||
|
@ -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::<Marker>().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 {
|
||||
|
190
voxygen/src/ui/ice/widget/compound_graphic.rs
Normal file
190
voxygen/src/ui/ice/widget/compound_graphic.rs
Normal file
@ -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<u8>),
|
||||
}
|
||||
|
||||
// TODO: consider faculties for composing compound graphics (if a use case pops
|
||||
// up)
|
||||
pub struct Graphic {
|
||||
aabr: Aabr<u16>,
|
||||
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<u8>, size: [u16; 2], offset: [u16; 2]) -> Self {
|
||||
Self::new(GraphicKind::Color(color), size, offset)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompoundGraphic {
|
||||
graphics: Vec<Graphic>,
|
||||
// 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<u8>, */
|
||||
}
|
||||
|
||||
impl CompoundGraphic {
|
||||
pub fn with_graphics(graphics: Vec<Graphic>) -> 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<u8>) -> Self {
|
||||
// self.color = color;
|
||||
// self
|
||||
//}
|
||||
}
|
||||
|
||||
impl<M, R> Widget<M, R> 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::<Marker>().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<I>(
|
||||
&mut self,
|
||||
graphics: I,
|
||||
//color: Rgba<u8>,
|
||||
layout: Layout<'_>,
|
||||
) -> Self::Output
|
||||
where
|
||||
I: Iterator<Item = (Rectangle, GraphicKind)>;
|
||||
}
|
||||
|
||||
impl<'a, M, R> From<CompoundGraphic> for Element<'a, M, R>
|
||||
where
|
||||
R: self::Renderer,
|
||||
{
|
||||
fn from(compound_graphic: CompoundGraphic) -> Element<'a, M, R> {
|
||||
Element::new(compound_graphic)
|
||||
}
|
||||
}
|
@ -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::<Marker>().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
|
||||
}
|
||||
}
|
||||
|
||||
|
93
voxygen/src/ui/ice/widget/stack.rs
Normal file
93
voxygen/src/ui/ice/widget/stack.rs
Normal file
@ -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<Element<'a, M, R>>,
|
||||
}
|
||||
|
||||
impl<'a, M, R> Stack<'a, M, R>
|
||||
where
|
||||
R: self::Renderer,
|
||||
{
|
||||
pub fn with_children(children: Vec<Element<'a, M, R>>) -> Self { Self { children } }
|
||||
}
|
||||
|
||||
impl<'a, M, R> Widget<M, R> 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::<Marker>().hash(state);
|
||||
|
||||
self.children
|
||||
.iter()
|
||||
.for_each(|child| child.hash_layout(state));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Renderer: iced::Renderer {
|
||||
fn draw<M>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
children: &[Element<'_, M, Self>],
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, M, R> From<Stack<'a, M, R>> 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) }
|
||||
}
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user