Kinda functional server list

This commit is contained in:
Thinkofdeath 2015-09-25 14:00:49 +01:00
parent 93edfa3828
commit 3bcfc6aa4c
9 changed files with 792 additions and 191 deletions

View File

@ -5,7 +5,6 @@ authors = [ "Thinkofdeath <thinkofdeath@spigotmc.org>" ]
build = "build.rs" build = "build.rs"
[dependencies] [dependencies]
rustc-serialize = "0.3"
glfw = "0.1.0" glfw = "0.1.0"
byteorder = "0.3.13" byteorder = "0.3.13"
hyper = "0.6.13" hyper = "0.6.13"
@ -16,6 +15,7 @@ zip = "0.1.12"
image = "0.3.12" image = "0.3.12"
time = "0.1.32" time = "0.1.32"
rand = "0.3.11" rand = "0.3.11"
rustc-serialize = "0.3"
[dependencies.steven_gl] [dependencies.steven_gl]
path = "./gl" path = "./gl"

View File

@ -14,6 +14,7 @@
use serde_json; use serde_json;
use std::fmt; use std::fmt;
use std::mem;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Component { pub enum Component {
@ -272,3 +273,85 @@ fn test_color_from() {
_ => panic!("Wrong type"), _ => panic!("Wrong type"),
} }
} }
const LEGACY_CHAR: char = '§';
pub fn convert_legacy(c: &mut Component) {
match c {
&mut Component::Text(ref mut txt) => {
if let Some(ref mut extra) = txt.modifier.extra.as_mut() {
for e in extra.iter_mut() {
convert_legacy(e);
}
}
if txt.text.contains(LEGACY_CHAR) {
let mut parts = Vec::new();
let mut last = 0;
let mut current = TextComponent::new("");
{
let mut iter = txt.text.char_indices();
while let Some((i, c)) = iter.next() {
if c == LEGACY_CHAR {
let next = match iter.next() {
Some(val) => val,
None => break,
};
let color_char = next.1.to_lowercase().next().unwrap();
current.text = txt.text[last .. i].to_owned();
last = next.0 + 1;
let mut modifier = if (color_char >= 'a' && color_char <= 'f') || (color_char >= '0' && color_char <= '9') {
Default::default()
} else {
current.modifier.clone()
};
let new = TextComponent::new("");
parts.push(Component::Text(mem::replace(&mut current, new)));
match color_char {
'0' => modifier.color = Some(Color::Black),
'1' => modifier.color = Some(Color::DarkBlue),
'2' => modifier.color = Some(Color::DarkGreen),
'3' => modifier.color = Some(Color::DarkAqua),
'4' => modifier.color = Some(Color::DarkRed),
'5' => modifier.color = Some(Color::DarkPurple),
'6' => modifier.color = Some(Color::Gold),
'7' => modifier.color = Some(Color::Gray),
'8' => modifier.color = Some(Color::DarkGray),
'9' => modifier.color = Some(Color::Blue),
'a' => modifier.color = Some(Color::Green),
'b' => modifier.color = Some(Color::Aqua),
'c' => modifier.color = Some(Color::Red),
'd' => modifier.color = Some(Color::LightPurple),
'e' => modifier.color = Some(Color::Yellow),
'f' => modifier.color = Some(Color::White),
'k' => modifier.obfuscated = Some(true),
'l' => modifier.bold = Some(true),
'm' => modifier.strikethrough = Some(true),
'n' => modifier.underlined = Some(true),
'o' => modifier.italic = Some(true),
'r' => {},
_ => unimplemented!(),
}
current.modifier = modifier;
}
}
}
if last < txt.text.len() {
current.text = txt.text[last..].to_owned();
parts.push(Component::Text(current));
}
let old = mem::replace(&mut txt.modifier.extra, Some(parts));
if let Some(old_extra) = old {
if let Some(ref mut extra) = txt.modifier.extra.as_mut() {
extra.extend(old_extra);
}
}
txt.text = "".to_owned();
}
},
}
}

View File

