mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
add ui text rendering
Former-commit-id: 5b9b809c660a2fdd178688bdaa63d16128debe08
This commit is contained in:
parent
e68fa6b579
commit
1962320f7e
@ -1,17 +1,22 @@
|
||||
#version 330 core
|
||||
|
||||
in vec3 f_pos;
|
||||
in vec2 f_uv;
|
||||
|
||||
layout (std140)
|
||||
uniform u_locals {
|
||||
vec4 bounds;
|
||||
};
|
||||
in vec4 f_color;
|
||||
flat in uint f_mode;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec4 tgt_color;
|
||||
|
||||
void main() {
|
||||
tgt_color = texture(u_tex, f_uv);
|
||||
// Text
|
||||
if (f_mode == uint(0)) {
|
||||
tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a);
|
||||
// Image
|
||||
} else if (f_mode == uint(1)) {
|
||||
tgt_color = texture(u_tex, f_uv);
|
||||
// 2D Geometry
|
||||
} else if (f_mode == uint(2)) {
|
||||
tgt_color = f_color;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,19 @@
|
||||
#version 330 core
|
||||
|
||||
in vec3 v_pos;
|
||||
in vec2 v_pos;
|
||||
in vec2 v_uv;
|
||||
|
||||
layout (std140)
|
||||
uniform u_locals {
|
||||
vec4 bounds;
|
||||
};
|
||||
in vec4 v_color;
|
||||
in uint v_mode;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec3 f_pos;
|
||||
out vec2 f_uv;
|
||||
flat out uint f_mode;
|
||||
out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
f_uv = v_uv;
|
||||
f_pos = vec3(vec2(bounds.x, bounds.y) + v_pos.xy * vec2(bounds.z, bounds.w), 0);
|
||||
f_pos.xy = vec2(f_pos.x * 2.0 - 1.0, f_pos.y * -2.0 + 1.0);
|
||||
|
||||
gl_Position = vec4(f_pos, 1);
|
||||
f_color = v_color;
|
||||
gl_Position = vec4(v_pos, 0.0, 1.0);
|
||||
f_mode = v_mode;
|
||||
}
|
||||
|
@ -5,14 +5,15 @@ use conrod_core::{
|
||||
Positionable,
|
||||
Sizeable,
|
||||
Widget,
|
||||
Labelable,
|
||||
widget_ids,
|
||||
event::{Widget as WidgetEvent, Input, Click},
|
||||
event::Input,
|
||||
image::Id as ImgId,
|
||||
input::state::mouse::Button as MouseButton,
|
||||
text::font::Id as FontId,
|
||||
widget::{
|
||||
Image,
|
||||
Button,
|
||||
Id as WidgId,
|
||||
Canvas,
|
||||
}
|
||||
};
|
||||
|
||||
@ -25,13 +26,14 @@ use crate::{
|
||||
|
||||
widget_ids!{
|
||||
struct Ids {
|
||||
menu_buttons[],
|
||||
bag,
|
||||
bag_contents,
|
||||
bag_close,
|
||||
menu_top,
|
||||
menu_mid,
|
||||
menu_bot,
|
||||
menu_canvas,
|
||||
menu_buttons[],
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +78,8 @@ pub struct TestHud {
|
||||
ids: Ids,
|
||||
imgs: Imgs,
|
||||
bag_open: bool,
|
||||
menu_open: bool,
|
||||
font_id: FontId,
|
||||
}
|
||||
|
||||
impl TestHud {
|
||||
@ -86,66 +90,111 @@ impl TestHud {
|
||||
ids.menu_buttons.resize(5, &mut ui.id_generator());
|
||||
// Load images
|
||||
let imgs = Imgs::new(&mut ui, window.renderer_mut());
|
||||
// Load font
|
||||
let font_id = ui.new_font(conrod_core::text::font::from_file(
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/font/Metamorphous-Regular.ttf")
|
||||
).unwrap());
|
||||
Self {
|
||||
ui,
|
||||
imgs,
|
||||
ids,
|
||||
bag_open: false,
|
||||
menu_open: false,
|
||||
font_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn ui_layout(&mut self) {
|
||||
// Update if a event has occured
|
||||
if !self.ui.global_input().events().next().is_some() {
|
||||
return;
|
||||
}
|
||||
// Process input
|
||||
for e in self.ui.widget_input(self.ids.bag).events() {
|
||||
match e {
|
||||
WidgetEvent::Click(click) => match click.button {
|
||||
MouseButton::Left => {
|
||||
self.bag_open = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for e in self.ui.widget_input(self.ids.bag_close).events() {
|
||||
match e {
|
||||
WidgetEvent::Click(click) => match click.button {
|
||||
MouseButton::Left => {
|
||||
self.bag_open = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let bag_open = self.bag_open;
|
||||
let mut ui_cell = self.ui.set_widgets();
|
||||
fn update_layout(&mut self) {
|
||||
// This is very useful
|
||||
let TestHud {
|
||||
ref mut ui,
|
||||
ref imgs,
|
||||
ref ids,
|
||||
ref mut bag_open,
|
||||
ref mut menu_open,
|
||||
..
|
||||
} = *self;
|
||||
|
||||
let ref mut ui_cell = ui.set_widgets();
|
||||
// Bag
|
||||
Button::image(self.imgs.bag)
|
||||
if Button::image(imgs.bag)
|
||||
.bottom_right_with_margin(20.0)
|
||||
.hover_image(if bag_open { self.imgs.bag } else { self.imgs.bag_hover })
|
||||
.press_image(if bag_open { self.imgs.bag } else { self.imgs.bag_press })
|
||||
.hover_image(if *bag_open { imgs.bag } else { imgs.bag_hover })
|
||||
.press_image(if *bag_open { imgs.bag } else { imgs.bag_press })
|
||||
.w_h(51.0, 58.0)
|
||||
.set(self.ids.bag, &mut ui_cell);
|
||||
.set(ids.bag, ui_cell)
|
||||
.was_clicked() {
|
||||
*bag_open = true;
|
||||
}
|
||||
// Bag contents
|
||||
if self.bag_open {
|
||||
if *bag_open {
|
||||
// Contents
|
||||
Image::new(self.imgs.bag_contents)
|
||||
Image::new(imgs.bag_contents)
|
||||
.w_h(212.0, 246.0)
|
||||
.x_y_relative_to(self.ids.bag, -92.0-25.5+12.0, 109.0+29.0-13.0)
|
||||
.set(self.ids.bag_contents, &mut ui_cell);
|
||||
.x_y_relative_to(ids.bag, -92.0-25.5+12.0, 109.0+29.0-13.0)
|
||||
.set(ids.bag_contents, ui_cell);
|
||||
// Close button
|
||||
Button::image(self.imgs.close_button)
|
||||
if Button::image(imgs.close_button)
|
||||
.w_h(20.0, 20.0)
|
||||
.hover_image(self.imgs.close_button_hover)
|
||||
.press_image(self.imgs.close_button_press)
|
||||
.top_right_with_margins_on(self.ids.bag_contents, 0.0, 10.0)
|
||||
.set(self.ids.bag_close, &mut ui_cell);
|
||||
.hover_image(imgs.close_button_hover)
|
||||
.press_image(imgs.close_button_press)
|
||||
.top_right_with_margins_on(ids.bag_contents, 0.0, 10.0)
|
||||
.set(ids.bag_close, ui_cell)
|
||||
.was_clicked() {
|
||||
*bag_open = false;
|
||||
}
|
||||
}
|
||||
// Attempt to make resizable image based container for buttons
|
||||
// Maybe this could be made into a Widget type if it is useful
|
||||
if *menu_open {
|
||||
let num = ids.menu_buttons.len();
|
||||
// Canvas to hold everything together
|
||||
Canvas::new()
|
||||
.w_h(106.0, 54.0 + num as f64 * 30.0)
|
||||
.middle_of(ui_cell.window)
|
||||
.set(ids.menu_canvas, ui_cell);
|
||||
// Top of menu
|
||||
Image::new(imgs.menu_top)
|
||||
.w_h(106.0, 28.0)
|
||||
.mid_top_of(ids.menu_canvas)
|
||||
// Does not work because of bug in conrod, but above line is equivalent
|
||||
//.parent(ids.menu_canvas)
|
||||
//.mid_top()
|
||||
.set(ids.menu_top, ui_cell);
|
||||
// Bottom of Menu
|
||||
// Note: conrod defaults to the last used parent
|
||||
Image::new(imgs.menu_bot)
|
||||
.w_h(106.0, 26.0)
|
||||
.mid_bottom()
|
||||
.set(ids.menu_bot, ui_cell);
|
||||
// Midsection background
|
||||
Image::new(imgs.menu_mid)
|
||||
.w_h(106.0, num as f64 * 30.0)
|
||||
.mid_bottom_with_margin(26.0)
|
||||
.set(ids.menu_mid, ui_cell);
|
||||
// Menu buttons
|
||||
if num > 0 {
|
||||
Button::image(imgs.menu_button)
|
||||
.mid_top_with_margin_on(ids.menu_mid, 8.0)
|
||||
.w_h(48.0, 20.0)
|
||||
.label(&format!("Button {}", 1))
|
||||
.label_rgb(1.0, 0.4, 1.0)
|
||||
.label_font_size(7)
|
||||
.set(ids.menu_buttons[0], ui_cell);
|
||||
}
|
||||
for i in 1..num {
|
||||
Button::image(imgs.menu_button)
|
||||
.down(10.0)
|
||||
.label(&format!("Button {}", i + 1))
|
||||
.label_rgb(1.0, 0.4, 1.0)
|
||||
.label_font_size(7)
|
||||
.set(ids.menu_buttons[i], ui_cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_menu(&mut self) {
|
||||
self.menu_open = !self.menu_open;
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, input: Input) {
|
||||
@ -153,7 +202,7 @@ impl TestHud {
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer) {
|
||||
self.ui_layout();
|
||||
self.update_layout();
|
||||
self.ui.maintain(renderer);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
// Library
|
||||
use vek::*;
|
||||
use image;
|
||||
|
||||
|
||||
// Crate
|
||||
|
@ -1,13 +1,11 @@
|
||||
// Library
|
||||
use conrod_core::{
|
||||
Positionable,
|
||||
Sizeable,
|
||||
Widget,
|
||||
event::Input,
|
||||
image::Id as ImgId,
|
||||
widget::{
|
||||
Image as ImageWidget,
|
||||
Canvas as CanvasWidget,
|
||||
Id as WidgId,
|
||||
}
|
||||
};
|
||||
@ -39,14 +37,9 @@ impl TitleUi {
|
||||
}
|
||||
|
||||
fn ui_layout(&mut self) {
|
||||
// Update if a event has occured
|
||||
if !self.ui.global_input().events().next().is_some() {
|
||||
return;
|
||||
}
|
||||
let mut ui_cell = self.ui.set_widgets();
|
||||
ImageWidget::new(self.title_img_id)
|
||||
.top_left()
|
||||
.w_h(500.0, 500.0)
|
||||
.set(self.widget_id, &mut ui_cell);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,11 @@ impl<P: Pipeline> Mesh<P> {
|
||||
Self { verts: vec![] }
|
||||
}
|
||||
|
||||
/// Clear vertices, allows reusing allocated memory of the underlying Vec
|
||||
pub fn clear(&mut self) {
|
||||
self.verts.clear();
|
||||
}
|
||||
|
||||
/// Get a slice referencing the vertices of this mesh.
|
||||
pub fn vertices(&self) -> &[P::Vertex] {
|
||||
&self.verts
|
||||
|
@ -30,9 +30,9 @@ pub use self::{
|
||||
Locals as TerrainLocals,
|
||||
},
|
||||
ui::{
|
||||
create_quad_mesh as create_ui_quad_mesh,
|
||||
push_quad_to_mesh as push_ui_quad_to_mesh,
|
||||
Mode as UiMode,
|
||||
UiPipeline,
|
||||
Locals as UiLocals,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -48,6 +48,7 @@ use gfx;
|
||||
pub enum RenderError {
|
||||
PipelineError(gfx::PipelineStateError<String>),
|
||||
UpdateError(gfx::UpdateError<usize>),
|
||||
TexUpdateError(gfx::UpdateError<[u16; 3]>),
|
||||
CombinedError(gfx::CombinedError),
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ use gfx::{
|
||||
// Macros
|
||||
gfx_defines,
|
||||
gfx_vertex_struct_meta,
|
||||
gfx_constant_struct_meta,
|
||||
gfx_impl_struct_meta,
|
||||
gfx_pipeline,
|
||||
gfx_pipeline_inner,
|
||||
@ -21,53 +20,70 @@ use super::super::{
|
||||
|
||||
gfx_defines! {
|
||||
vertex Vertex {
|
||||
pos: [f32; 3] = "v_pos",
|
||||
pos: [f32; 2] = "v_pos",
|
||||
uv: [f32; 2] = "v_uv",
|
||||
}
|
||||
|
||||
constant Locals {
|
||||
bounds: [f32; 4] = "bounds",
|
||||
color: [f32; 4] = "v_color",
|
||||
mode: u32 = "v_mode",
|
||||
}
|
||||
|
||||
pipeline pipe {
|
||||
vbuf: gfx::VertexBuffer<Vertex> = (),
|
||||
|
||||
locals: gfx::ConstantBuffer<Locals> = "u_locals",
|
||||
tex: gfx::TextureSampler<[f32; 4]> = "u_tex",
|
||||
|
||||
scissor: gfx::Scissor = (),
|
||||
|
||||
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA),
|
||||
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::PASS_TEST,
|
||||
}
|
||||
}
|
||||
|
||||
impl Locals {
|
||||
pub fn default() -> Self {
|
||||
Self { bounds: [0.0, 0.0, 1.0, 1.0] }
|
||||
}
|
||||
|
||||
pub fn new(bounds: [f32; 4]) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiPipeline;
|
||||
|
||||
impl Pipeline for UiPipeline {
|
||||
type Vertex = Vertex;
|
||||
}
|
||||
|
||||
pub fn create_quad_mesh() -> Mesh<UiPipeline> {
|
||||
let mut mesh = Mesh::new();
|
||||
/// Draw text from the text cache texture `tex` in the fragment shader.
|
||||
pub const MODE_TEXT: u32 = 0;
|
||||
/// Draw an image from the texture at `tex` in the fragment shader.
|
||||
pub const MODE_IMAGE: u32 = 1;
|
||||
/// Ignore `tex` and draw simple, colored 2D geometry.
|
||||
pub const MODE_GEOMETRY: u32 = 2;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mesh.push_quad(Quad::new(
|
||||
Vertex { pos: [0.0, 0.0, 0.0], uv: [0.0, 0.0] },
|
||||
Vertex { pos: [0.0, 1.0, 0.0], uv: [0.0, 1.0] },
|
||||
Vertex { pos: [1.0, 1.0, 0.0], uv: [1.0, 1.0] },
|
||||
Vertex { pos: [1.0, 0.0, 0.0], uv: [1.0, 0.0] },
|
||||
));
|
||||
|
||||
mesh
|
||||
pub enum Mode {
|
||||
Text,
|
||||
Image,
|
||||
Geometry,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
fn value(self) -> u32 {
|
||||
match self {
|
||||
Mode::Text => MODE_TEXT,
|
||||
Mode::Image => MODE_IMAGE,
|
||||
Mode::Geometry => MODE_GEOMETRY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: don't use [f32; 4] for rectangle as the format (eg 2 points vs point + dims) is ambiguous
|
||||
pub fn push_quad_to_mesh(mesh: &mut Mesh<UiPipeline>, rect: [f32; 4], uv_rect: [f32; 4], color: [f32; 4], mode: Mode) {
|
||||
let mode_val = mode.value();
|
||||
let v = |pos, uv| {
|
||||
Vertex {
|
||||
pos,
|
||||
uv,
|
||||
color,
|
||||
mode: mode_val,
|
||||
}
|
||||
};
|
||||
let (l, t, r, b) = (rect[0], rect[1], rect[2], rect[3]);
|
||||
let (uv_l, uv_t, uv_r, uv_b) = (uv_rect[0], uv_rect[1], uv_rect[2], uv_rect[3]);
|
||||
mesh.push_quad(Quad::new(
|
||||
v([r, t], [uv_r, uv_t]),
|
||||
v([l, t], [uv_l, uv_t]),
|
||||
v([l, b], [uv_l, uv_b]),
|
||||
v([r, b], [uv_r, uv_b]),
|
||||
));
|
||||
}
|
||||
|
@ -173,6 +173,34 @@ impl Renderer {
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the specified dimensions
|
||||
pub fn create_dynamic_texture<P: Pipeline>(
|
||||
&mut self,
|
||||
dims: Vec2<u16>
|
||||
) -> Result<Texture<P>, RenderError> {
|
||||
Texture::new_dynamic(
|
||||
&mut self.factory,
|
||||
dims.x,
|
||||
dims.y,
|
||||
)
|
||||
}
|
||||
|
||||
/// Update a texture with the provided offset, size, and data
|
||||
pub fn update_texture<P: Pipeline>(
|
||||
&mut self,
|
||||
texture: &Texture<P>,
|
||||
offset: [u16; 2],
|
||||
size: [u16; 2],
|
||||
data: &[[u8; 4]]
|
||||
) -> Result<(), RenderError> {
|
||||
texture.update(
|
||||
&mut self.encoder,
|
||||
offset,
|
||||
size,
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
/// Queue the rendering of the provided skybox model in the upcoming frame.
|
||||
pub fn render_skybox(
|
||||
&mut self,
|
||||
@ -239,15 +267,15 @@ impl Renderer {
|
||||
pub fn render_ui_element(
|
||||
&mut self,
|
||||
model: &Model<ui::UiPipeline>,
|
||||
locals: &Consts<ui::Locals>,
|
||||
tex: &Texture<ui::UiPipeline>,
|
||||
) {
|
||||
let (width, height) = self.get_resolution().map(|e| e).into_tuple();
|
||||
self.encoder.draw(
|
||||
&model.slice,
|
||||
&self.ui_pipeline.pso,
|
||||
&ui::pipe::Data {
|
||||
vbuf: model.vbuf.clone(),
|
||||
locals: locals.buf.clone(),
|
||||
scissor: gfx::Rect { x: 0, y: 0, w: width, h: height },
|
||||
tex: (tex.srv.clone(), tex.sampler.clone()),
|
||||
tgt_color: self.tgt_color_view.clone(),
|
||||
tgt_depth: self.tgt_depth_view.clone(),
|
||||
|
@ -54,4 +54,62 @@ impl<P: Pipeline> Texture<P> {
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
pub fn new_dynamic(
|
||||
factory: &mut gfx_backend::Factory,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> Result<Self, RenderError> {
|
||||
let tex = factory.create_texture(
|
||||
gfx::texture::Kind::D2(
|
||||
width,
|
||||
height,
|
||||
gfx::texture::AaMode::Single,
|
||||
),
|
||||
1 as gfx::texture::Level,
|
||||
gfx::memory::Bind::SHADER_RESOURCE,
|
||||
gfx::memory::Usage::Dynamic,
|
||||
Some(<<ShaderFormat as gfx::format::Formatted>::Channel as gfx::format::ChannelTyped>::get_channel_type()),
|
||||
)
|
||||
.map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?;
|
||||
|
||||
let srv =
|
||||
factory.view_texture_as_shader_resource::<ShaderFormat>(&tex, (0, 0), gfx::format::Swizzle::new())
|
||||
.map_err(|err| RenderError::CombinedError(gfx::CombinedError::Resource(err)))?;
|
||||
|
||||
Ok(Self {
|
||||
tex,
|
||||
srv,
|
||||
// TODO: is this the right sampler?
|
||||
sampler: factory.create_sampler(gfx::texture::SamplerInfo::new(
|
||||
gfx::texture::FilterMethod::Scale,
|
||||
//this is what conrod's gfx backend uses but i want to see the other one first to compare
|
||||
//gfx::texture::FilterMethod::Bilinear
|
||||
gfx::texture::WrapMode::Clamp,
|
||||
)),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
// Updates a texture with the given data (used for updating the GlyphCache texture)
|
||||
pub fn update(
|
||||
&self,
|
||||
encoder: &mut gfx::Encoder<gfx_backend::Resources, gfx_backend::CommandBuffer>,
|
||||
offset: [u16; 2],
|
||||
size: [u16; 2],
|
||||
data: &[[u8; 4]],
|
||||
) -> Result<(), RenderError> {
|
||||
let info = gfx::texture::ImageInfoCommon {
|
||||
xoffset: offset[0],
|
||||
yoffset: offset[1],
|
||||
zoffset: 0,
|
||||
width: size[0],
|
||||
height: size[1],
|
||||
depth: 0,
|
||||
format: (),
|
||||
mipmap: 0,
|
||||
};
|
||||
encoder
|
||||
.update_texture::<<ShaderFormat as gfx::format::Formatted>::Surface, ShaderFormat>(&self.tex, None, info, data)
|
||||
.map_err(|err| RenderError::TexUpdateError(err))
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ impl PlayState for SessionState {
|
||||
Event::Close => return PlayStateResult::Shutdown,
|
||||
// When 'q' is pressed, exit the session
|
||||
Event::Char('q') => return PlayStateResult::Pop,
|
||||
// When 'm' is pressed, open/close the in-game test menu
|
||||
Event::Char('m') => self.test_hud.toggle_menu(),
|
||||
// Toggle cursor grabbing
|
||||
Event::KeyDown(Key::ToggleCursor) => {
|
||||
global_state.window.grab_cursor(!global_state.window.is_cursor_grabbed());
|
||||
|
@ -7,11 +7,16 @@ use conrod_core::{
|
||||
Ui as CrUi,
|
||||
UiBuilder,
|
||||
UiCell,
|
||||
text::{
|
||||
Font,
|
||||
GlyphCache,
|
||||
font::Id as FontId,
|
||||
},
|
||||
image::{Map, Id as ImgId},
|
||||
widget::{Id as WidgId, id::Generator},
|
||||
render::Primitive,
|
||||
event::Input,
|
||||
input::{Global, Widget},
|
||||
input::Widget,
|
||||
};
|
||||
|
||||
// Crate
|
||||
@ -21,11 +26,11 @@ use crate::{
|
||||
RenderError,
|
||||
Renderer,
|
||||
Model,
|
||||
Mesh,
|
||||
Texture,
|
||||
UiPipeline,
|
||||
UiLocals,
|
||||
Consts,
|
||||
create_ui_quad_mesh,
|
||||
UiMode,
|
||||
push_ui_quad_to_mesh,
|
||||
},
|
||||
window::Window,
|
||||
};
|
||||
@ -36,25 +41,38 @@ pub enum UiError {
|
||||
}
|
||||
|
||||
pub struct Cache {
|
||||
model: Model<UiPipeline>,
|
||||
blank_texture: Texture<UiPipeline>,
|
||||
glyph_cache: GlyphCache<'static>,
|
||||
glyph_cache_tex: Texture<UiPipeline>,
|
||||
}
|
||||
|
||||
// TODO: Should functions be returning UiError instead of Error?
|
||||
impl Cache {
|
||||
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
|
||||
// TODO: remove map if it is uneeded(or remove this comment)
|
||||
let (w, h) = renderer.get_resolution().map(|e| e).into_tuple();
|
||||
const SCALE_TOLERANCE: f32 = 0.1;
|
||||
const POSITION_TOLERANCE: f32 = 0.1;
|
||||
|
||||
Ok(Self {
|
||||
model: renderer.create_model(&create_ui_quad_mesh())?,
|
||||
blank_texture: renderer.create_texture(&DynamicImage::new_rgba8(1, 1))?,
|
||||
glyph_cache: GlyphCache::builder()
|
||||
.dimensions(w as u32, h as u32)
|
||||
.scale_tolerance(SCALE_TOLERANCE)
|
||||
.position_tolerance(POSITION_TOLERANCE)
|
||||
.build(),
|
||||
glyph_cache_tex: renderer.create_dynamic_texture((w, h).into())?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn model(&self) -> &Model<UiPipeline> { &self.model }
|
||||
pub fn blank_texture(&self) -> &Texture<UiPipeline> { &self.blank_texture }
|
||||
pub fn glyph_cache_tex(&self) -> &Texture<UiPipeline> { &self.glyph_cache_tex }
|
||||
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture<UiPipeline>) { (&mut self.glyph_cache, &self.glyph_cache_tex) }
|
||||
}
|
||||
|
||||
pub enum UiPrimitive {
|
||||
Image(Consts<UiLocals>, ImgId)
|
||||
pub enum DrawCommand {
|
||||
Image(Model<UiPipeline>, ImgId),
|
||||
// Text and non-textured geometry
|
||||
Plain(Model<UiPipeline>),
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
@ -62,7 +80,7 @@ pub struct Ui {
|
||||
image_map: Map<Texture<UiPipeline>>,
|
||||
cache: Cache,
|
||||
// Primatives to draw on the next render
|
||||
ui_primitives: Vec<UiPrimitive>,
|
||||
draw_commands: Vec<DrawCommand>,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
@ -73,7 +91,7 @@ impl Ui {
|
||||
ui: UiBuilder::new([w, h]).build(),
|
||||
image_map: Map::new(),
|
||||
cache: Cache::new(window.renderer_mut())?,
|
||||
ui_primitives: vec![],
|
||||
draw_commands: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
@ -81,6 +99,10 @@ impl Ui {
|
||||
Ok(self.image_map.insert(renderer.create_texture(image)?))
|
||||
}
|
||||
|
||||
pub fn new_font(&mut self, font: Font) -> FontId {
|
||||
self.ui.fonts.insert(font)
|
||||
}
|
||||
|
||||
pub fn id_generator(&mut self) -> Generator {
|
||||
self.ui.widget_id_generator()
|
||||
}
|
||||
@ -97,67 +119,184 @@ impl Ui {
|
||||
self.ui.widget_input(id)
|
||||
}
|
||||
|
||||
pub fn global_input(&self) -> &Global {
|
||||
self.ui.global_input()
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer) {
|
||||
let ref mut ui = self.ui;
|
||||
// removed this because ui will resize itself now that it recieves events
|
||||
// update window size
|
||||
//let res = renderer.get_resolution().map(|e| e as f64);
|
||||
//if res[0] != ui.win_w || res[1] != ui.win_h {
|
||||
// ui.win_w = res[0];
|
||||
// ui.win_h = res[1];
|
||||
// ui.needs_redraw();
|
||||
//}
|
||||
// Gather primatives and recreate locals only if ui_changed
|
||||
// Gather primatives and recreate "mesh" only if ui_changed
|
||||
if let Some(mut primitives) = ui.draw_if_changed() {
|
||||
self.ui_primitives.clear();
|
||||
self.draw_commands.clear();
|
||||
let mut mesh = Mesh::new();
|
||||
|
||||
let mut current_img = None;
|
||||
|
||||
// Switches to the `Plain` state and completes the previous `Command` if not already in the
|
||||
// `Plain` state.
|
||||
macro_rules! switch_to_plain_state {
|
||||
() => {
|
||||
if let Some(image_id) = current_img.take() {
|
||||
self.draw_commands.push(DrawCommand::Image(renderer.create_model(&mesh).unwrap(), image_id));
|
||||
mesh.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
while let Some(prim) = primitives.next() {
|
||||
// TODO: Use scizzor
|
||||
let Primitive {kind, scizzor, id, rect} = prim;
|
||||
// Transform from conrod to our render coords
|
||||
// Conrod uses the center of the screen as the origin
|
||||
// Up & Right are positive directions
|
||||
let x = prim.rect.left();
|
||||
let y = prim.rect.top();
|
||||
let (w, h) = prim.rect.w_h();
|
||||
/*let x = rect.left();
|
||||
let y = rect.top();
|
||||
let (w, h) = rect.w_h();
|
||||
let bounds = [
|
||||
(x / ui.win_w + 0.5) as f32,
|
||||
(-1.0 * (y / ui.win_h) + 0.5) as f32,
|
||||
(w / ui.win_w) as f32,
|
||||
(h / ui.win_h) as f32
|
||||
];
|
||||
// TODO: Remove this
|
||||
let new_ui_locals = renderer.create_consts(&[UiLocals::new(bounds)])
|
||||
.expect("Could not create new const for ui locals");
|
||||
use conrod_core::render::{PrimitiveKind};
|
||||
// TODO: Use scizzor
|
||||
let Primitive {kind, scizzor, id, ..} = prim;
|
||||
];*/
|
||||
use conrod_core::render::PrimitiveKind;
|
||||
match kind {
|
||||
// TODO: use source_rect
|
||||
PrimitiveKind::Image { image_id, color, source_rect } => {
|
||||
//renderer.update_consts(&mut self.locals, &[UiLocals::new(
|
||||
// [0.0, 0.0, 1.0, 1.0],
|
||||
// )]);
|
||||
self.ui_primitives.push(UiPrimitive::Image(new_ui_locals, image_id));
|
||||
|
||||
// Switch to the `Image` state for this image if we're not in it already.
|
||||
let new_image_id = image_id;
|
||||
match current_img {
|
||||
// If we're already in the drawing mode for this image, we're done.
|
||||
Some(image_id) if image_id == new_image_id => (),
|
||||
// If we were in the `Plain` drawing state, switch to Image drawing state.
|
||||
None => {
|
||||
self.draw_commands.push(DrawCommand::Plain(renderer.create_model(&mesh).unwrap()));
|
||||
mesh.clear();
|
||||
current_img = Some(new_image_id);
|
||||
}
|
||||
// If we were drawing a different image, switch state to draw *this* image.
|
||||
Some(image_id) => {
|
||||
self.draw_commands.push(DrawCommand::Image(renderer.create_model(&mesh).unwrap(), image_id));
|
||||
mesh.clear();
|
||||
current_img = Some(new_image_id);
|
||||
}
|
||||
}
|
||||
|
||||
let color = color.unwrap_or(conrod_core::color::WHITE).to_fsa();
|
||||
|
||||
//let (image_w, image_h) = image_map.get(&image_id).unwrap().1;
|
||||
//let (image_w, image_h) = (image_w as Scalar, image_h as Scalar);
|
||||
|
||||
// Get the sides of the source rectangle as uv coordinates.
|
||||
//
|
||||
// Texture coordinates range:
|
||||
// - left to right: 0.0 to 1.0
|
||||
// - bottom to top: 1.0 to 0.0
|
||||
// Note bottom and top are flipped in comparison to glium so that we don't need to flip images when loading
|
||||
/*let (uv_l, uv_r, uv_t, uv_b) = match source_rect {
|
||||
Some(src_rect) => {
|
||||
let (l, r, b, t) = src_rect.l_r_b_t();
|
||||
((l / image_w) as f32,
|
||||
(r / image_w) as f32,
|
||||
(b / image_h) as f32,
|
||||
(t / image_h) as f32)
|
||||
}
|
||||
None => (0.0, 1.0, 0.0, 1.0),
|
||||
};*/
|
||||
let (uv_l, uv_r, uv_t, uv_b) = (0.0, 1.0, 0.0, 1.0);
|
||||
let (l, r, b, t) = rect.l_r_b_t();
|
||||
// Convert from conrod Scalar range to GL range -1.0 to 1.0.
|
||||
let (l, r, b, t) = (
|
||||
(l / ui.win_w * 2.0) as f32,
|
||||
(r / ui.win_w * 2.0) as f32,
|
||||
(b / ui.win_h * 2.0) as f32,
|
||||
(t / ui.win_h * 2.0) as f32,
|
||||
);
|
||||
push_ui_quad_to_mesh(
|
||||
&mut mesh,
|
||||
[l, t , r, b],
|
||||
[uv_l, uv_t, uv_r, uv_b],
|
||||
color,
|
||||
UiMode::Image,
|
||||
);
|
||||
|
||||
}
|
||||
PrimitiveKind::Text { color, text, font_id } => {
|
||||
switch_to_plain_state!();
|
||||
// Get screen width
|
||||
let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as f32).into_tuple();
|
||||
// Calculate dpi factor
|
||||
let dpi_factor = screen_w / ui.win_w as f32;
|
||||
|
||||
let positioned_glyphs = text.positioned_glyphs(dpi_factor);
|
||||
let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex();
|
||||
// Queue the glyphs to be cached
|
||||
for glyph in positioned_glyphs {
|
||||
glyph_cache.queue_glyph(font_id.index(), glyph.clone());
|
||||
}
|
||||
|
||||
glyph_cache.cache_queued(|rect, data| {
|
||||
let offset = [rect.min.x as u16, rect.min.y as u16];
|
||||
let size = [rect.width() as u16, rect.height() as u16];
|
||||
|
||||
let new_data = data.iter().map(|x| [255, 255, 255, *x]).collect::<Vec<[u8; 4]>>();
|
||||
|
||||
renderer.update_texture(cache_tex, offset, size, &new_data);
|
||||
}).unwrap();
|
||||
|
||||
// TODO: consider gamma....
|
||||
let color = color.to_fsa();
|
||||
|
||||
for g in positioned_glyphs {
|
||||
if let Ok(Some((uv_rect, screen_rect))) = glyph_cache.rect_for(font_id.index(), g) {
|
||||
let (uv_l, uv_r, uv_t, uv_b) = (
|
||||
uv_rect.min.x,
|
||||
uv_rect.max.x,
|
||||
uv_rect.min.y,
|
||||
uv_rect.max.y,
|
||||
);
|
||||
let (l, t, r, b) = (
|
||||
(screen_rect.min.x as f32 / screen_w - 0.5) * 2.0,
|
||||
(screen_rect.min.y as f32 / screen_h - 0.5) * -2.0,
|
||||
(screen_rect.max.x as f32 / screen_w - 0.5) * 2.0,
|
||||
(screen_rect.max.y as f32 / screen_h - 0.5) * -2.0,
|
||||
);
|
||||
push_ui_quad_to_mesh(
|
||||
&mut mesh,
|
||||
[l, t , r, b],
|
||||
[uv_l, uv_t, uv_r, uv_b],
|
||||
color,
|
||||
UiMode::Text,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
// TODO: Add these
|
||||
//PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);}
|
||||
//PrimitiveKind::Rectangle { color } => {println!("primitive kind rect[x:{},y:{},w:{},h:{}] with color {:?} and id {:?}", x, y, w, h, color, id);}
|
||||
//PrimitiveKind::Text {..} => {println!("primitive kind text with id {:?}", id);}
|
||||
//PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);}
|
||||
//PrimitiveKind::TrianglesSingleColor {..} => {println!("primitive kind singlecolor with id {:?}", id);}
|
||||
}
|
||||
}
|
||||
// Enter the final command.
|
||||
match current_img {
|
||||
None =>
|
||||
self.draw_commands.push(DrawCommand::Plain(renderer.create_model(&mesh).unwrap())),
|
||||
Some(image_id) =>
|
||||
self.draw_commands.push(DrawCommand::Image(renderer.create_model(&mesh).unwrap(), image_id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer) {
|
||||
self.ui_primitives.iter().for_each(|ui_primitive| match ui_primitive {
|
||||
UiPrimitive::Image(ui_locals, image_id) => {
|
||||
let tex = self.image_map.get(&image_id).expect("Image does not exist in image map");
|
||||
renderer.render_ui_element(&self.cache.model(), &ui_locals, &tex);
|
||||
for draw_command in self.draw_commands.iter() {
|
||||
match draw_command {
|
||||
DrawCommand::Image(model, image_id) => {
|
||||
let tex = self.image_map.get(&image_id).expect("Image does not exist in image map");
|
||||
renderer.render_ui_element(&model, &tex);
|
||||
},
|
||||
DrawCommand::Plain(model) => {
|
||||
let tex = self.cache.glyph_cache_tex();
|
||||
renderer.render_ui_element(&model, &tex);
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
voxygen/test_assets/test_menu_top.png
(Stored with Git LFS)
BIN
voxygen/test_assets/test_menu_top.png
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue
Block a user