stevenarella/src/screen/server_list.rs

578 lines
23 KiB
Rust

// Copyright 2015 Matthew Collins
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fs;
use std::thread;
use std::sync::mpsc;
use std::rc::Rc;
use std::cell::RefCell;
use ui;
use render;
use format;
use format::{Component, TextComponent};
use protocol;
use serde_json;
use time;
use image;
use rustc_serialize::base64::FromBase64;
use rand;
use rand::Rng;
pub struct ServerList {
elements: Option<UIElements>,
disconnect_reason: Option<Component>,
needs_reload: Rc<RefCell<bool>>,
}
struct UIElements {
logo: ui::logo::Logo,
elements: ui::Collection,
servers: Vec<Server>,
}
struct Server {
collection: ui::Collection,
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 {
fn update_position(&mut self) {
if self.offset < 0.0 {
self.y = self.offset * 200.0;
} else {
self.y = self.offset * 100.0;
}
}
}
impl ServerList {
pub fn new(disconnect_reason: Option<Component>) -> ServerList {
ServerList {
elements: None,
disconnect_reason: disconnect_reason,
needs_reload: Rc::new(RefCell::new(false)),
}
}
fn reload_server_list(&mut self,
renderer: &mut render::Renderer,
ui_container: &mut ui::Container) {
let elements = self.elements.as_mut().unwrap();
*self.needs_reload.borrow_mut() = false;
{
// Clean up previous list entries and icons.
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(_) => return,
};
let servers_info: serde_json::Value = serde_json::from_reader(file).unwrap();
let servers = servers_info.find("servers").unwrap().as_array().unwrap();
let mut offset = 0.0;
// Default icon whilst we ping the servers or if the server doesn't provide one
let default_icon = render::Renderer::get_texture(renderer.get_textures_ref(),
"misc/unknown_server");
// General gui icons
let icons = render::Renderer::get_texture(renderer.get_textures_ref(), "gui/icons");
for svr in servers {
let name = svr.find("name").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");
// Everything is attached to this
let mut back = ui::Image::new(solid,
0.0,
offset * 100.0,
700.0,
100.0,
0.0,
0.0,
1.0,
1.0,
0,
0,
0);
back.set_a(100);
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();
// Make whole entry interactable
{
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, _, 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 |_, _| {
println!("Connecting to {}", address);
}));
}
// Server name
let mut text = ui::Text::new(renderer, &name, 100.0, 5.0, 255, 255, 255);
text.set_parent(&server.back);
server.collection.add(ui_container.add(text));
// Server icon
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.icon = server.collection.add(ui_container.add(icon));
// Ping indicator
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.ping = server.collection.add(ui_container.add(ping));
// Player count
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.players = server.collection.add(ui_container.add(players));
// Server's message of the day
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));
// Version information
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));
// Delete entry button
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);
// Edit entry button
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;
// Don't block the main thread whilst pinging the server
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
};
drop(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,
}));
}
Err(err) => {
let e = format!("{}", err);
let mut msg = TextComponent::new(&e);
msg.modifier.color = Some(format::Color::Red);
drop(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,
}));
}
}
});
}
}
}
impl super::Screen for ServerList {
fn on_active(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) {
let logo = ui::logo::Logo::new(renderer.resources.clone(), renderer, ui_container);
let mut elements = ui::Collection::new();
// Refresh the server list
let (mut refresh, mut txt) = super::new_button_text(renderer,
"Refresh",
300.0,
-50.0 - 15.0,
100.0,
30.0);
refresh.set_v_attach(ui::VAttach::Middle);
refresh.set_h_attach(ui::HAttach::Center);
let re = ui_container.add(refresh);
txt.set_parent(&re);
let tre = ui_container.add(txt);
let nr = self.needs_reload.clone();
super::button_action(ui_container,
re.clone(),
Some(tre.clone()),
Some(Rc::new(move |_, _| {
*nr.borrow_mut() = true;
})));
elements.add(re);
elements.add(tre);
// Add a new server to the list
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()), None);
elements.add(re);
elements.add(tre);
// Options menu
let mut options = super::new_button(renderer, 5.0, 25.0, 40.0, 40.0);
options.set_v_attach(ui::VAttach::Bottom);
options.set_h_attach(ui::HAttach::Right);
let re = ui_container.add(options);
let mut cog = ui::Image::new(render::Renderer::get_texture(renderer.get_textures_ref(),
"steven:gui/cog"),
0.0,
0.0,
40.0,
40.0,
0.0,
0.0,
1.0,
1.0,
255,
255,
255);
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));
// Disclaimer
let mut warn = ui::Text::new(renderer,
"Not affiliated with Mojang/Minecraft",
5.0,
5.0,
255,
200,
200);
warn.set_v_attach(ui::VAttach::Bottom);
warn.set_h_attach(ui::HAttach::Right);
elements.add(ui_container.add(warn));
// If we are kicked from a server display the reason
if let Some(ref disconnect_reason) = self.disconnect_reason {
let mut dis_msg = ui::Text::new(renderer, "Disconnected", 0.0, 32.0, 255, 0, 0);
dis_msg.set_h_attach(ui::HAttach::Center);
let mut dis = ui::Formatted::with_width_limit(renderer,
disconnect_reason.clone(),
0.0,
48.0,
600.0);
dis.set_h_attach(ui::HAttach::Center);
let mut back =
ui::Image::new(render::Renderer::get_texture(renderer.get_textures_ref(),
"steven:solid"),
0.0,
30.0,
dis.get_width().max(dis_msg.get_width()) + 4.0,
dis.get_height() + 4.0 + 16.0,
0.0,
0.0,
1.0,
1.0,
0,
0,
0);
back.set_a(100);
back.set_h_attach(ui::HAttach::Center);
elements.add(ui_container.add(back));
elements.add(ui_container.add(dis));
elements.add(ui_container.add(dis_msg));
}
self.elements = Some(UIElements {
logo: logo,
elements: elements,
servers: Vec::new(),
});
self.reload_server_list(renderer, ui_container);
}
fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) {
// Clean up
{
let elements = self.elements.as_mut().unwrap();
elements.logo.remove(ui_container);
elements.elements.remove_all(ui_container);
for server in &mut elements.servers {
server.collection.remove_all(ui_container);
}
elements.servers.clear();
}
self.elements = None
}
fn tick(&mut self,
delta: f64,
renderer: &mut render::Renderer,
ui_container: &mut ui::Container) {
if *self.needs_reload.borrow() {
self.reload_server_list(renderer, ui_container);
}
let elements = self.elements.as_mut().unwrap();
elements.logo.tick(renderer, ui_container);
for s in &mut elements.servers {
// Animate the entries
{
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);
}
}
// Keep checking to see if the server has finished being
// pinged
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);
// Selects the icon for the given ping range
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));
}
_ => {}
}
}
}
}
fn on_scroll(&mut self, _: f64, y: f64) {
let elements = self.elements.as_mut().unwrap();
if elements.servers.is_empty() {
return;
}
let mut diff = y / 1.0;
{
let last = elements.servers.last().unwrap();
if last.offset + diff <= 2.0 {
diff = 2.0 - last.offset;
}
let first = elements.servers.first().unwrap();
if first.offset + diff >= 0.0 {
diff = -first.offset;
}
}
for s in &mut elements.servers {
s.offset += diff;
s.update_position();
}
}
}