@ -32,6 +32,7 @@ extern crate steven_openssl as openssl;
extern crate hyper; extern crate hyper;
extern crate flate2; extern crate flate2;
extern crate rand; extern crate rand;
extern crate rustc_serialize;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use glfw::{Action, Context, Key}; use glfw::{Action, Context, Key};
@ -55,6 +56,8 @@ fn main() {
window.set_key_polling(true); window.set_key_polling(true);
window.set_scroll_polling(true); window.set_scroll_polling(true);
window.set_mouse_button_polling(true);
window.set_cursor_pos_polling(true);
window.make_current(); window.make_current();
glfw.set_swap_interval(1); glfw.set_swap_interval(1);
@ -83,12 +86,18 @@ fn main() {
window.swap_buffers(); window.swap_buffers();
glfw.poll_events(); glfw.poll_events();
for (_, event) in glfw::flush_messages(&events) { for (_, event) in glfw::flush_messages(&events) {
handle_window_event(&mut window, &mut screen_sys, event); handle_window_event(&mut window, &mut renderer, &mut screen_sys, &mut ui_container, event);
} }
} }
} }
fn handle_window_event(window: &mut glfw::Window, screen_sys: &mut screen::ScreenSystem, event: glfw::WindowEvent) { fn handle_window_event(
window: &mut glfw::Window,
renderer: &mut render::Renderer,
screen_sys: &mut screen::ScreenSystem,
ui_container: &mut ui::Container,
event: glfw::WindowEvent
) {
match event { match event {
glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => { glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
window.set_should_close(true) window.set_should_close(true)
@ -96,6 +105,17 @@ fn handle_window_event(window: &mut glfw::Window, screen_sys: &mut screen::Scree
glfw::WindowEvent::Scroll(x, y) => { glfw::WindowEvent::Scroll(x, y) => {
screen_sys.on_scroll(x, y); screen_sys.on_scroll(x, y);
}, },
glfw::WindowEvent::MouseButton(glfw::MouseButton::Button1, Action::Press, _) => {
let (width, height) = window.get_size();
let (xpos, ypos) = window.get_cursor_pos();
let (fw, fh) = window.get_framebuffer_size();
ui_container.click_at(renderer, xpos*((fw as f64)/(width as f64)), ypos*((fh as f64)/(height as f64)), fw as f64, fh as f64)
},
glfw::WindowEvent::CursorPos(xpos, ypos) => {
let (width, height) = window.get_size();
let (fw, fh) = window.get_framebuffer_size();
ui_container.hover_at(renderer, xpos*((fw as f64)/(width as f64)), ypos*((fh as f64)/(height as f64)), fw as f64, fh as f64)
}
_ => {} _ => {}
} }
} }

View File

@ -30,6 +30,9 @@ use std::convert;
use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt};
use flate2::read::{ZlibDecoder, ZlibEncoder}; use flate2::read::{ZlibDecoder, ZlibEncoder};
use flate2; use flate2;
use time;
pub const SUPPORTED_PROTOCOL: i32 = 73;
/// Helper macro for defining packets /// Helper macro for defining packets
#[macro_export] #[macro_export]
@ -130,7 +133,7 @@ macro_rules! state_packets {
pub mod packet; pub mod packet;
pub trait Serializable { pub trait Serializable: Sized {
fn read_from(buf: &mut io::Read) -> Result<Self, io::Error>; fn read_from(buf: &mut io::Read) -> Result<Self, io::Error>;
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>; fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>;
} }
@ -651,11 +654,14 @@ pub struct Conn {
impl Conn { impl Conn {
pub fn new(target: &str) -> Result<Conn, Error>{ pub fn new(target: &str) -> Result<Conn, Error>{
// TODO SRV record support // TODO SRV record support
let stream = match TcpStream::connect(target) { let mut parts = target.split(":").collect::<Vec<&str>>();
Ok(val) => val, let address = if parts.len() == 1 {
Err(err) => return Result::Err(Error::IOError(err)) parts.push("25565");
format!("{}:25565", parts[0])
} else {
format!("{}:{}", parts[0], parts[1])
}; };
let parts = target.split(":").collect::<Vec<&str>>(); let stream = try!(TcpStream::connect(&*address));
Result::Ok(Conn { Result::Ok(Conn {
stream: stream, stream: stream,
host: parts[0].to_owned(), host: parts[0].to_owned(),
@ -748,6 +754,91 @@ impl Conn {
self.compression_read = Some(ZlibDecoder::new(io::Cursor::new(Vec::new()))); self.compression_read = Some(ZlibDecoder::new(io::Cursor::new(Vec::new())));
} }
} }
pub fn do_status(mut self) -> Result<(Status, time::Duration), Error>{
use serde_json::Value;
use self::packet::status::serverbound::*;
use self::packet::handshake::serverbound::*;
use self::packet::Packet;
let host = self.host.clone();
let port = self.port;
try!(self.write_packet(Handshake{
protocol_version: VarInt(SUPPORTED_PROTOCOL),
host: host,
port: port,
next: VarInt(1),
}));
self.state = State::Status;
try!(self.write_packet(StatusRequest{empty: ()}));
let status = if let Packet::StatusResponse(res) = try!(self.read_packet()) {
res.status
} else {
return Err(Error::Err("Wrong packet".to_owned()));
};
let start = time::now();
try!(self.write_packet(StatusPing{ping: 42}));
if let Packet::StatusPong(_) = try!(self.read_packet()) {
} else {
return Err(Error::Err("Wrong packet".to_owned()));
};
let ping = time::now() - start;
let val: Value = match serde_json::from_str(&status) {
Ok(val) => val,
Err(_) => return Err(Error::Err("Json parse error".to_owned())),
};
let invalid_status = || Error::Err("Invalid status".to_owned());
let version = try!(val.find("version").ok_or(invalid_status()));
let players = try!(val.find("players").ok_or(invalid_status()));
Ok((Status {
version: StatusVersion {
name: try!(version.find("name").and_then(Value::as_string).ok_or(invalid_status())).to_owned(),
protocol: try!(version.find("protocol").and_then(Value::as_i64).ok_or(invalid_status())) as i32,
},
players: StatusPlayers {
max: try!(players.find("max").and_then(Value::as_i64).ok_or(invalid_status())) as i32,
online: try!(players.find("online").and_then(Value::as_i64).ok_or(invalid_status())) as i32,
sample: Vec::new(), // TODO
},
description: format::Component::from_value(try!(val.find("description").ok_or(invalid_status()))),
favicon: val.find("favicon").and_then(Value::as_string).map(|v| v.to_owned()),
}, ping))
}
}
#[derive(Debug)]
pub struct Status {
pub version: StatusVersion,
pub players: StatusPlayers,
pub description: format::Component,
pub favicon: Option<String>,
}
#[derive(Debug)]
pub struct StatusVersion {
pub name: String,
pub protocol: i32,
}
#[derive(Debug)]
pub struct StatusPlayers {
pub max: i32,
pub online: i32,
pub sample: Vec<StatusPlayer>,
}
#[derive(Debug)]
pub struct StatusPlayer {
name: String,
id: String,
} }
impl Read for Conn { impl Read for Conn {
@ -805,6 +896,7 @@ pub trait PacketType {
fn write(self, buf: &mut io::Write) -> Result<(), io::Error>; fn write(self, buf: &mut io::Write) -> Result<(), io::Error>;
} }
// REMOVE ME
// #[test] // #[test]
pub fn test() { pub fn test() {
let mut c = Conn::new("localhost:25565").unwrap(); let mut c = Conn::new("localhost:25565").unwrap();

View File

@ -193,6 +193,9 @@ pub struct TextureManager {
animated_textures: Vec<AnimatedTexture>, animated_textures: Vec<AnimatedTexture>,
pending_uploads: Vec<(i32, atlas::Rect, Vec<u8>)>, pending_uploads: Vec<(i32, atlas::Rect, Vec<u8>)>,
dynamic_textures: HashMap<String, (i32, atlas::Rect)>,
free_dynamics: Vec<(i32, atlas::Rect)>,
} }
impl TextureManager { impl TextureManager {
@ -204,6 +207,9 @@ impl TextureManager {
atlases: Vec::new(), atlases: Vec::new(),
animated_textures: Vec::new(), animated_textures: Vec::new(),
pending_uploads: Vec::new(), pending_uploads: Vec::new(),
dynamic_textures: HashMap::new(),
free_dynamics: Vec::new(),
}; };
tm.add_defaults(); tm.add_defaults();
tm tm
@ -222,6 +228,8 @@ impl TextureManager {
} }
fn update_textures(&mut self, version: usize) { fn update_textures(&mut self, version: usize) {
self.dynamic_textures.clear();
self.free_dynamics.clear();
self.pending_uploads.clear(); self.pending_uploads.clear();
self.atlases.clear(); self.atlases.clear();
self.animated_textures.clear(); self.animated_textures.clear();
@ -391,6 +399,74 @@ impl TextureManager {
self.textures.insert(full_name.to_owned(), t.clone()); self.textures.insert(full_name.to_owned(), t.clone());
t t
} }
pub fn put_dynamic(&mut self, plugin: &str, name: &str, img: image::DynamicImage) -> Texture {
let (width, height) = img.dimensions();
let (width, height) = (width as usize, height as usize);
let mut rect = None;
let mut rect_pos = 0;
for (i, r) in self.free_dynamics.iter().enumerate() {
let (atlas, r) = *r;
if r.width == width && r.height == height {
rect_pos = i;
rect = Some((atlas, r));
break;
} else if r.width >= width && r.height >= height {
rect_pos = i;
rect = Some((atlas, r));
}
}
let data = img.to_rgba().into_vec();
let mut new = false;
let (atlas, rect) = if let Some(r) = rect {
self.free_dynamics.remove(rect_pos);
r
} else {
new = true;
self.find_free(width as usize, height as usize)
};
let mut full_name = String::new();
if plugin != "minecraft" {
full_name.push_str(plugin);
full_name.push_str(":");
}
full_name.push_str(name);
self.dynamic_textures.insert(full_name.clone(), (atlas, rect));
if new {
self.put_texture(plugin, name, width as u32, height as u32, data)
} else {
let t = Texture {
name: full_name.clone(),
version: self.version,
atlas: atlas,
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
rel_x: 0.0,
rel_y: 0.0,
rel_width: 1.0,
rel_height: 1.0,
is_rel: false,
};
self.textures.insert(full_name.to_owned(), t.clone());
t
}
}
pub fn remove_dynamic(&mut self, plugin: &str, name: &str) {
let mut full_name = String::new();
if plugin != "minecraft" {
full_name.push_str(plugin);
full_name.push_str(":");
}
full_name.push_str(name);
let desc = self.dynamic_textures.remove(&full_name).unwrap();
self.free_dynamics.push(desc);
}
} }
struct AnimatedTexture { struct AnimatedTexture {

View File

@ -2,6 +2,8 @@
mod server_list; mod server_list;
pub use self::server_list::*; pub use self::server_list::*;
use std::rc::Rc;
use render; use render;
use ui; use ui;
@ -141,4 +143,50 @@ pub fn new_button_text(renderer: &mut render::Renderer, val: &str, x: f64, y: f6
text.set_v_attach(ui::VAttach::Middle); text.set_v_attach(ui::VAttach::Middle);
text.set_h_attach(ui::HAttach::Center); text.set_h_attach(ui::HAttach::Center);
(batch, text) (batch, text)
}
pub fn button_action(ui_container: &mut ui::Container,
btn: ui::ElementRef<ui::Batch>, txt: Option<ui::ElementRef<ui::Text>>,
click: Option<Rc<Fn(&mut render::Renderer, &mut ui::Container)>>
) {
let batch = ui_container.get_mut(&btn);
batch.add_hover_func(Rc::new(move |over, renderer, ui_container| {
let texture = render::Renderer::get_texture(renderer.get_textures_ref(), "gui/widgets").relative(
0.0, (if over { 86.0 } else { 66.0 }) / 256.0, 200.0 / 256.0, 20.0 / 256.0
);
{
let batch = ui_container.get_mut(&btn);
for i in 0 .. batch.len() {
let img = batch.get_mut_at::<ui::Image>(i);
match i {
_i @ 0 ...3 => img.set_texture(texture.clone()),
4 => img.set_texture(texture.clone().relative(
2.0 / 200.0, 0.0, 196.0 / 200.0, 2.0 / 20.0
)),
5 => img.set_texture(texture.clone().relative(
2.0 / 200.0, 17.0 / 20.0, 196.0 / 200.0, 3.0 / 20.0
)),
6 => img.set_texture(texture.clone().relative(
0.0, 2.0 / 20.0, 2.0 / 200.0, 15.0 / 20.0
)),
7 => img.set_texture(texture.clone().relative(
198.0 / 200.0, 2.0 / 20.0, 2.0 / 200.0, 15.0 / 20.0
)),
8 => img.set_texture(texture.clone().relative(
2.0 / 200.0, 2.0 / 20.0, 196.0 / 200.0, 15.0 / 20.0
)),
_ => unreachable!(),
}
}
}
let txt = txt.clone();
if let Some(txt) = txt {
let text = ui_container.get_mut(&txt);
text.set_b(if over { 160 } else { 255 });
}
}));
if let Some(click) = click {
batch.add_click_func(click);
}
} }

View File

@ -1,14 +1,25 @@
use std::fs; use std::fs;
use std::thread;
use std::sync::mpsc;
use std::rc::Rc;
use ui; use ui;
use render; use render;
use format; use format;
use format::{Component, TextComponent};
use protocol;
use serde_json; use serde_json;
use std::cmp::max; use time;
use image;
use rustc_serialize::base64::FromBase64;
use rand;
use rand::{Rng};
pub struct ServerList { pub struct ServerList {
elements: Option<UIElements>, elements: Option<UIElements>,
disconnect_reason: Option<format::Component>, disconnect_reason: Option<Component>,
} }
struct UIElements { struct UIElements {
@ -22,6 +33,28 @@ struct Server {
back: ui::ElementRef<ui::Image>, back: ui::ElementRef<ui::Image>,
offset: f64, offset: f64,
y: f64, y: f64,
motd: ui::ElementRef<ui::Formatted>,
ping: ui::ElementRef<ui::Image>,
players: ui::ElementRef<ui::Text>,
version: ui::ElementRef<ui::Formatted>,
icon: ui::ElementRef<ui::Image>,
icon_texture: Option<String>,
done_ping: bool,
recv: mpsc::Receiver<PingInfo>,
}
struct PingInfo {
motd: format::Component,
ping: time::Duration,
exists: bool,
online: i32,
max: i32,
protocol_version: i32,
protocol_name: String,
favicon: Option<image::DynamicImage>,
} }
impl Server { impl Server {
@ -35,7 +68,7 @@ impl Server {
} }
impl ServerList { impl ServerList {
pub fn new(disconnect_reason: Option<format::Component>) -> ServerList { pub fn new(disconnect_reason: Option<Component>) -> ServerList {
ServerList { ServerList {
elements: None, elements: None,
disconnect_reason: disconnect_reason, disconnect_reason: disconnect_reason,
@ -44,14 +77,20 @@ impl ServerList {
fn reload_server_list(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) { fn reload_server_list(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) {
let elements = self.elements.as_mut().unwrap(); let elements = self.elements.as_mut().unwrap();
for server in &mut elements.servers { {
server.collection.remove_all(ui_container); let mut tex = renderer.get_textures_ref().write().unwrap();
for server in &mut elements.servers {
server.collection.remove_all(ui_container);
if let Some(ref icon) = server.icon_texture {
tex.remove_dynamic("steven_icon", &icon);
}
}
} }
elements.servers.clear(); elements.servers.clear();
let file = match fs::File::open("servers.json") { let file = match fs::File::open("servers.json") {
Ok(val) => val, Ok(val) => val,
Err(e) => return, Err(_) => return,
}; };
let servers_info: serde_json::Value = serde_json::from_reader(file).unwrap(); let servers_info: serde_json::Value = serde_json::from_reader(file).unwrap();
let servers = servers_info.find("servers").unwrap().as_array().unwrap(); let servers = servers_info.find("servers").unwrap().as_array().unwrap();
@ -62,7 +101,7 @@ impl ServerList {
for svr in servers { for svr in servers {
let name = svr.find("name").unwrap().as_string().unwrap(); let name = svr.find("name").unwrap().as_string().unwrap();
let address = svr.find("address").unwrap().as_string().unwrap(); let address = svr.find("address").unwrap().as_string().unwrap().to_owned();
let solid = render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid"); let solid = render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid");
@ -71,14 +110,38 @@ impl ServerList {
back.set_v_attach(ui::VAttach::Middle); back.set_v_attach(ui::VAttach::Middle);
back.set_h_attach(ui::HAttach::Center); back.set_h_attach(ui::HAttach::Center);
let (send, recv) = mpsc::channel::<PingInfo>();
let mut server = Server { let mut server = Server {
collection: ui::Collection::new(), collection: ui::Collection::new(),
back: ui_container.add(back), back: ui_container.add(back),
offset: offset, offset: offset,
y: 0.0, y: 0.0,
done_ping: false,
recv: recv,
motd: Default::default(),
ping: Default::default(),
players: Default::default(),
version: Default::default(),
icon: Default::default(),
icon_texture: None,
}; };
server.collection.add(server.back.clone()); server.collection.add(server.back.clone());
server.update_position(); server.update_position();
{
let back = ui_container.get_mut(&server.back);
let back_ref = server.back.clone();
let address = address.clone();
back.add_hover_func(Rc::new(move |over, renderer, ui_container| {
let back = ui_container.get_mut(&back_ref);
back.set_a(if over { 200 } else { 100 });
}));
back.add_click_func(Rc::new(move |renderer, ui_container| {
println!("Connecting to {}", address);
}));
}
let mut text = ui::Text::new(renderer, &name, 100.0, 5.0, 255, 255, 255); let mut text = ui::Text::new(renderer, &name, 100.0, 5.0, 255, 255, 255);
text.set_parent(&server.back); text.set_parent(&server.back);
@ -86,20 +149,94 @@ impl ServerList {
let mut icon = ui::Image::new(default_icon.clone(), 5.0, 5.0, 90.0, 90.0, 0.0, 0.0, 1.0, 1.0, 255, 255, 255); let mut icon = ui::Image::new(default_icon.clone(), 5.0, 5.0, 90.0, 90.0, 0.0, 0.0, 1.0, 1.0, 255, 255, 255);
icon.set_parent(&server.back); icon.set_parent(&server.back);
server.collection.add(ui_container.add(icon)); server.icon = server.collection.add(ui_container.add(icon));
let mut ping = ui::Image::new(icons.clone(), 5.0, 5.0, 20.0, 16.0, 0.0, 56.0/256.0, 10.0/256.0, 8.0/256.0, 255, 255, 255); let mut ping = ui::Image::new(icons.clone(), 5.0, 5.0, 20.0, 16.0, 0.0, 56.0/256.0, 10.0/256.0, 8.0/256.0, 255, 255, 255);
ping.set_h_attach(ui::HAttach::Right); ping.set_h_attach(ui::HAttach::Right);
ping.set_parent(&server.back); ping.set_parent(&server.back);
server.collection.add(ui_container.add(ping)); server.ping = server.collection.add(ui_container.add(ping));
let mut players = ui::Text::new(renderer, "???", 30.0, 5.0, 255, 255, 255); let mut players = ui::Text::new(renderer, "???", 30.0, 5.0, 255, 255, 255);
players.set_h_attach(ui::HAttach::Right); players.set_h_attach(ui::HAttach::Right);
players.set_parent(&server.back); players.set_parent(&server.back);
server.collection.add(ui_container.add(players)); server.players = server.collection.add(ui_container.add(players));
let mut motd = ui::Formatted::with_width_limit(renderer, Component::Text(TextComponent::new("Connecting...")), 100.0, 23.0, 700.0 - (90.0 + 10.0 + 5.0));
motd.set_parent(&server.back);
server.motd = server.collection.add(ui_container.add(motd));
let mut version = ui::Formatted::with_width_limit(renderer, Component::Text(TextComponent::new("")), 100.0, 5.0, 700.0 - (90.0 + 10.0 + 5.0));
version.set_v_attach(ui::VAttach::Bottom);
version.set_parent(&server.back);
server.version = server.collection.add(ui_container.add(version));
let (mut del, mut txt) = super::new_button_text(renderer, "X", 0.0, 0.0, 25.0, 25.0);
del.set_v_attach(ui::VAttach::Bottom);
del.set_h_attach(ui::HAttach::Right);
del.set_parent(&server.back);
let re = ui_container.add(del);
txt.set_parent(&re);
let tre = ui_container.add(txt);
super::button_action(ui_container, re.clone(), Some(tre.clone()), None);
server.collection.add(re);
server.collection.add(tre);
let (mut edit, mut txt) = super::new_button_text(renderer, "E", 25.0, 0.0, 25.0, 25.0);
edit.set_v_attach(ui::VAttach::Bottom);
edit.set_h_attach(ui::HAttach::Right);
edit.set_parent(&server.back);
let re = ui_container.add(edit);
txt.set_parent(&re);
let tre = ui_container.add(txt);
super::button_action(ui_container, re.clone(), Some(tre.clone()), None);
server.collection.add(re);
server.collection.add(tre);
elements.servers.push(server); elements.servers.push(server);
offset += 1.0; offset += 1.0;
thread::spawn(move || {
match protocol::Conn::new(&address).and_then(|conn| conn.do_status()) {
Ok(res) => {
let mut desc = res.0.description;
format::convert_legacy(&mut desc);
let favicon = if let Some(icon) = res.0.favicon {
let data = icon["data:image/png;base64,".len()..].from_base64().unwrap();
Some(image::load_from_memory(
&data
).unwrap())
} else {
None
};
send.send(PingInfo {
motd: desc,
ping: res.1,
exists: true,
online: res.0.players.online,
max: res.0.players.max,
protocol_version: res.0.version.protocol,
protocol_name: res.0.version.name,
favicon: favicon,
}).unwrap();
},
Err(err) => {
let e = format!("{}", err);
let mut msg = TextComponent::new(&e);
msg.modifier.color = Some(format::Color::Red);
send.send(PingInfo {
motd: Component::Text(msg),
ping: time::Duration::seconds(99999),
exists: false,
online: 0,
max: 0,
protocol_version: 0,
protocol_name: "".to_owned(),
favicon: None,
}).unwrap();
},
}
});
} }
} }
} }
@ -117,16 +254,22 @@ impl super::Screen for ServerList {
refresh.set_h_attach(ui::HAttach::Center); refresh.set_h_attach(ui::HAttach::Center);
let re = ui_container.add(refresh); let re = ui_container.add(refresh);
txt.set_parent(&re); txt.set_parent(&re);
let tre = ui_container.add(txt);
super::button_action(ui_container, re.clone(), Some(tre.clone()), None);
elements.add(re); elements.add(re);
elements.add(ui_container.add(txt)); elements.add(tre);
let (mut add, mut txt) = super::new_button_text(renderer, "Add", 200.0, -50.0-15.0, 100.0, 30.0); let (mut add, mut txt) = super::new_button_text(renderer, "Add", 200.0, -50.0-15.0, 100.0, 30.0);
add.set_v_attach(ui::VAttach::Middle); add.set_v_attach(ui::VAttach::Middle);
add.set_h_attach(ui::HAttach::Center); add.set_h_attach(ui::HAttach::Center);
let re = ui_container.add(add); let re = ui_container.add(add);
txt.set_parent(&re); txt.set_parent(&re);
let tre = ui_container.add(txt);
super::button_action(ui_container, re.clone(), Some(tre.clone()), Some(Rc::new(|renderer, ui_container| {
})));
elements.add(re); elements.add(re);
elements.add(ui_container.add(txt)); elements.add(tre);
let mut options = super::new_button(renderer, 5.0, 25.0, 40.0, 40.0); let mut options = super::new_button(renderer, 5.0, 25.0, 40.0, 40.0);
options.set_v_attach(ui::VAttach::Bottom); options.set_v_attach(ui::VAttach::Bottom);
@ -136,6 +279,7 @@ impl super::Screen for ServerList {
cog.set_parent(&re); cog.set_parent(&re);
cog.set_v_attach(ui::VAttach::Middle); cog.set_v_attach(ui::VAttach::Middle);
cog.set_h_attach(ui::HAttach::Center); cog.set_h_attach(ui::HAttach::Center);
super::button_action(ui_container, re.clone(), None, None);
elements.add(re); elements.add(re);
elements.add(ui_container.add(cog)); elements.add(ui_container.add(cog));
@ -188,13 +332,78 @@ impl super::Screen for ServerList {
elements.logo.tick(renderer, ui_container); elements.logo.tick(renderer, ui_container);
for s in &mut elements.servers { for s in &mut elements.servers {
let back = ui_container.get_mut(&s.back); {
let dy = s.y - back.get_y(); let back = ui_container.get_mut(&s.back);
if dy*dy > 1.0 { let dy = s.y - back.get_y();
let y = back.get_y(); if dy*dy > 1.0 {
back.set_y(y + delta * dy * 0.1); let y = back.get_y();
} else { back.set_y(y + delta * dy * 0.1);
back.set_y(s.y); } else {
back.set_y(s.y);
}
}
if !s.done_ping {
match s.recv.try_recv() {
Ok(res) => {
s.done_ping = true;
{
let motd = ui_container.get_mut(&s.motd);
motd.set_component(renderer, res.motd);
}
{
let ping = ui_container.get_mut(&s.ping);
let y = match res.ping.num_milliseconds() {
_x @ 0 ... 75 => 16.0 / 256.0,
_x @ 76 ... 150 => 24.0 / 256.0,
_x @ 151 ... 225 => 32.0 / 256.0,
_x @ 226 ... 350 => 40.0 / 256.0,
_x @ 351 ... 999 => 48.0 / 256.0,
_ => 56.0 / 256.0,
};
ping.set_t_y(y);
}
if res.exists {
{
let players = ui_container.get_mut(&s.players);
let txt = if res.protocol_version == protocol::SUPPORTED_PROTOCOL {
players.set_g(255);
players.set_b(255);
format!("{}/{}", res.online, res.max)
} else {
players.set_g(85);
players.set_b(85);
format!("Out of date {}/{}", res.online, res.max)
};
players.set_text(renderer, &txt);
}
{
let version = ui_container.get_mut(&s.version);
let mut txt = TextComponent::new(&res.protocol_name);
txt.modifier.color = Some(format::Color::Yellow);
let mut msg = Component::Text(txt);
format::convert_legacy(&mut msg);
version.set_component(renderer, msg);
}
}
if let Some(favicon) = res.favicon {
let name: String = rand::thread_rng().gen_ascii_chars().take(30).collect();
let tex = renderer.get_textures_ref();
s.icon_texture = Some(name.clone());
let icon_tex = tex.write().unwrap().put_dynamic("steven_icon", &name, favicon);
let icon = ui_container.get_mut(&s.icon);
icon.set_texture(icon_tex);
}
},
Err(mpsc::TryRecvError::Disconnected) => {
s.done_ping = true;
let motd = ui_container.get_mut(&s.motd);
let mut txt = TextComponent::new("Channel dropped");
txt.modifier.color = Some(format::Color::Red);
motd.set_component(renderer, Component::Text(txt));
},
_ => {}
}
} }
} }
} }

View File

@ -1,80 +1,61 @@
pub struct Formatted { ui_element!(Formatted {
dirty: bool,
data: Vec<u8>,
parent: Option<ElementRefInner>,
should_draw: bool,
layer: isize,
val: format::Component, val: format::Component,
x: f64,
y: f64,
width: f64, width: f64,
height: f64, height: f64,
v_attach: VAttach,
h_attach: HAttach,
scale_x: f64, scale_x: f64,
scale_y: f64, scale_y: f64,
text: Vec<Element>, text: Vec<Element>,
max_width: f64, max_width: f64,
lines: usize, lines: usize
} });
impl Formatted { impl Formatted {
pub fn new(renderer: &mut render::Renderer, val: format::Component, x: f64, y: f64) -> Formatted { base_impl!();
let mut f = Formatted {
dirty: true,
data: Vec::new(),
parent: None, pub fn new(renderer: &mut render::Renderer, val: format::Component, x: f64, y: f64) -> Formatted {
should_draw: true, let mut f = ui_create!(Formatted {
layer: 0,
val: val, val: val,
x: x, x: x,
y: y, y: y,
width: 0.0, width: 0.0,
height: 18.0, height: 18.0,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
scale_x: 1.0, scale_x: 1.0,
scale_y: 1.0, scale_y: 1.0,
text: Vec::new(), text: Vec::new(),
max_width: -1.0, max_width: -1.0,
lines: 0, lines: 0
}; });
f.set_component(renderer); f.init_component(renderer);
f f
} }
pub fn with_width_limit(renderer: &mut render::Renderer, val: format::Component, x: f64, y: f64, max_width: f64) -> Formatted { pub fn with_width_limit(renderer: &mut render::Renderer, val: format::Component, x: f64, y: f64, max_width: f64) -> Formatted {
let mut f = Formatted { let mut f = ui_create!(Formatted {
dirty: true,
data: Vec::new(),
parent: None,
should_draw: true,
layer: 0,
val: val, val: val,
x: x, x: x,
y: y, y: y,
width: 0.0, width: 0.0,
height: 18.0, height: 18.0,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
scale_x: 1.0, scale_x: 1.0,
scale_y: 1.0, scale_y: 1.0,
text: Vec::new(), text: Vec::new(),
max_width: max_width, max_width: max_width,
lines: 0, lines: 0
}; });
f.set_component(renderer); f.init_component(renderer);
f f
} }
pub fn set_component(&mut self, renderer: &mut render::Renderer, val: format::Component) {
self.val = val;
self.init_component(renderer);
}
fn set_component(&mut self, renderer: &mut render::Renderer) { fn init_component(&mut self, renderer: &mut render::Renderer) {
self.text.clear(); self.text.clear();
let mut state = FormatState { let mut state = FormatState {
lines: 0, lines: 0,
@ -93,7 +74,7 @@ impl Formatted {
} }
fn update(&mut self, renderer: &mut render::Renderer) { fn update(&mut self, renderer: &mut render::Renderer) {
self.set_component(renderer); self.init_component(renderer);
} }
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, width: f64, height: f64, delta: f64) -> &Vec<u8> { fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, width: f64, height: f64, delta: f64) -> &Vec<u8> {
@ -116,18 +97,8 @@ impl Formatted {
((self.width + 2.0) * self.scale_x, self.height * self.scale_y) ((self.width + 2.0) * self.scale_x, self.height * self.scale_y)
} }
pub fn set_parent<T: UIElement>(&mut self, other: &ElementRef<T>) {
self.parent = Some(other.inner);
self.dirty = true;
}
lazy_field!(layer, isize, get_layer, set_layer);
lazy_field!(x, f64, get_x, set_x);
lazy_field!(y, f64, get_y, set_y);
lazy_field!(width, f64, get_width, set_width); lazy_field!(width, f64, get_width, set_width);
lazy_field!(height, f64, get_height, set_height); lazy_field!(height, f64, get_height, set_height);
lazy_field!(v_attach, VAttach, get_v_attach, set_v_attach);
lazy_field!(h_attach, HAttach, get_h_attach, set_h_attach);
lazy_field!(scale_x, f64, get_scale_x, set_scale_x); lazy_field!(scale_x, f64, get_scale_x, set_scale_x);
lazy_field!(scale_y, f64, get_scale_y, set_scale_y); lazy_field!(scale_y, f64, get_scale_y, set_scale_y);
@ -183,7 +154,7 @@ impl <'a> FormatState<'a> {
fn append_text(&mut self, txt: &str, color: format::Color) { fn append_text(&mut self, txt: &str, color: format::Color) {
let mut width = 0.0; let mut width = 0.0;
let mut last = 0; let mut last = 0;
for (i, c) in txt.chars().enumerate() { for (i, c) in txt.char_indices() {
let size = self.renderer.ui.size_of_char(c) + 2.0; let size = self.renderer.ui.size_of_char(c) + 2.0;
if (self.max_width > 0.0 && self.offset + width + size > self.max_width) || c == '\n' { if (self.max_width > 0.0 && self.offset + width + size > self.max_width) || c == '\n' {
let (rr, gg, bb) = color.to_rgb(); let (rr, gg, bb) = color.to_rgb();

View File

@ -16,6 +16,7 @@ pub mod logo;
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc;
use rand; use rand;
use render; use render;
use format; use format;
@ -34,6 +35,37 @@ pub enum Element {
macro_rules! element_impl { macro_rules! element_impl {
($($name:ident),+) => ( ($($name:ident),+) => (
impl Element { impl Element {
fn get_click_funcs(&self) -> Vec<Rc<Fn(&mut render::Renderer, &mut Container)>> {
match self {
$(
&Element::$name(ref val) => val.click_funcs.clone(),
)+
_ => unimplemented!(),
}
}
fn get_hover_funcs(&self) -> Vec<Rc<Fn(bool, &mut render::Renderer, &mut Container)>> {
match self {
$(
&Element::$name(ref val) => val.hover_funcs.clone(),
)+
_ => unimplemented!(),
}
}
fn should_call_hover(&mut self, new: bool) -> bool{
match self {
$(
&mut Element::$name(ref mut val) => {
let ret = val.hovered != new;
val.hovered = new;
ret
},
)+
_ => unimplemented!(),
}
}
fn should_draw(&self) -> bool { fn should_draw(&self) -> bool {
match self { match self {
$( $(
@ -204,8 +236,9 @@ impl Collection {
} }
} }
pub fn add<T: UIElement>(&mut self, element: ElementRef<T>) { pub fn add<T: UIElement>(&mut self, element: ElementRef<T>) -> ElementRef<T> {
self.elements.push(element.inner); self.elements.push(element.inner);
element
} }
pub fn remove_all(&mut self, container: &mut Container) { pub fn remove_all(&mut self, container: &mut Container) {
@ -296,10 +329,10 @@ impl Container {
self.version = renderer.ui.version; self.version = renderer.ui.version;
} }
// Borrow rules seem to prevent us from doing this in the first pass // Borrow rules seems to prevent us from doing this in the first pass
// so we split it. // so we split it.
let regions = self.collect_elements(sw, sh); let regions = self.collect_elements(sw, sh);
for re in &mut self.elements_list { for re in &self.elements_list {
let mut e = self.elements.get_mut(re).unwrap(); let mut e = self.elements.get_mut(re).unwrap();
if !e.should_draw() { if !e.should_draw() {
continue; continue;
@ -335,6 +368,61 @@ impl Container {
map map
} }
pub fn click_at(&mut self, renderer: &mut render::Renderer, x: f64, y: f64, width: f64, height: f64) {
let (sw, sh) = match self.mode {
Mode::Scaled => (SCALED_WIDTH / width, SCALED_HEIGHT / height),
Mode::Unscaled(scale) => (scale, scale),
};
let mx = (x / width) * SCALED_WIDTH;
let my = (y / height) * SCALED_HEIGHT;
let mut click = None;
for re in self.elements_list.iter().rev() {
let e = self.elements.get(re).unwrap();
let funcs = e.get_click_funcs();
if !funcs.is_empty() {
let r = self.get_draw_region(e, sw, sh);
if mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h {
click = Some(funcs);
break;
}
}
}
if let Some(click) = click {
for c in &click {
c(renderer, self);
}
}
}
pub fn hover_at(&mut self, renderer: &mut render::Renderer, x: f64, y: f64, width: f64, height: f64) {
let (sw, sh) = match self.mode {
Mode::Scaled => (SCALED_WIDTH / width, SCALED_HEIGHT / height),
Mode::Unscaled(scale) => (scale, scale),
};
let mx = (x / width) * SCALED_WIDTH;
let my = (y / height) * SCALED_HEIGHT;
let mut hovers = Vec::new();
for re in self.elements_list.iter().rev() {
let e = self.elements.get(re).unwrap();
let funcs = e.get_hover_funcs();
if !funcs.is_empty() {
let r = self.get_draw_region(e, sw, sh);
hovers.push((*re, funcs, mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h));
}
}
for hover in &hovers {
let call = {
let e = self.elements.get_mut(&hover.0).unwrap();
e.should_call_hover(hover.2)
};
if call {
for f in &hover.1 {
f(hover.2, renderer, self);
}
}
}
}
fn get_draw_region(&self, e: &Element, sw: f64, sh: f64) -> Region { fn get_draw_region(&self, e: &Element, sw: f64, sh: f64) -> Region {
let super_region = match e.get_parent() { let super_region = match e.get_parent() {
Some(ref p) => self.get_draw_region(self.elements.get(p).unwrap(), sw, sh), Some(ref p) => self.get_draw_region(self.elements.get(p).unwrap(), sw, sh),
@ -387,20 +475,82 @@ macro_rules! lazy_field {
) )
} }
pub struct Image { macro_rules! ui_element {
dirty: bool, (
data: Vec<u8>, $name:ident {
$(
$field:ident : $field_ty:ty
),+
}
) => (
pub struct $name {
dirty: bool,
data: Vec<u8>,
parent: Option<ElementRefInner>,
should_draw: bool,
layer: isize,
x: f64,
y: f64,
v_attach: VAttach,
h_attach: HAttach,
click_funcs: Vec<Rc<Fn(&mut render::Renderer, &mut Container)>>,
hover_funcs: Vec<Rc<Fn(bool, &mut render::Renderer, &mut Container)>>,
hovered: bool,
$(
$field: $field_ty
),+
}
)
}
parent: Option<ElementRefInner>, macro_rules! base_impl {
should_draw: bool, () => (
pub fn set_parent<T: UIElement>(&mut self, other: &ElementRef<T>) {
self.parent = Some(other.inner);
self.dirty = true;
}
pub fn add_click_func(&mut self, f: Rc<Fn(&mut render::Renderer, &mut Container)>) {
self.click_funcs.push(f);
}
pub fn add_hover_func(&mut self, f: Rc<Fn(bool, &mut render::Renderer, &mut Container)>) {
self.hover_funcs.push(f);
}
lazy_field!(layer, isize, get_layer, set_layer);
lazy_field!(x, f64, get_x, set_x);
lazy_field!(y, f64, get_y, set_y);
lazy_field!(v_attach, VAttach, get_v_attach, set_v_attach);
lazy_field!(h_attach, HAttach, get_h_attach, set_h_attach);
)
}
macro_rules! ui_create {
($name:ident {
$($field:ident: $e:expr),+
}) => (
$name {
dirty: true,
data: Vec::new(),
parent: None,
should_draw: true,
layer: 0,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
click_funcs: Vec::new(),
hover_funcs: Vec::new(),
hovered: false,
$($field: $e),+
}
)
}
ui_element!(Image {
texture: render::Texture, texture: render::Texture,
layer: isize,
x: f64,
y: f64,
width: f64, width: f64,
height: f64, height: f64,
v_attach: VAttach,
h_attach: HAttach,
t_x: f64, t_x: f64,
t_y: f64, t_y: f64,
@ -410,25 +560,19 @@ pub struct Image {
r: u8, r: u8,
g: u8, g: u8,
b: u8, b: u8,
a: u8, a: u8
} });
impl Image { impl Image {
pub fn new(texture: render::Texture, x: f64, y: f64, w: f64, h: f64, t_x: f64, t_y: f64, t_width: f64, t_height: f64, r: u8, g: u8, b: u8) -> Image { base_impl!();
Image {
dirty: true,
data: Vec::new(),
parent: None, pub fn new(texture: render::Texture, x: f64, y: f64, w: f64, h: f64, t_x: f64, t_y: f64, t_width: f64, t_height: f64, r: u8, g: u8, b: u8) -> Image {
should_draw: true, ui_create!(Image {
texture: texture, texture: texture,
layer: 0,
x: x, x: x,
y: y, y: y,
width: w, width: w,
height: h, height: h,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
t_x: t_x, t_x: t_x,
t_y: t_y, t_y: t_y,
@ -438,8 +582,8 @@ impl Image {
r: r, r: r,
g: g, g: g,
b: b, b: b,
a: 255, a: 255
} })
} }
fn update(&mut self, renderer: &mut render::Renderer) {} fn update(&mut self, renderer: &mut render::Renderer) {}
@ -463,11 +607,6 @@ impl Image {
(self.width, self.height) (self.width, self.height)
} }
pub fn set_parent<T: UIElement>(&mut self, other: &ElementRef<T>) {
self.parent = Some(other.inner);
self.dirty = true;
}
pub fn get_texture(&self) -> render::Texture { pub fn get_texture(&self) -> render::Texture {
self.texture.clone() self.texture.clone()
} }
@ -477,13 +616,8 @@ impl Image {
self.dirty = true; self.dirty = true;
} }
lazy_field!(layer, isize, get_layer, set_layer);
lazy_field!(x, f64, get_x, set_x);
lazy_field!(y, f64, get_y, set_y);
lazy_field!(width, f64, get_width, set_width); lazy_field!(width, f64, get_width, set_width);
lazy_field!(height, f64, get_height, set_height); lazy_field!(height, f64, get_height, set_height);
lazy_field!(v_attach, VAttach, get_v_attach, set_v_attach);
lazy_field!(h_attach, HAttach, get_h_attach, set_h_attach);
lazy_field!(t_x, f64, get_t_x, set_t_x); lazy_field!(t_x, f64, get_t_x, set_t_x);
lazy_field!(t_y, f64, get_t_y, set_t_y); lazy_field!(t_y, f64, get_t_y, set_t_y);
@ -516,48 +650,34 @@ impl UIElement for Image {
} }
} }
// TODO Getting values out? #[derive(Clone, Copy)]
pub struct BatchRef<T: UIElement> {
pub struct Batch { index: usize,
dirty: bool, ty: PhantomData<T>,
data: Vec<u8>,
parent: Option<ElementRefInner>,
should_draw: bool,
layer: isize,
x: f64,
y: f64,
width: f64,
height: f64,
v_attach: VAttach,
h_attach: HAttach,
elements: Vec<Element>,
} }
impl Batch { ui_element!(Batch {
pub fn new(x: f64, y: f64, w: f64, h: f64) -> Batch { width: f64,
Batch { height: f64,
dirty: true,
data: Vec::new(),
parent: None, elements: Vec<Element>
should_draw: true, });
layer: 0,
impl Batch {
base_impl!();
pub fn new(x: f64, y: f64, w: f64, h: f64) -> Batch {
ui_create!(Batch {
x: x, x: x,
y: y, y: y,
width: w, width: w,
height: h, height: h,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
elements: Vec::new(), elements: Vec::new()
} })
} }
fn update(&mut self, renderer: &mut render::Renderer) { fn update(&mut self, renderer: &mut render::Renderer) {}
}
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, width: f64, height: f64, delta: f64) -> &Vec<u8> { fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, width: f64, height: f64, delta: f64) -> &Vec<u8> {
if self.dirty { if self.dirty {
@ -580,23 +700,31 @@ impl Batch {
(self.width, self.height) (self.width, self.height)
} }
pub fn set_parent<T: UIElement>(&mut self, other: &ElementRef<T>) { pub fn add<T: UIElement>(&mut self, e: T) -> BatchRef<T> {
self.parent = Some(other.inner);
self.dirty = true;
}
pub fn add<T: UIElement>(&mut self, e: T) {
self.elements.push(e.wrap()); self.elements.push(e.wrap());
BatchRef { index: self.elements.len() - 1, ty: PhantomData }
}
pub fn get<T: UIElement>(&self, r: BatchRef<T>) -> &T {
T::unwrap_ref(&self.elements[r.index])
}
pub fn get_mut<T: UIElement>(&mut self, r: BatchRef<T>) -> &mut T {
self.dirty = true;
T::unwrap_ref_mut(&mut self.elements[r.index])
}
pub fn get_mut_at<T: UIElement>(&mut self, index: usize) -> &mut T {
self.dirty = true;
T::unwrap_ref_mut(&mut self.elements[index])
}
pub fn len(&self) -> usize {
self.elements.len()
} }
lazy_field!(layer, isize, get_layer, set_layer);
lazy_field!(x, f64, get_x, set_x);
lazy_field!(y, f64, get_y, set_y);
lazy_field!(width, f64, get_width, set_width); lazy_field!(width, f64, get_width, set_width);
lazy_field!(height, f64, get_height, set_height); lazy_field!(height, f64, get_height, set_height);
lazy_field!(v_attach, VAttach, get_v_attach, set_v_attach);
lazy_field!(h_attach, HAttach, get_h_attach, set_h_attach);
} }
impl UIElement for Batch { impl UIElement for Batch {
@ -619,53 +747,37 @@ impl UIElement for Batch {
} }
} }
pub struct Text { ui_element!(Text {
dirty: bool,
data: Vec<u8>,
parent: Option<ElementRefInner>,
should_draw: bool,
layer: isize,
val: String, val: String,
x: f64,
y: f64,
width: f64, width: f64,
height: f64, height: f64,
v_attach: VAttach,
h_attach: HAttach,
scale_x: f64, scale_x: f64,
scale_y: f64, scale_y: f64,
rotation: f64, rotation: f64,
r: u8, r: u8,
g: u8, g: u8,
b: u8, b: u8,
a: u8, a: u8
} });
impl Text { impl Text {
pub fn new(renderer: &render::Renderer, val: &str, x: f64, y: f64, r: u8, g: u8, b: u8) -> Text { base_impl!();
Text {
dirty: true,
data: Vec::new(),
parent: None, pub fn new(renderer: &render::Renderer, val: &str, x: f64, y: f64, r: u8, g: u8, b: u8) -> Text {
should_draw: true, ui_create!(Text {
layer: 0,
val: val.to_owned(), val: val.to_owned(),
x: x, x: x,
y: y, y: y,
width: renderer.ui.size_of_string(val), width: renderer.ui.size_of_string(val),
height: 18.0, height: 18.0,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
scale_x: 1.0, scale_x: 1.0,
scale_y: 1.0, scale_y: 1.0,
rotation: 0.0, rotation: 0.0,
r: r, r: r,
g: g, g: g,
b: b, b: b,
a: 255, a: 255
} })
} }
fn update(&mut self, renderer: &mut render::Renderer) { fn update(&mut self, renderer: &mut render::Renderer) {
@ -701,11 +813,6 @@ impl Text {
((self.width + 2.0) * self.scale_x, self.height * self.scale_y) ((self.width + 2.0) * self.scale_x, self.height * self.scale_y)
} }
pub fn set_parent<T: UIElement>(&mut self, other: &ElementRef<T>) {
self.parent = Some(other.inner);
self.dirty = true;
}
pub fn get_text(&self) -> &str { pub fn get_text(&self) -> &str {
&self.val &self.val
} }
@ -716,13 +823,8 @@ impl Text {
self.width = renderer.ui.size_of_string(val); self.width = renderer.ui.size_of_string(val);
} }
lazy_field!(layer, isize, get_layer, set_layer);
lazy_field!(x, f64, get_x, set_x);
lazy_field!(y, f64, get_y, set_y);
lazy_field!(width, f64, get_width, set_width); lazy_field!(width, f64, get_width, set_width);
lazy_field!(height, f64, get_height, set_height); lazy_field!(height, f64, get_height, set_height);
lazy_field!(v_attach, VAttach, get_v_attach, set_v_attach);
lazy_field!(h_attach, HAttach, get_h_attach, set_h_attach);
lazy_field!(scale_x, f64, get_scale_x, set_scale_x); lazy_field!(scale_x, f64, get_scale_x, set_scale_x);
lazy_field!(scale_y, f64, get_scale_y, set_scale_y); lazy_field!(scale_y, f64, get_scale_y, set_scale_y);
lazy_field!(rotation, f64, get_rotation, set_rotation); lazy_field!(rotation, f64, get_rotation, set_rotation);