Add Stack and CompoundGraphic widgets

This commit is contained in:
Imbris 2020-04-16 00:27:25 -04:00
parent b5d31f6cba
commit 5428feb0df
12 changed files with 441 additions and 35 deletions

Binary file not shown.

View File

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

View File

@ -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;
}
}

View 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,
)
}
}

View File

@ -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>,

View 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,
)
}
}

View File

@ -1,2 +1,4 @@
pub mod background_container;
pub mod compound_graphic;
pub mod image;
pub mod stack;

View File

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

View 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)
}
}

View File

@ -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
}
}

View 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) }
}

View File

@ -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,