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"
[dependencies]
rustc-serialize = "0.3"
glfw = "0.1.0"
byteorder = "0.3.13"
hyper = "0.6.13"
@ -16,6 +15,7 @@ zip = "0.1.12"
image = "0.3.12"
time = "0.1.32"
rand = "0.3.11"
rustc-serialize = "0.3"
[dependencies.steven_gl]
path = "./gl"

View File

@ -14,6 +14,7 @@
use serde_json;
use std::fmt;
use std::mem;
#[derive(Debug, Clone)]
pub enum Component {
@ -272,3 +273,85 @@ fn test_color_from() {
_ => 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 flate2;
extern crate rand;
extern crate rustc_serialize;
use std::sync::{Arc, RwLock};
use glfw::{Action, Context, Key};
@ -55,6 +56,8 @@ fn main() {
window.set_key_polling(true);
window.set_scroll_polling(true);
window.set_mouse_button_polling(true);
window.set_cursor_pos_polling(true);
window.make_current();
glfw.set_swap_interval(1);
@ -83,12 +86,18 @@ fn main() {
window.swap_buffers();
glfw.poll_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 {
glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
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) => {
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 flate2::read::{ZlibDecoder, ZlibEncoder};
use flate2;
use time;
pub const SUPPORTED_PROTOCOL: i32 = 73;
/// Helper macro for defining packets
#[macro_export]
@ -130,7 +133,7 @@ macro_rules! state_packets {
pub mod packet;
pub trait Serializable {
pub trait Serializable: Sized {
fn read_from(buf: &mut io::Read) -> Result<Self, io::Error>;
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>;
}
@ -651,11 +654,14 @@ pub struct Conn {
impl Conn {
pub fn new(target: &str) -> Result<Conn, Error>{
// TODO SRV record support
let stream = match TcpStream::connect(target) {
Ok(val) => val,
Err(err) => return Result::Err(Error::IOError(err))
let mut parts = target.split(":").collect::<Vec<&str>>();
let address = if parts.len() == 1 {
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 {
stream: stream,
host: parts[0].to_owned(),
@ -748,6 +754,91 @@ impl Conn {
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 {
@ -805,6 +896,7 @@ pub trait PacketType {
fn write(self, buf: &mut io::Write) -> Result<(), io::Error>;
}
// REMOVE ME
// #[test]
pub fn test() {
let mut c = Conn::new("localhost:25565").unwrap();

View File

@ -193,6 +193,9 @@ pub struct TextureManager {
animated_textures: Vec<AnimatedTexture>,
pending_uploads: Vec<(i32, atlas::Rect, Vec<u8>)>,
dynamic_textures: HashMap<String, (i32, atlas::Rect)>,
free_dynamics: Vec<(i32, atlas::Rect)>,
}
impl TextureManager {
@ -204,6 +207,9 @@ impl TextureManager {
atlases: Vec::new(),
animated_textures: Vec::new(),
pending_uploads: Vec::new(),
dynamic_textures: HashMap::new(),
free_dynamics: Vec::new(),
};
tm.add_defaults();
tm
@ -222,6 +228,8 @@ impl TextureManager {
}
fn update_textures(&mut self, version: usize) {
self.dynamic_textures.clear();
self.free_dynamics.clear();
self.pending_uploads.clear();
self.atlases.clear();
self.animated_textures.clear();
@ -391,6 +399,74 @@ impl TextureManager {
self.textures.insert(full_name.to_owned(), t.clone());
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 {

View File

@ -2,6 +2,8 @@
mod server_list;
pub use self::server_list::*;
use std::rc::Rc;
use render;
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_h_attach(ui::HAttach::Center);
(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::thread;
use std::sync::mpsc;
use std::rc::Rc;
use ui;
use render;
use format;
use format::{Component, TextComponent};
use protocol;
use serde_json;
use std::cmp::max;
use time;
use image;
use rustc_serialize::base64::FromBase64;
use rand;
use rand::{Rng};
pub struct ServerList {
elements: Option<UIElements>,
disconnect_reason: Option<format::Component>,
disconnect_reason: Option<Component>,
}
struct UIElements {
@ -22,6 +33,28 @@ struct Server {
back: ui::ElementRef<ui::Image>,
offset: 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 {
@ -35,7 +68,7 @@ impl Server {
}
impl ServerList {
pub fn new(disconnect_reason: Option<format::Component>) -> ServerList {
pub fn new(disconnect_reason: Option<Component>) -> ServerList {
ServerList {
elements: None,
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) {
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();
let file = match fs::File::open("servers.json") {
Ok(val) => val,
Err(e) => return,
Err(_) => return,
};
let servers_info: serde_json::Value = serde_json::from_reader(file).unwrap();
let servers = servers_info.find("servers").unwrap().as_array().unwrap();
@ -62,7 +101,7 @@ impl ServerList {
for svr in servers {
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");
@ -71,14 +110,38 @@ impl ServerList {
back.set_v_attach(ui::VAttach::Middle);
back.set_h_attach(ui::HAttach::Center);
let (send, recv) = mpsc::channel::<PingInfo>();
let mut server = Server {
collection: ui::Collection::new(),
back: ui_container.add(back),
offset: offset,
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.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);
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);
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);
ping.set_h_attach(ui::HAttach::Right);
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);
players.set_h_attach(ui::HAttach::Right);
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);
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);
let re = ui_container.add(refresh);
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(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);
add.set_v_attach(ui::VAttach::Middle);
add.set_h_attach(ui::HAttach::Center);
let re = ui_container.add(add);
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(ui_container.add(txt));
elements.add(tre);
let mut options = super::new_button(renderer, 5.0, 25.0, 40.0, 40.0);
options.set_v_attach(ui::VAttach::Bottom);
@ -136,6 +279,7 @@ impl super::Screen for ServerList {
cog.set_parent(&re);
cog.set_v_attach(ui::VAttach::Middle);
cog.set_h_attach(ui::HAttach::Center);
super::button_action(ui_container, re.clone(), None, None);
elements.add(re);
elements.add(ui_container.add(cog));
@ -188,13 +332,78 @@ impl super::Screen for ServerList {
elements.logo.tick(renderer, ui_container);
for s in &mut elements.servers {
let back = ui_container.get_mut(&s.back);
let dy = s.y - back.get_y();
if dy*dy > 1.0 {
let y = back.get_y();
back.set_y(y + delta * dy * 0.1);
} else {
back.set_y(s.y);
{
let back = ui_container.get_mut(&s.back);
let dy = s.y - back.get_y();
if dy*dy > 1.0 {
let y = back.get_y();
back.set_y(y + delta * dy * 0.1);
} 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 {
dirty: bool,
data: Vec<u8>,
parent: Option<ElementRefInner>,
should_draw: bool,
layer: isize,
ui_element!(Formatted {
val: format::Component,
x: f64,
y: f64,
width: f64,
height: f64,
v_attach: VAttach,
h_attach: HAttach,
scale_x: f64,
scale_y: f64,
text: Vec<Element>,
max_width: f64,
lines: usize,
}
lines: usize
});
impl Formatted {
pub fn new(renderer: &mut render::Renderer, val: format::Component, x: f64, y: f64) -> Formatted {
let mut f = Formatted {
dirty: true,
data: Vec::new(),
base_impl!();
parent: None,
should_draw: true,
layer: 0,
pub fn new(renderer: &mut render::Renderer, val: format::Component, x: f64, y: f64) -> Formatted {
let mut f = ui_create!(Formatted {
val: val,
x: x,
y: y,
width: 0.0,
height: 18.0,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
scale_x: 1.0,
scale_y: 1.0,
text: Vec::new(),
max_width: -1.0,
lines: 0,
};
f.set_component(renderer);
lines: 0
});
f.init_component(renderer);
f
}
pub fn with_width_limit(renderer: &mut render::Renderer, val: format::Component, x: f64, y: f64, max_width: f64) -> Formatted {
let mut f = Formatted {
dirty: true,
data: Vec::new(),
parent: None,
should_draw: true,
layer: 0,
let mut f = ui_create!(Formatted {
val: val,
x: x,
y: y,
width: 0.0,
height: 18.0,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
scale_x: 1.0,
scale_y: 1.0,
text: Vec::new(),
max_width: max_width,
lines: 0,
};
f.set_component(renderer);
lines: 0
});
f.init_component(renderer);
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();
let mut state = FormatState {
lines: 0,
@ -93,7 +74,7 @@ impl Formatted {
}
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> {
@ -116,18 +97,8 @@ impl Formatted {
((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!(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_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) {
let mut width = 0.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;
if (self.max_width > 0.0 && self.offset + width + size > self.max_width) || c == '\n' {
let (rr, gg, bb) = color.to_rgb();

View File

@ -16,6 +16,7 @@ pub mod logo;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::rc::Rc;
use rand;
use render;
use format;
@ -34,6 +35,37 @@ pub enum Element {
macro_rules! element_impl {
($($name:ident),+) => (
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 {
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);
element
}
pub fn remove_all(&mut self, container: &mut Container) {
@ -296,10 +329,10 @@ impl Container {
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.
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();
if !e.should_draw() {
continue;
@ -335,6 +368,61 @@ impl Container {
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 {
let super_region = match e.get_parent() {
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 {
dirty: bool,
data: Vec<u8>,
macro_rules! ui_element {
(
$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>,
should_draw: bool,
macro_rules! base_impl {
() => (
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,
layer: isize,
x: f64,
y: f64,
width: f64,
height: f64,
v_attach: VAttach,
h_attach: HAttach,
t_x: f64,
t_y: f64,
@ -410,25 +560,19 @@ pub struct Image {
r: u8,
g: u8,
b: u8,
a: u8,
}
a: u8
});
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 {
Image {
dirty: true,
data: Vec::new(),
base_impl!();
parent: None,
should_draw: true,
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 {
ui_create!(Image {
texture: texture,
layer: 0,
x: x,
y: y,
width: w,
height: h,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
t_x: t_x,
t_y: t_y,
@ -438,8 +582,8 @@ impl Image {
r: r,
g: g,
b: b,
a: 255,
}
a: 255
})
}
fn update(&mut self, renderer: &mut render::Renderer) {}
@ -463,11 +607,6 @@ impl Image {
(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 {
self.texture.clone()
}
@ -477,13 +616,8 @@ impl Image {
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!(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_y, f64, get_t_y, set_t_y);
@ -516,48 +650,34 @@ impl UIElement for Image {
}
}
// TODO Getting values out?
pub struct Batch {
dirty: bool,
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>,
#[derive(Clone, Copy)]
pub struct BatchRef<T: UIElement> {
index: usize,
ty: PhantomData<T>,
}
impl Batch {
pub fn new(x: f64, y: f64, w: f64, h: f64) -> Batch {
Batch {
dirty: true,
data: Vec::new(),
ui_element!(Batch {
width: f64,
height: f64,
parent: None,
should_draw: true,
layer: 0,
elements: Vec<Element>
});
impl Batch {
base_impl!();
pub fn new(x: f64, y: f64, w: f64, h: f64) -> Batch {
ui_create!(Batch {
x: x,
y: y,
width: w,
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> {
if self.dirty {
@ -580,23 +700,31 @@ impl Batch {
(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 add<T: UIElement>(&mut self, e: T) {
pub fn add<T: UIElement>(&mut self, e: T) -> BatchRef<T> {
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!(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 {
@ -619,53 +747,37 @@ impl UIElement for Batch {
}
}
pub struct Text {
dirty: bool,
data: Vec<u8>,
parent: Option<ElementRefInner>,
should_draw: bool,
layer: isize,
ui_element!(Text {
val: String,
x: f64,
y: f64,
width: f64,
height: f64,
v_attach: VAttach,
h_attach: HAttach,
scale_x: f64,
scale_y: f64,
rotation: f64,
r: u8,
g: u8,
b: u8,
a: u8,
}
a: u8
});
impl Text {
pub fn new(renderer: &render::Renderer, val: &str, x: f64, y: f64, r: u8, g: u8, b: u8) -> Text {
Text {
dirty: true,
data: Vec::new(),
base_impl!();
parent: None,
should_draw: true,
layer: 0,
pub fn new(renderer: &render::Renderer, val: &str, x: f64, y: f64, r: u8, g: u8, b: u8) -> Text {
ui_create!(Text {
val: val.to_owned(),
x: x,
y: y,
width: renderer.ui.size_of_string(val),
height: 18.0,
v_attach: VAttach::Top,
h_attach: HAttach::Left,
scale_x: 1.0,
scale_y: 1.0,
rotation: 0.0,
r: r,
g: g,
b: b,
a: 255,
}
a: 255
})
}
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)
}
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 {
&self.val
}
@ -716,13 +823,8 @@ impl Text {
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!(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_y, f64, get_scale_y, set_scale_y);
lazy_field!(rotation, f64, get_rotation, set_rotation);