add ui text rendering

Former-commit-id: 5b9b809c660a2fdd178688bdaa63d16128debe08
This commit is contained in:
Imberflur 2019-02-22 21:41:52 -05:00 committed by Imbris
parent e68fa6b579
commit 1962320f7e
13 changed files with 448 additions and 156 deletions

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
// Library
use vek::*;
use image;
// Crate

View File

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

View File

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

View File

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

View File

@ -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]),
));
}

View File

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

View File

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

View File

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

View File

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

Binary file not shown.