diff --git a/Cargo.toml b/Cargo.toml index 27dceb4..9141397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ authors = [ "Thinkofdeath " ] 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" diff --git a/src/format.rs b/src/format.rs index 6cbe786..f0c9cba 100644 --- a/src/format.rs +++ b/src/format.rs @@ -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(); + } + }, + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ae8ceb0..e1357aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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) + } _ => {} } } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 1aacef2..483f314 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -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; 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{ // 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::>(); + let address = if parts.len() == 1 { + parts.push("25565"); + format!("{}:25565", parts[0]) + } else { + format!("{}:{}", parts[0], parts[1]) }; - let parts = target.split(":").collect::>(); + 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, +} + +#[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, +} + +#[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(); diff --git a/src/render/mod.rs b/src/render/mod.rs index bbab203..d36070f 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -193,6 +193,9 @@ pub struct TextureManager { animated_textures: Vec, pending_uploads: Vec<(i32, atlas::Rect, Vec)>, + + dynamic_textures: HashMap, + 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 { diff --git a/src/screen/mod.rs b/src/screen/mod.rs index 9450630..76a4827 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -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, txt: Option>, + click: Option> +) { + 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::(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); + } } \ No newline at end of file diff --git a/src/screen/server_list.rs b/src/screen/server_list.rs index ea8cc45..60c2c87 100644 --- a/src/screen/server_list.rs +++ b/src/screen/server_list.rs @@ -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, - disconnect_reason: Option, + disconnect_reason: Option, } struct UIElements { @@ -22,6 +33,28 @@ struct Server { back: ui::ElementRef, offset: f64, y: f64, + + motd: ui::ElementRef, + ping: ui::ElementRef, + players: ui::ElementRef, + version: ui::ElementRef, + + icon: ui::ElementRef, + icon_texture: Option, + + done_ping: bool, + recv: mpsc::Receiver, +} + +struct PingInfo { + motd: format::Component, + ping: time::Duration, + exists: bool, + online: i32, + max: i32, + protocol_version: i32, + protocol_name: String, + favicon: Option, } impl Server { @@ -35,7 +68,7 @@ impl Server { } impl ServerList { - pub fn new(disconnect_reason: Option) -> ServerList { + pub fn new(disconnect_reason: Option) -> 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::(); 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)); + }, + _ => {} + } } } } diff --git a/src/ui/formatted.rs b/src/ui/formatted.rs index 474659e..07c6edf 100644 --- a/src/ui/formatted.rs +++ b/src/ui/formatted.rs @@ -1,80 +1,61 @@ -pub struct Formatted { - dirty: bool, - data: Vec, - - parent: Option, - 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, 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 { @@ -116,18 +97,8 @@ impl Formatted { ((self.width + 2.0) * self.scale_x, self.height * self.scale_y) } - pub fn set_parent(&mut self, other: &ElementRef) { - 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(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 7cf33db..a2aa023 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -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> { + match self { + $( + &Element::$name(ref val) => val.click_funcs.clone(), + )+ + _ => unimplemented!(), + } + } + + fn get_hover_funcs(&self) -> Vec> { + 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(&mut self, element: ElementRef) { + pub fn add(&mut self, element: ElementRef) -> ElementRef { 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, +macro_rules! ui_element { + ( + $name:ident { + $( + $field:ident : $field_ty:ty + ),+ + } + ) => ( + pub struct $name { + dirty: bool, + data: Vec, + parent: Option, + should_draw: bool, + layer: isize, + x: f64, + y: f64, + v_attach: VAttach, + h_attach: HAttach, + click_funcs: Vec>, + hover_funcs: Vec>, + hovered: bool, + $( + $field: $field_ty + ),+ + } + ) +} - parent: Option, - should_draw: bool, +macro_rules! base_impl { + () => ( + pub fn set_parent(&mut self, other: &ElementRef) { + self.parent = Some(other.inner); + self.dirty = true; + } + + pub fn add_click_func(&mut self, f: Rc) { + self.click_funcs.push(f); + } + + pub fn add_hover_func(&mut self, f: Rc) { + 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(&mut self, other: &ElementRef) { - 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, - - parent: Option, - should_draw: bool, - layer: isize, - x: f64, - y: f64, - width: f64, - height: f64, - v_attach: VAttach, - h_attach: HAttach, - - elements: Vec, +#[derive(Clone, Copy)] +pub struct BatchRef { + index: usize, + ty: PhantomData, } -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 +}); + +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 { if self.dirty { @@ -580,23 +700,31 @@ impl Batch { (self.width, self.height) } - pub fn set_parent(&mut self, other: &ElementRef) { - self.parent = Some(other.inner); - self.dirty = true; - } - - pub fn add(&mut self, e: T) { + pub fn add(&mut self, e: T) -> BatchRef { self.elements.push(e.wrap()); + BatchRef { index: self.elements.len() - 1, ty: PhantomData } + } + + pub fn get(&self, r: BatchRef) -> &T { + T::unwrap_ref(&self.elements[r.index]) + } + + pub fn get_mut(&mut self, r: BatchRef) -> &mut T { + self.dirty = true; + T::unwrap_ref_mut(&mut self.elements[r.index]) + } + + pub fn get_mut_at(&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, - - parent: Option, - 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(&mut self, other: &ElementRef) { - 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);