456 lines
13 KiB
Rust
456 lines
13 KiB
Rust
|
|
||
|
use std::sync::{Arc, RwLock};
|
||
|
use std::io::Write;
|
||
|
use std::collections::HashMap;
|
||
|
use resources;
|
||
|
use gl;
|
||
|
use render;
|
||
|
use byteorder::{WriteBytesExt, NativeEndian};
|
||
|
use image;
|
||
|
use image::{GenericImage};
|
||
|
|
||
|
const UI_WIDTH: f64 = 854.0;
|
||
|
const UI_HEIGHT: f64 = 480.0;
|
||
|
|
||
|
pub struct UIState {
|
||
|
textures: Arc<RwLock<render::TextureManager>>,
|
||
|
resources: Arc<RwLock<resources::Manager>>,
|
||
|
version: usize,
|
||
|
|
||
|
data: Vec<u8>,
|
||
|
prev_size: usize,
|
||
|
count: usize,
|
||
|
|
||
|
array: gl::VertexArray,
|
||
|
buffer: gl::Buffer,
|
||
|
index_buffer: gl::Buffer,
|
||
|
index_type: gl::Type,
|
||
|
max_index: usize,
|
||
|
|
||
|
shader: gl::Program,
|
||
|
s_position: gl::Attribute,
|
||
|
s_texture_info: gl::Attribute,
|
||
|
s_texture_offset: gl::Attribute,
|
||
|
s_color: gl::Attribute,
|
||
|
s_texture: gl::Uniform,
|
||
|
s_screensize: gl::Uniform,
|
||
|
|
||
|
// Font
|
||
|
font_pages: Vec<Option<render::Texture>>,
|
||
|
font_character_info: [(i32,i32); 0x10000],
|
||
|
char_map: HashMap<char, char>,
|
||
|
page_width: f64,
|
||
|
page_height: f64,
|
||
|
}
|
||
|
|
||
|
impl UIState {
|
||
|
pub fn new(glsl: &super::glsl::Registry, textures: Arc<RwLock<render::TextureManager>>, res: Arc<RwLock<resources::Manager>>) -> UIState {
|
||
|
let v = glsl.get("ui_vertex");
|
||
|
let f = glsl.get("ui_frag");
|
||
|
let shader = super::create_program(&v, &f);
|
||
|
let s_position = shader.attribute_location("aPosition");
|
||
|
let s_texture_info = shader.attribute_location("aTextureInfo");
|
||
|
let s_texture_offset = shader.attribute_location("aTextureOffset");
|
||
|
let s_color = shader.attribute_location("aColor");
|
||
|
let s_texture = shader.uniform_location("textures");
|
||
|
let s_screensize = shader.uniform_location("screenSize");
|
||
|
|
||
|
let array = gl::VertexArray::new();
|
||
|
array.bind();
|
||
|
let buffer = gl::Buffer::new();
|
||
|
buffer.bind(gl::ARRAY_BUFFER);
|
||
|
s_position.enable();
|
||
|
s_texture_info.enable();
|
||
|
s_texture_offset.enable();
|
||
|
s_color.enable();
|
||
|
s_position.vertex_pointer_int(3, gl::SHORT, 28, 0);
|
||
|
s_texture_info.vertex_pointer(4, gl::UNSIGNED_SHORT, false, 28, 8);
|
||
|
s_texture_offset.vertex_pointer_int(3, gl::SHORT, 28, 16);
|
||
|
s_color.vertex_pointer(4, gl::UNSIGNED_BYTE, true, 28, 24);
|
||
|
|
||
|
let index_buffer = gl::Buffer::new();
|
||
|
index_buffer.bind(gl::ELEMENT_ARRAY_BUFFER);
|
||
|
|
||
|
let mut pages = Vec::with_capacity(0x100);
|
||
|
for _ in 0 .. 0x100 {
|
||
|
pages.push(Option::None);
|
||
|
}
|
||
|
|
||
|
let mut char_map = HashMap::new();
|
||
|
let ascii_chars = "ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■";
|
||
|
let mut pos = 0u32;
|
||
|
for c in ascii_chars.chars() {
|
||
|
char_map.insert(c, ::std::char::from_u32(pos).unwrap());
|
||
|
pos += 1;
|
||
|
}
|
||
|
|
||
|
UIState {
|
||
|
textures: textures,
|
||
|
resources: res,
|
||
|
version: 0xFFFF,
|
||
|
|
||
|
data: Vec::new(),
|
||
|
count: 0,
|
||
|
prev_size: 0,
|
||
|
|
||
|
index_type: gl::UNSIGNED_BYTE,
|
||
|
array: array,
|
||
|
buffer: buffer,
|
||
|
index_buffer: index_buffer,
|
||
|
max_index: 0,
|
||
|
|
||
|
shader: shader,
|
||
|
s_position: s_position,
|
||
|
s_texture_info: s_texture_info,
|
||
|
s_texture_offset: s_texture_offset,
|
||
|
s_color: s_color,
|
||
|
s_texture: s_texture,
|
||
|
s_screensize: s_screensize,
|
||
|
|
||
|
// Font
|
||
|
font_pages: pages,
|
||
|
font_character_info: [(0, 0); 0x10000],
|
||
|
char_map: char_map,
|
||
|
page_width: 0.0,
|
||
|
page_height: 0.0,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn tick(&mut self, width: u32, height: u32) {
|
||
|
{
|
||
|
let version = self.resources.read().unwrap().version();
|
||
|
if self.version != version {
|
||
|
self.version = version;
|
||
|
self.load_font();
|
||
|
}
|
||
|
}
|
||
|
// Prevent clipping with the world
|
||
|
gl::clear(gl::ClearFlags::Depth);
|
||
|
gl::depth_func(gl::LESS_OR_EQUAL);
|
||
|
gl::enable(gl::BLEND);
|
||
|
|
||
|
self.shader.use_program();
|
||
|
self.s_texture.set_int(0);
|
||
|
if self.count > 0 {
|
||
|
self.array.bind();
|
||
|
if self.max_index < self.count {
|
||
|
let (data, ty) = render::generate_element_buffer(self.count);
|
||
|
self.index_type = ty;
|
||
|
self.index_buffer.bind(gl::ELEMENT_ARRAY_BUFFER);
|
||
|
self.index_buffer.set_data(gl::ELEMENT_ARRAY_BUFFER, &data, gl::DYNAMIC_DRAW);
|
||
|
self.max_index = self.count;
|
||
|
}
|
||
|
|
||
|
self.s_screensize.set_float2(width as f32, height as f32);
|
||
|
|
||
|
self.buffer.bind(gl::ARRAY_BUFFER);
|
||
|
if self.data.len() > self.prev_size {
|
||
|
self.prev_size = self.data.len();
|
||
|
self.buffer.set_data(gl::ARRAY_BUFFER, &self.data, gl::STREAM_DRAW);
|
||
|
} else {
|
||
|
let mut target = self.buffer.map(gl::ARRAY_BUFFER, gl::WRITE_ONLY, self.data.len());
|
||
|
target.write_all(&self.data[..]).unwrap();
|
||
|
}
|
||
|
gl::draw_elements(gl::TRIANGLES, self.count, self.index_type, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
gl::disable(gl::BLEND);
|
||
|
self.data.clear();
|
||
|
self.count = 0;
|
||
|
}
|
||
|
|
||
|
pub fn add_bytes(&mut self, data: &Vec<u8>) {
|
||
|
self.data.extend(data);
|
||
|
self.count += (data.len() / (28 + 4)) * 6;
|
||
|
}
|
||
|
|
||
|
pub fn character_texture(&mut self, c: char) -> render::Texture {
|
||
|
let raw = c as u32;
|
||
|
let page = raw >> 8;
|
||
|
// Lazy load fonts to size memory
|
||
|
if self.font_pages[page as usize].is_none() {
|
||
|
let name = if page == 0 {
|
||
|
"font/ascii".to_owned()
|
||
|
} else {
|
||
|
format!("font/unicode_page_{:02X}", page)
|
||
|
};
|
||
|
let textures = self.textures.clone();
|
||
|
self.font_pages[page as usize] = Some(render::Renderer::get_texture(&textures, &name));
|
||
|
}
|
||
|
let p = self.font_pages[page as usize].clone().unwrap();
|
||
|
|
||
|
let raw = if page == 0 {
|
||
|
self.char_map[&c] as u32
|
||
|
} else {
|
||
|
raw
|
||
|
};
|
||
|
let ch = raw & 0xFF;
|
||
|
let cx = ch & 0xF;
|
||
|
let cy = ch >> 4;
|
||
|
let info = self.font_character_info[raw as usize];
|
||
|
if page == 0 {
|
||
|
let sw = (self.page_width / 16.0) as u32;
|
||
|
let sh = (self.page_height / 16.0) as u32;
|
||
|
return p.relative(
|
||
|
(cx * sw + info.0 as u32) as f32 / (self.page_width as f32),
|
||
|
(cy * sh) as f32 / (self.page_height as f32),
|
||
|
(info.1 - info.0) as f32 / (self.page_width as f32),
|
||
|
(sh as f32) / (self.page_height as f32)
|
||
|
)
|
||
|
}
|
||
|
return p.relative(
|
||
|
(cx * 16 + info.0 as u32) as f32 / 256.0,
|
||
|
(cy * 16) as f32 / 256.0,
|
||
|
(info.1 - info.0) as f32 / 256.0,
|
||
|
16.0 / 256.0
|
||
|
)
|
||
|
}
|
||
|
|
||
|
pub fn size_of_string(&self, val: &str) -> f64 {
|
||
|
let mut size = 0.0;
|
||
|
for c in val.chars() {
|
||
|
size += self.size_of_char(c) + 2.0;
|
||
|
}
|
||
|
size - 2.0
|
||
|
}
|
||
|
|
||
|
pub fn size_of_char(&self, c: char) -> f64 {
|
||
|
if c == ' ' {
|
||
|
return 4.0;
|
||
|
}
|
||
|
let r = c as u32;
|
||
|
if r >> 8 == 0 {
|
||
|
let r = self.char_map[&c] as u32;
|
||
|
let info = self.font_character_info[r as usize];
|
||
|
let sw = self.page_width / 16.0;
|
||
|
return (((info.1 - info.0) as f64) / sw) * 16.0;
|
||
|
}
|
||
|
let info = self.font_character_info[c as usize];
|
||
|
return (info.1 - info.0) as f64;
|
||
|
}
|
||
|
|
||
|
fn load_font(&mut self) {
|
||
|
let res = self.resources.read().unwrap();
|
||
|
if let Some(mut info) = res.open("minecraft", "font/glyph_sizes.bin") {
|
||
|
let mut data = Vec::with_capacity(0x10000);
|
||
|
info.read_to_end(&mut data).unwrap();
|
||
|
for i in 0 .. self.font_character_info.len() {
|
||
|
// Top nibble - start position
|
||
|
// Bottom nibble - end position
|
||
|
self.font_character_info[i].0 = (data[i] >> 4) as i32;
|
||
|
self.font_character_info[i].1 = (data[i] & 0xF) as i32 + 1;
|
||
|
}
|
||
|
}
|
||
|
if let Some(mut val) = res.open("minecraft", "textures/font/ascii.png") {
|
||
|
let mut data = Vec::new();
|
||
|
val.read_to_end(&mut data).unwrap();
|
||
|
if let Ok(img) = image::load_from_memory(&data) {
|
||
|
let (width, height) = img.dimensions();
|
||
|
self.page_width = width as f64;
|
||
|
self.page_height = height as f64;
|
||
|
let sw = width / 16;
|
||
|
let sh = height / 16;
|
||
|
for i in 0 .. 256 {
|
||
|
let cx = (i & 0xF) * sw;
|
||
|
let cy = (i >> 4) * sh;
|
||
|
let mut start = true;
|
||
|
'x_loop: for x in 0 .. sw {
|
||
|
for y in 0 .. sh {
|
||
|
let a = img.get_pixel(cx+x, cy+y).data[3];
|
||
|
if start && a != 0 {
|
||
|
self.font_character_info[i as usize].0 = x as i32;
|
||
|
start = false;
|
||
|
continue 'x_loop;
|
||
|
} else if !start && a != 0 {
|
||
|
continue 'x_loop;
|
||
|
}
|
||
|
}
|
||
|
if !start {
|
||
|
self.font_character_info[i as usize].1 = x as i32;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn new_text(&mut self, val: &str, x: f64, y: f64, r: u8, g: u8, b: u8) -> UIText {
|
||
|
self.new_text_scaled(val, x, y, 1.0, 1.0, r, g, b)
|
||
|
}
|
||
|
|
||
|
pub fn new_text_scaled(&mut self, val: &str, x: f64, y: f64, sx: f64, sy: f64, r: u8, g: u8, b: u8) -> UIText {
|
||
|
self.create_text(val, x, y, sx, sy, 0.0, r, g, b)
|
||
|
}
|
||
|
|
||
|
pub fn new_text_rotated(&mut self, val: &str, x: f64, y: f64, sx: f64, sy: f64, rotation: f64, r: u8, g: u8, b: u8) -> UIText {
|
||
|
self.create_text(val, x, y, sx, sy, rotation, r, g, b)
|
||
|
}
|
||
|
|
||
|
fn create_text(&mut self, val: &str, x: f64, y: f64, sx: f64, sy: f64, rotation: f64, r: u8, g: u8, b: u8) -> UIText {
|
||
|
let mut elements = Vec::new();
|
||
|
let mut offset = 0.0;
|
||
|
for ch in val.chars() {
|
||
|
if ch == ' ' {
|
||
|
offset += 6.0;
|
||
|
continue;
|
||
|
}
|
||
|
let texture = self.character_texture(ch);
|
||
|
let mut raw = ch as u32;
|
||
|
let page = raw >> 8;
|
||
|
|
||
|
if page == 0 {
|
||
|
raw = self.char_map[&ch] as u32;
|
||
|
}
|
||
|
let info = self.font_character_info[raw as usize];
|
||
|
let w = if page == 0 {
|
||
|
let sw = self.page_width / 16.0;
|
||
|
((info.1 - info.0) as f64 / sw) * 16.0
|
||
|
} else {
|
||
|
(info.1 - info.0) as f64
|
||
|
};
|
||
|
|
||
|
let mut dsx = offset + 2.0;
|
||
|
let mut dsy = 2.0;
|
||
|
let mut dx = offset;
|
||
|
let mut dy = 0.0;
|
||
|
if rotation != 0.0 {
|
||
|
let c = rotation.cos();
|
||
|
let s = rotation.sin();
|
||
|
let tmpx = dsx - (w * 0.5);
|
||
|
let tmpy = dsy - (16.0 * 0.5);
|
||
|
dsx = (w * 0.5) + (tmpx*c - tmpy*s);
|
||
|
dsy = (16.0 * 0.5) + (tmpy*c + tmpx*s);
|
||
|
let tmpx = dx - (w * 0.5);
|
||
|
let tmpy = dy - (16.0 * 0.5);
|
||
|
dx = (w * 0.5) + (tmpx*c - tmpy*s);
|
||
|
dy = (16.0 * 0.5) + (tmpy*c + tmpx*s);
|
||
|
}
|
||
|
|
||
|
let mut shadow = UIElement::new(&texture, x+dsx*sx, y+dsy*sy, w*sx, 16.0*sy, 0.0, 0.0, 1.0, 1.0);
|
||
|
shadow.r = ((r as f64) * 0.25) as u8;
|
||
|
shadow.g = ((g as f64) * 0.25) as u8;
|
||
|
shadow.b = ((b as f64) * 0.25) as u8;
|
||
|
shadow.rotation = rotation;
|
||
|
elements.push(shadow);
|
||
|
|
||
|
let mut text = UIElement::new(&texture, x+dx*sx, y+dy*sy, w*sx, 16.0*sy, 0.0, 0.0, 1.0, 1.0);
|
||
|
text.r = r;
|
||
|
text.g = g;
|
||
|
text.b = b;
|
||
|
text.rotation = rotation;
|
||
|
elements.push(text);
|
||
|
offset += w + 2.0;
|
||
|
}
|
||
|
UIText {
|
||
|
elements: elements,
|
||
|
width: (offset - 2.0) * sx,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct UIText {
|
||
|
pub elements: Vec<UIElement>,
|
||
|
pub width: f64,
|
||
|
}
|
||
|
|
||
|
impl UIText {
|
||
|
pub fn bytes(&self, width: f64, height: f64) -> Vec<u8> {
|
||
|
let mut buf = Vec::with_capacity(28*4*self.elements.len());
|
||
|
for e in &self.elements {
|
||
|
buf.extend(e.bytes(width, height));
|
||
|
}
|
||
|
buf
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct UIElement {
|
||
|
pub x: f64,
|
||
|
pub y: f64,
|
||
|
pub w: f64,
|
||
|
pub h: f64,
|
||
|
pub layer: isize,
|
||
|
pub t_x: u16,
|
||
|
pub t_y: u16,
|
||
|
pub t_w: u16,
|
||
|
pub t_h: u16,
|
||
|
pub t_offsetx: i16,
|
||
|
pub t_offsety: i16,
|
||
|
pub t_atlas: i16,
|
||
|
pub t_sizew: i16,
|
||
|
pub t_sizeh: i16,
|
||
|
pub r: u8,
|
||
|
pub g: u8,
|
||
|
pub b: u8,
|
||
|
pub a: u8,
|
||
|
pub rotation: f64,
|
||
|
}
|
||
|
|
||
|
impl UIElement {
|
||
|
pub fn new(tex: &render::Texture, x: f64, y: f64, width: f64, height: f64, tx: f64, ty: f64, tw: f64, th: f64) -> UIElement {
|
||
|
let twidth = tex.get_width();
|
||
|
let theight = tex.get_height();
|
||
|
UIElement {
|
||
|
x: x / UI_WIDTH,
|
||
|
y: y / UI_HEIGHT,
|
||
|
w: width / UI_WIDTH,
|
||
|
h: height / UI_HEIGHT,
|
||
|
layer: 0,
|
||
|
t_x: tex.get_x() as u16,
|
||
|
t_y: tex.get_y() as u16,
|
||
|
t_w: twidth as u16,
|
||
|
t_h: theight as u16,
|
||
|
t_atlas: tex.atlas as i16,
|
||
|
t_offsetx: (tx * (twidth as f64) * 16.0) as i16,
|
||
|
t_offsety: (ty * (theight as f64) * 16.0) as i16,
|
||
|
t_sizew: (tw * (twidth as f64) * 16.0) as i16,
|
||
|
t_sizeh: (th * (theight as f64) * 16.0) as i16,
|
||
|
r: 255,
|
||
|
g: 255,
|
||
|
b: 255,
|
||
|
a: 255,
|
||
|
rotation: 0.0,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn bytes(&self, width: f64, height: f64) -> Vec<u8> {
|
||
|
let mut buf = Vec::with_capacity(28*4);
|
||
|
self.append_vertex(&mut buf, self.x, self.y, self.t_offsetx, self.t_offsety, width, height);
|
||
|
self.append_vertex(&mut buf, self.x + self.w, self.y, self.t_offsetx + self.t_sizew, self.t_offsety, width, height);
|
||
|
self.append_vertex(&mut buf, self.x, self.y + self.h, self.t_offsetx, self.t_offsety + self.t_sizeh, width, height);
|
||
|
self.append_vertex(&mut buf, self.x + self.w, self.y + self.h, self.t_offsetx + self.t_sizew, self.t_offsety + self.t_sizeh, width, height);
|
||
|
buf
|
||
|
}
|
||
|
|
||
|
#[allow(unused_must_use)]
|
||
|
pub fn append_vertex(&self, buf: &mut Vec<u8>, x: f64, y: f64, tx: i16, ty: i16, width: f64, height: f64) {
|
||
|
let mut dx = x as f64;
|
||
|
let mut dy = y as f64;
|
||
|
if self.rotation != 0.0 {
|
||
|
let c = self.rotation.cos();
|
||
|
let s = self.rotation.sin();
|
||
|
let tmpx = dx - self.x - (self.w / 2.0);
|
||
|
let tmpy = dy - self.y - (self.h / 2.0);
|
||
|
dx = (self.w / 2.0) + (tmpx * c - tmpy * s) + self.x;
|
||
|
dy = (self.h / 2.0) + (tmpy * c + tmpx * s) + self.y;
|
||
|
}
|
||
|
|
||
|
buf.write_i16::<NativeEndian>((dx*width+0.5).floor() as i16);
|
||
|
buf.write_i16::<NativeEndian>((dy*height+0.5).floor() as i16);
|
||
|
buf.write_i16::<NativeEndian>((self.layer * 256) as i16);
|
||
|
buf.write_i16::<NativeEndian>(0);
|
||
|
buf.write_u16::<NativeEndian>(self.t_x);
|
||
|
buf.write_u16::<NativeEndian>(self.t_y);
|
||
|
buf.write_u16::<NativeEndian>(self.t_w);
|
||
|
buf.write_u16::<NativeEndian>(self.t_h);
|
||
|
buf.write_i16::<NativeEndian>(tx);
|
||
|
buf.write_i16::<NativeEndian>(ty);
|
||
|
buf.write_i16::<NativeEndian>(self.t_atlas);
|
||
|
buf.write_i16::<NativeEndian>(0);
|
||
|
buf.write_u8(self.r);
|
||
|
buf.write_u8(self.g);
|
||
|
buf.write_u8(self.b);
|
||
|
buf.write_u8(self.a);
|
||
|
}
|
||
|
}
|