diff --git a/src/console/mod.rs b/src/console/mod.rs index a1b1b62..9bea549 100644 --- a/src/console/mod.rs +++ b/src/console/mod.rs @@ -181,18 +181,25 @@ impl Vars { pub struct Console { history: Vec, + dirty: bool, - collection: ui::Collection, + elements: Option, active: bool, position: f64, } +struct ConsoleElements { + background: ui::ImageRef, + lines: Vec, +} + impl Console { pub fn new() -> Console { Console { history: vec![Component::Text(TextComponent::new("")); 200], + dirty: false, - collection: ui::Collection::new(), + elements: None, active: false, position: -220.0, } @@ -208,14 +215,11 @@ impl Console { pub fn tick(&mut self, ui_container: &mut ui::Container, - renderer: &mut render::Renderer, + renderer: &render::Renderer, delta: f64, width: f64) { - // To make sure the console is always on top it constant removes and readds itself. - // Its hacky but the console should never appear for normal users so its not really - // a major issue. - self.collection.remove_all(ui_container); if !self.active && self.position <= -220.0 { + self.elements = None; return; } if self.active { @@ -229,39 +233,48 @@ impl Console { } else { self.position = -220.0; } + let w = match ui_container.mode { ui::Mode::Scaled => width, ui::Mode::Unscaled(scale) => 854.0 / scale, }; - - let mut background = ui::Image::new( - render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid"), - 0.0, self.position, w, 220.0, - 0.0, 0.0, 1.0, 1.0, - 0, 0, 0 - ); - background.set_a(180); - let background = self.collection.add(ui_container.add(background)); - - let mut lines = Vec::new(); - let mut offset = 0.0; - for line in self.history.iter().rev() { - if offset >= 210.0 { - break; - } - let mut fmt = ui::Formatted::with_width_limit( - renderer, - line.clone(), - 5.0, 5.0 + offset, - w - 1.0 - ); - fmt.set_parent(&background); - fmt.set_v_attach(ui::VAttach::Bottom); - offset += fmt.get_height(); - lines.push(ui_container.add(fmt)); + if self.elements.is_none() { + let background = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, self.position) + .size(w, 220.0) + .colour((0, 0, 0, 180)) + .draw_index(500) + .create(ui_container); + self.elements = Some(ConsoleElements { + background: background, + lines: vec![], + }); + self.dirty = true; } - for fmt in lines { - self.collection.add(fmt); + let elements = self.elements.as_mut().unwrap(); + let mut background = elements.background.borrow_mut(); + background.y = self.position; + background.width = w; + + if self.dirty { + self.dirty = false; + elements.lines.clear(); + + let mut offset = 0.0; + for line in self.history.iter().rev() { + if offset >= 210.0 { + break; + } + let (_, height) = ui::Formatted::compute_size(renderer, line, w - 10.0); + elements.lines.push(ui::FormattedBuilder::new() + .text(line.clone()) + .position(5.0, 5.0 + offset) + .max_width(w - 10.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Left) + .create(&mut *background)); + offset += height; + } } } @@ -314,6 +327,7 @@ impl Console { Component::Text(TextComponent::new(&format!("{}", record.args()))) ]); self.history.push(Component::Text(msg)); + self.dirty = true; } } diff --git a/src/entity/player.rs b/src/entity/player.rs index f5ed8a6..d3117a9 100644 --- a/src/entity/player.rs +++ b/src/entity/player.rs @@ -494,7 +494,6 @@ struct MovementHandler { gamemode: ecs::Key, position: ecs::Key, velocity: ecs::Key, - game_info: ecs::Key, bounds: ecs::Key, rotation: ecs::Key, } @@ -518,7 +517,6 @@ impl MovementHandler { gamemode: m.get_key(), position: position, velocity: velocity, - game_info: m.get_key(), bounds: bounds, rotation: rotation, } diff --git a/src/main.rs b/src/main.rs index 7b2efc6..e200e19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ #![recursion_limit="300"] #![feature(const_fn)] +#![feature(rc_would_unwrap)] extern crate sdl2; extern crate image; @@ -243,7 +244,6 @@ fn main() { let delta = (diff.num_nanoseconds().unwrap() as f64) / frame_time; let (width, height) = window.drawable_size(); - let vsync_changed = *game.vars.get(settings::R_VSYNC); if vsync != vsync_changed { vsync = vsync_changed; @@ -262,7 +262,7 @@ fn main() { game.console .lock() .unwrap() - .tick(&mut ui_container, &mut game.renderer, delta, width as f64); + .tick(&mut ui_container, &game.renderer, delta, width as f64); ui_container.tick(&mut game.renderer, delta, width as f64, height as f64); game.renderer.tick(&mut game.server.world, delta, width, height); diff --git a/src/render/mod.rs b/src/render/mod.rs index 6819eca..dcb98b2 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1200,13 +1200,14 @@ impl TextureManager { height: height, }; self.pending_uploads.push((tex.atlas, rect, data)); - let t = tex.relative(0.0, 0.0, (width as f32) / (tex.width as f32), (height as f32) / (tex.height as f32)); + let mut t = tex.relative(0.0, 0.0, (width as f32) / (tex.width as f32), (height as f32) / (tex.height as f32)); let old_name = mem::replace(&mut tex.name, format!("steven-dynamic:{}", name)); self.dynamic_textures.insert(name.to_owned(), (tex.clone(), img)); // We need to rename the texture itself so that get_texture calls // work with the new name let mut old = self.textures.remove(&old_name).unwrap(); old.name = format!("steven-dynamic:{}", name); + t.name = old.name.clone(); self.textures.insert(format!("steven-dynamic:{}", name), old); t } else { @@ -1239,7 +1240,7 @@ struct AnimationFrame { #[derive(Clone, Debug)] pub struct Texture { - name: String, + pub name: String, version: usize, pub atlas: i32, x: usize, diff --git a/src/screen/connecting.rs b/src/screen/connecting.rs index e71756d..40abf33 100644 --- a/src/screen/connecting.rs +++ b/src/screen/connecting.rs @@ -14,7 +14,6 @@ use ui; use render; -use format::{self, Component, TextComponent}; pub struct Connecting { elements: Option, @@ -23,7 +22,9 @@ pub struct Connecting { struct UIElements { logo: ui::logo::Logo, - elements: ui::Collection, + _connect_msg: ui::TextRef, + _msg: ui::TextRef, + _disclaimer: ui::TextRef, } @@ -38,63 +39,48 @@ impl Connecting { impl super::Screen for Connecting { 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(); + let logo = ui::logo::Logo::new(renderer.resources.clone(), ui_container); - let mut connect_msg = ui::Formatted::new( - renderer, - Component::Text(TextComponent::new("Connecting to")), - 0.0, -16.0 - ); - connect_msg.set_v_attach(ui::VAttach::Middle); - connect_msg.set_h_attach(ui::HAttach::Center); - elements.add(ui_container.add(connect_msg)); + let connect_msg = ui::TextBuilder::new() + .text("Connecting to") + .position(0.0, -16.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); - let mut msg = TextComponent::new(&self.target); - msg.modifier.color = Some(format::Color::Yellow); - let mut server_msg = ui::Formatted::new( - renderer, - Component::Text(msg), - 0.0, 16.0 - ); - server_msg.set_v_attach(ui::VAttach::Middle); - server_msg.set_h_attach(ui::HAttach::Center); - elements.add(ui_container.add(server_msg)); + let msg = ui::TextBuilder::new() + .text(self.target.clone()) + .position(0.0, 16.0) + .colour((255, 255, 85, 255)) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); // 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)); + let disclaimer = ui::TextBuilder::new() + .text("Not affiliated with Mojang/Minecraft") + .position(5.0, 5.0) + .colour((255, 200, 200, 255)) + .alignment(ui::VAttach::Bottom, ui::HAttach::Right) + .create(ui_container); self.elements = Some(UIElements { logo: logo, - elements: elements, + _disclaimer: disclaimer, + _msg: msg, + _connect_msg: connect_msg, }); } - fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut 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); - } self.elements = None } fn tick(&mut self, _delta: f64, renderer: &mut render::Renderer, - ui_container: &mut ui::Container) -> Option>{ + _ui_container: &mut ui::Container) -> Option>{ let elements = self.elements.as_mut().unwrap(); - elements.logo.tick(renderer, ui_container); + elements.logo.tick(renderer); None } } diff --git a/src/screen/edit_server.rs b/src/screen/edit_server.rs index 5d903ec..fd95cdf 100644 --- a/src/screen/edit_server.rs +++ b/src/screen/edit_server.rs @@ -27,7 +27,11 @@ pub struct EditServerEntry { struct UIElements { logo: ui::logo::Logo, - elements: ui::Collection, + + _name: ui::TextBoxRef, + _address: ui::TextBoxRef, + _done: ui::ButtonRef, + _cancel: ui::ButtonRef, } impl EditServerEntry { @@ -77,104 +81,99 @@ impl EditServerEntry { impl super::Screen for EditServerEntry { 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(); + let logo = ui::logo::Logo::new(renderer.resources.clone(), ui_container); // Name - let mut server_name = ui::TextBox::new( - renderer, self.entry_info.as_ref().map_or("", |v| &v.1), - 0.0, -20.0, 400.0, 40.0 - ); - server_name.set_v_attach(ui::VAttach::Middle); - server_name.set_h_attach(ui::HAttach::Center); - server_name.add_submit_func(|_, ui| { - ui.cycle_focus(); - }); - let ure = ui_container.add(server_name); - let mut server_name_label = ui::Text::new(renderer, "Name:", 0.0, -18.0, 255, 255, 255); - server_name_label.set_parent(&ure); - let server_name_txt = elements.add(ure); - elements.add(ui_container.add(server_name_label)); + let server_name = ui::TextBoxBuilder::new() + .input(self.entry_info.as_ref().map_or("", |v| &v.1)) + .position(0.0, -20.0) + .size(400.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + ui::TextBuilder::new() + .text("Name:") + .position(0.0, -18.0) + .attach(&mut *server_name.borrow_mut()); - // Name - let mut server_address = ui::TextBox::new( - renderer, self.entry_info.as_ref().map_or("", |v| &v.2), - 0.0, 40.0, 400.0, 40.0 - ); - server_address.set_v_attach(ui::VAttach::Middle); - server_address.set_h_attach(ui::HAttach::Center); - server_address.add_submit_func(|_, ui| { - ui.cycle_focus(); - }); - let ure = ui_container.add(server_address); - let mut server_address_label = ui::Text::new(renderer, "Address:", 0.0, -18.0, 255, 255, 255); - server_address_label.set_parent(&ure); - let server_address_txt = elements.add(ure); - elements.add(ui_container.add(server_address_label)); + // Address + let server_address = ui::TextBoxBuilder::new() + .input(self.entry_info.as_ref().map_or("", |v| &v.2)) + .position(0.0, 40.0) + .size(400.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + ui::TextBuilder::new() + .text("Address") + .position(0.0, -18.0) + .attach(&mut *server_address.borrow_mut()); // Done - let (mut done, mut txt) = super::new_button_text( - renderer, "Done", - 110.0, 100.0, 200.0, 40.0 - ); - done.set_v_attach(ui::VAttach::Middle); - done.set_h_attach(ui::HAttach::Center); - let re = ui_container.add(done); - txt.set_parent(&re); - let tre = ui_container.add(txt); - - let index = self.entry_info.as_ref().map(|v| v.0); - super::button_action(ui_container, re.clone(), Some(tre.clone()), move |game, uic| { - Self::save_servers( - index, - &uic.get(&server_name_txt).get_input(), - &uic.get(&server_address_txt).get_input() - ); - game.screen_sys.replace_screen(Box::new(super::ServerList::new(None))); - }); - elements.add(re); - elements.add(tre); + let done = ui::ButtonBuilder::new() + .position(110.0, 100.0) + .size(200.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut done = done.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Done") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *done); + done.add_text(txt); + let index = self.entry_info.as_ref().map(|v| v.0); + let server_name = server_name.clone(); + let server_address = server_address.clone(); + done.add_click_func(move |_, game| { + Self::save_servers( + index, + &server_name.borrow().input, + &server_address.borrow().input, + ); + game.screen_sys.replace_screen(Box::new(super::ServerList::new(None))); + true + }); + } // Cancel - let (mut cancel, mut txt) = super::new_button_text( - renderer, "Cancel", - -110.0, 100.0, 200.0, 40.0 - ); - cancel.set_v_attach(ui::VAttach::Middle); - cancel.set_h_attach(ui::HAttach::Center); - let re = ui_container.add(cancel); - txt.set_parent(&re); - let tre = ui_container.add(txt); - super::button_action(ui_container, re.clone(), Some(tre.clone()), |game, _| { - game.screen_sys.replace_screen(Box::new(super::ServerList::new(None))); - }); - elements.add(re); - elements.add(tre); - + let cancel = ui::ButtonBuilder::new() + .position(-110.0, 100.0) + .size(200.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut cancel = cancel.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Cancel") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *cancel); + cancel.add_text(txt); + cancel.add_click_func(|_, game| { + game.screen_sys.replace_screen(Box::new(super::ServerList::new(None))); + true + }); + } self.elements = Some(UIElements { logo: logo, - elements: elements, + _name: server_name, + _address: server_address, + _done: done, + _cancel: cancel, }); } - fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut 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); - } self.elements = None } fn tick(&mut self, _delta: f64, renderer: &mut render::Renderer, - ui_container: &mut ui::Container) -> Option> { + _ui_container: &mut ui::Container) -> Option> { let elements = self.elements.as_mut().unwrap(); - elements.logo.tick(renderer, ui_container); + elements.logo.tick(renderer); None } diff --git a/src/screen/login.rs b/src/screen/login.rs index 6aab9f8..950783c 100644 --- a/src/screen/login.rs +++ b/src/screen/login.rs @@ -33,13 +33,13 @@ pub struct Login { struct UIElements { logo: ui::logo::Logo, - elements: ui::Collection, - login_btn: ui::ElementRef, - login_btn_text: ui::ElementRef, - login_error: ui::ElementRef, - username_txt: ui::ElementRef, - password_txt: ui::ElementRef, + login_btn: ui::ButtonRef, + login_btn_text: ui::TextRef, + login_error: ui::TextRef, + username_txt: ui::TextBoxRef, + password_txt: ui::TextBoxRef, + _disclaimer: ui::TextRef, try_login: Rc>, refresh: bool, login_res: Option>>, @@ -56,73 +56,76 @@ impl Login { impl super::Screen for Login { 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(); + let logo = ui::logo::Logo::new(renderer.resources.clone(), ui_container); let try_login = Rc::new(Cell::new(false)); // Login - let (mut login, mut txt) = super::new_button_text(renderer, "Login", 0.0, 100.0, 400.0, 40.0); - login.set_v_attach(ui::VAttach::Middle); - login.set_h_attach(ui::HAttach::Center); - let re = ui_container.add(login); - txt.set_parent(&re); - let tre = ui_container.add(txt); - let tl = try_login.clone(); - super::button_action(ui_container, - re.clone(), - Some(tre.clone()), - move |_, _| { - tl.set(true); - }); - let login_btn = elements.add(re); - let login_btn_text = elements.add(tre); + let login_btn = ui::ButtonBuilder::new() + .position(0.0, 100.0) + .size(400.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + let login_btn_text = ui::TextBuilder::new() + .text("Login") + .position(0.0, 0.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *login_btn.borrow_mut()); + { + let mut btn = login_btn.borrow_mut(); + btn.add_text(login_btn_text.clone()); + let tl = try_login.clone(); + btn.add_click_func(move |_, _| { + tl.set(true); + true + }); + } + // Login Error - let mut login_error = ui::Text::new(renderer, "", 0.0, 150.0, 255, 50, 50); - login_error.set_v_attach(ui::VAttach::Middle); - login_error.set_h_attach(ui::HAttach::Center); - let login_error = elements.add(ui_container.add(login_error)); + let login_error = ui::TextBuilder::new() + .text("") + .position(0.0, 150.0) + .colour((255, 50, 50, 255)) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); // Username - let mut username = ui::TextBox::new(renderer, "", 0.0, -20.0, 400.0, 40.0); - username.set_v_attach(ui::VAttach::Middle); - username.set_h_attach(ui::HAttach::Center); - username.add_submit_func(|_, ui| { - ui.cycle_focus(); - }); - let ure = ui_container.add(username); - let mut username_label = ui::Text::new(renderer, "Username/Email:", 0.0, -18.0, 255, 255, 255); - username_label.set_parent(&ure); - let username_txt = elements.add(ure); - elements.add(ui_container.add(username_label)); + let username_txt = ui::TextBoxBuilder::new() + .position(0.0, -20.0) + .size(400.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + ui::TextBox::make_focusable(&username_txt, ui_container); + ui::TextBuilder::new() + .text("Username/Email:") + .position(0.0, -18.0) + .attach(&mut *username_txt.borrow_mut()); // Password - let mut password = ui::TextBox::new(renderer, "", 0.0, 40.0, 400.0, 40.0); - password.set_v_attach(ui::VAttach::Middle); - password.set_h_attach(ui::HAttach::Center); - password.set_password(true); + let password_txt = ui::TextBoxBuilder::new() + .position(0.0, 40.0) + .size(400.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .password(true) + .create(ui_container); + ui::TextBox::make_focusable(&password_txt, ui_container); + ui::TextBuilder::new() + .text("Password:") + .position(0.0, -18.0) + .attach(&mut *password_txt.borrow_mut()); let tl = try_login.clone(); - password.add_submit_func(move |_, _| { + password_txt.borrow_mut().add_submit_func(move |_, _| { tl.set(true); }); - let pre = ui_container.add(password); - let mut password_label = ui::Text::new(renderer, "Password:", 0.0, -18.0, 255, 255, 255); - password_label.set_parent(&pre); - let password_txt = elements.add(pre); - elements.add(ui_container.add(password_label)); // 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)); + let disclaimer = ui::TextBuilder::new() + .text("Not affiliated with Mojang/Minecraft") + .position(5.0, 5.0) + .colour((255, 200, 200, 255)) + .alignment(ui::VAttach::Bottom, ui::HAttach::Right) + .create(ui_container); let profile = mojang::Profile { username: self.vars.get(auth::CL_USERNAME).clone(), @@ -134,7 +137,6 @@ impl super::Screen for Login { self.elements = Some(UIElements { logo: logo, - elements: elements, profile: profile, login_btn: login_btn, login_btn_text: login_btn_text, @@ -143,52 +145,37 @@ impl super::Screen for Login { refresh: refresh, login_res: None, + _disclaimer: disclaimer, + username_txt: username_txt, password_txt: password_txt, }); } - fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut 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); - } self.elements = None } fn tick(&mut self, _delta: f64, renderer: &mut render::Renderer, - ui_container: &mut ui::Container) -> Option> { + _ui_container: &mut ui::Container) -> Option> { let elements = self.elements.as_mut().unwrap(); if elements.try_login.get() && elements.login_res.is_none() { elements.try_login.set(false); let (tx, rx) = mpsc::channel(); elements.login_res = Some(rx); - { - let btn = ui_container.get_mut(&elements.login_btn); - btn.set_disabled(true); - } - { - let txt = ui_container.get_mut(&elements.login_btn_text); - txt.set_text(renderer, "Logging in..."); - } + elements.login_btn.borrow_mut().disabled = true; + elements.login_btn_text.borrow_mut().text = "Logging in...".into(); let mut client_token = self.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); if client_token.is_empty() { client_token = rand::thread_rng().gen_ascii_chars().take(20).collect::(); self.vars.set(auth::AUTH_CLIENT_TOKEN, client_token); } let client_token = self.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); - let username = { - let txt = ui_container.get(&elements.username_txt); - txt.get_input() - }; - let password = { - let txt = ui_container.get(&elements.password_txt); - txt.get_input() - }; + let username = elements.username_txt.borrow().input.clone(); + let password = elements.password_txt.borrow().input.clone(); let refresh = elements.refresh; let profile = elements.profile.clone(); thread::spawn(move || { @@ -203,14 +190,8 @@ impl super::Screen for Login { if let Some(rx) = elements.login_res.as_ref() { if let Ok(res) = rx.try_recv() { done = true; - { - let btn = ui_container.get_mut(&elements.login_btn); - btn.set_disabled(false); - } - { - let txt = ui_container.get_mut(&elements.login_btn_text); - txt.set_text(renderer, "Login"); - } + elements.login_btn.borrow_mut().disabled = false; + elements.login_btn_text.borrow_mut().text = "Login".into(); match res { Ok(val) => { self.vars.set(auth::CL_USERNAME, val.username.clone()); @@ -220,8 +201,7 @@ impl super::Screen for Login { return Some(Box::new(super::ServerList::new(None))); }, Err(err) => { - let login_error = ui_container.get_mut(&elements.login_error); - login_error.set_text(renderer, &format!("{}", err)); + elements.login_error.borrow_mut().text = format!("{}", err); }, } } @@ -230,7 +210,7 @@ impl super::Screen for Login { elements.login_res = None; } - elements.logo.tick(renderer, ui_container); + elements.logo.tick(renderer); None } } diff --git a/src/screen/mod.rs b/src/screen/mod.rs index ecfe142..fbece93 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -15,11 +15,12 @@ mod server_list; pub use self::server_list::*; mod login; -pub mod settings_menu; - pub use self::login::*; + pub mod connecting; pub mod edit_server; + +pub mod settings_menu; pub use self::settings_menu::{SettingsMenu, VideoSettingsMenu, AudioSettingsMenu}; use render; @@ -148,40 +149,3 @@ impl ScreenSystem { current.screen.on_scroll(x, y); } } - -pub fn new_button_text(renderer: &mut render::Renderer, - val: &str, - x: f64, - y: f64, - w: f64, - h: f64) - -> (ui::Button, ui::Text) { - let btn = ui::Button::new(x, y, w, h); - let mut text = ui::Text::new(renderer, val, 0.0, 0.0, 255, 255, 255); - text.set_v_attach(ui::VAttach::Middle); - text.set_h_attach(ui::HAttach::Center); - (btn, text) -} - -pub fn button_action(ui_container: &mut ui::Container, - btn: ui::ElementRef, - txt: Option>, - click: F) { - let button = ui_container.get_mut(&btn); - button.add_hover_func(move |over, _, ui_container| { - let disabled = { - let button = ui_container.get_mut(&btn); - button.is_disabled() - }; - let txt = txt.clone(); - if let Some(txt) = txt { - let text = ui_container.get_mut(&txt); - text.set_b(if over && !disabled { - 160 - } else { - 255 - }); - } - }); - button.add_click_func(click); -} diff --git a/src/screen/server_list.rs b/src/screen/server_list.rs index 2a21330..9a14a7d 100644 --- a/src/screen/server_list.rs +++ b/src/screen/server_list.rs @@ -40,22 +40,27 @@ pub struct ServerList { struct UIElements { logo: ui::logo::Logo, - elements: ui::Collection, servers: Vec, + + _add_btn: ui::ButtonRef, + _refresh_btn: ui::ButtonRef, + _options_btn: ui::ButtonRef, + _disclaimer: ui::TextRef, + + _disconnected: Option, } struct Server { - collection: ui::Collection, - back: ui::ElementRef, + back: ui::ImageRef, offset: f64, y: f64, - motd: ui::ElementRef, - ping: ui::ElementRef, - players: ui::ElementRef, - version: ui::ElementRef, + motd: ui::FormattedRef, + ping: ui::ImageRef, + players: ui::TextRef, + version: ui::FormattedRef, - icon: ui::ElementRef, + icon: ui::ImageRef, icon_texture: Option, done_ping: bool, @@ -98,10 +103,9 @@ impl ServerList { let elements = self.elements.as_mut().unwrap(); *self.needs_reload.borrow_mut() = false; { - // Clean up previous list entries and icons. + // Clean up previous list 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(&icon); } @@ -117,161 +121,139 @@ impl ServerList { 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 (index, svr) in servers.iter().enumerate() { let name = svr.find("name").unwrap().as_string().unwrap().to_owned(); 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 back = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, offset * 100.0) + .size(700.0, 100.0) + .colour((0, 0, 0, 100)) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); let (send, recv) = mpsc::channel::(); + // Make whole entry interactable + { + let mut backr = back.borrow_mut(); + let address = address.clone(); + backr.add_hover_func(move |this, over, _| { + this.colour.3 = if over { + 200 + } else { + 100 + }; + false + }); + backr.add_click_func(move |_, game| { + game.screen_sys.replace_screen(Box::new(super::connecting::Connecting::new(&address))); + game.connect_to(&address); + true + }); + } + + // Server name + ui::TextBuilder::new() + .text(name.clone()) + .position(100.0, 5.0) + .attach(&mut *back.borrow_mut()); + + // Server icon + let icon = ui::ImageBuilder::new() + .texture("misc/unknown_server") + .position(5.0, 5.0) + .size(90.0, 90.0) + .attach(&mut *back.borrow_mut()); + + + // Ping indicator + let ping = ui::ImageBuilder::new() + .texture("gui/icons") + .position(5.0, 5.0) + .size(20.0, 16.0) + .texture_coords((0.0, 56.0 / 256.0, 10.0 / 256.0, 8.0 / 256.0)) + .alignment(ui::VAttach::Top, ui::HAttach::Right) + .attach(&mut *back.borrow_mut()); + + // Player count + let players = ui::TextBuilder::new() + .text("???") + .position(30.0, 5.0) + .alignment(ui::VAttach::Top, ui::HAttach::Right) + .attach(&mut *back.borrow_mut()); + + // Server's message of the day + let motd = ui::FormattedBuilder::new() + .text(Component::Text(TextComponent::new("Connecting..."))) + .position(100.0, 23.0) + .max_width(700.0 - (90.0 + 10.0 + 5.0)) + .attach(&mut *back.borrow_mut()); + + // Version information + let version = ui::FormattedBuilder::new() + .text(Component::Text(TextComponent::new(""))) + .position(100.0, 5.0) + .max_width(700.0 - (90.0 + 10.0 + 5.0)) + .alignment(ui::VAttach::Bottom, ui::HAttach::Left) + .attach(&mut *back.borrow_mut()); + + // Delete entry button + let delete_entry = ui::ButtonBuilder::new() + .position(0.0, 0.0) + .size(25.0, 25.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Right) + .attach(&mut *back.borrow_mut()); + { + let mut btn = delete_entry.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("X") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *btn); + btn.add_text(txt); + } + + // Edit entry button + let edit_entry = ui::ButtonBuilder::new() + .position(25.0, 0.0) + .size(25.0, 25.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Right) + .attach(&mut *back.borrow_mut()); + { + let mut btn = edit_entry.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("E") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *btn); + btn.add_text(txt); + let index = index; + let sname = name.clone(); + let saddr = address.clone(); + btn.add_click_func(move |_, game| { + game.screen_sys.replace_screen(Box::new(super::edit_server::EditServerEntry::new( + Some((index, sname.clone(), saddr.clone())) + ))); + true + }) + } + let mut server = Server { - collection: ui::Collection::new(), - back: ui_container.add(back), + back: back, offset: offset, y: 0.0, done_ping: false, recv: recv, - motd: Default::default(), - ping: Default::default(), - players: Default::default(), - version: Default::default(), + motd: motd, + ping: ping, + players: players, + version: version, - icon: Default::default(), + icon: icon, 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(move |over, _, ui_container| { - let back = ui_container.get_mut(&back_ref); - back.set_a(if over { - 200 - } else { - 100 - }); - }); - - back.add_click_func(move |game, _| { - game.screen_sys.replace_screen(Box::new(super::connecting::Connecting::new(&address))); - game.connect_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()), |_,_| {}); // TOOO: delete entry - 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); - let index = index; - let sname = name.clone(); - let saddr = address.clone(); - super::button_action(ui_container, re.clone(), Some(tre.clone()), move |game,_|{ - let sname = sname.clone(); - let saddr = saddr.clone(); - game.screen_sys.replace_screen(Box::new(super::edit_server::EditServerEntry::new( - Some((index, sname, saddr)) - ))); - }); - server.collection.add(re); - server.collection.add(tre); - elements.servers.push(server); offset += 1.0; @@ -304,7 +286,7 @@ impl ServerList { let e = format!("{}", err); let mut msg = TextComponent::new(&e); msg.modifier.color = Some(format::Color::Red); - drop(send.send(PingInfo { + let _ = send.send(PingInfo { motd: Component::Text(msg), ping: time::Duration::seconds(99999), exists: false, @@ -313,7 +295,7 @@ impl ServerList { protocol_version: 0, protocol_name: "".to_owned(), favicon: None, - })); + }); } } }); @@ -323,140 +305,129 @@ impl ServerList { 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(); + let logo = ui::logo::Logo::new(renderer.resources.clone(), ui_container); // 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()), - move |_, _| { - *nr.borrow_mut() = true; - }); - elements.add(re); - elements.add(tre); + let refresh = ui::ButtonBuilder::new() + .position(300.0, -50.0 - 15.0) + .size(100.0, 30.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .draw_index(2) + .create(ui_container); + { + let mut refresh = refresh.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Refresh") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *refresh); + refresh.add_text(txt); + let nr = self.needs_reload.clone(); + refresh.add_click_func(move |_, _| { + *nr.borrow_mut() = true; + true + }) + } // 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()), |game, _|{ - game.screen_sys.replace_screen(Box::new(super::edit_server::EditServerEntry::new( - None - ))); - }); - elements.add(re); - elements.add(tre); + let add = ui::ButtonBuilder::new() + .position(200.0, -50.0 - 15.0) + .size(100.0, 30.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .draw_index(2) + .create(ui_container); + { + let mut add = add.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Add") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *add); + add.add_text(txt); + add.add_click_func(move |_, game| { + game.screen_sys.replace_screen(Box::new(super::edit_server::EditServerEntry::new( + None + ))); + true + }) + } // Options menu - let mut options = ui::Button::new(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, | game, _ | { - game.screen_sys.add_screen(Box::new(super::SettingsMenu::new(game.vars.clone(), false))); - }); - elements.add(re); - elements.add(ui_container.add(cog)); + let options = ui::ButtonBuilder::new() + .position(5.0, 25.0) + .size(40.0, 40.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Right) + .create(ui_container); + { + let mut options = options.borrow_mut(); + ui::ImageBuilder::new() + .texture("steven:gui/cog") + .position(0.0, 0.0) + .size(40.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *options); + options.add_click_func(|_, game| { + game.screen_sys.add_screen(Box::new(super::SettingsMenu::new(game.vars.clone(), false))); + true + }); + } // 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)); + let disclaimer = ui::TextBuilder::new() + .text("Not affiliated with Mojang/Minecraft") + .position(5.0, 5.0) + .colour((255, 200, 200, 255)) + .alignment(ui::VAttach::Bottom, ui::HAttach::Right) + .create(ui_container); // 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)); - } + let disconnected = if let Some(ref disconnect_reason) = self.disconnect_reason { + let (width, height) = ui::Formatted::compute_size(renderer, disconnect_reason, 600.0); + let background = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, 3.0) + .size(width.max(renderer.ui.size_of_string("Disconnected")) + 4.0, height + 4.0 + 16.0) + .colour((0, 0, 0, 100)) + .alignment(ui::VAttach::Top, ui::HAttach::Center) + .create(ui_container); + ui::TextBuilder::new() + .text("Disconnected") + .position(0.0, 2.0) + .colour((255, 0, 0, 255)) + .alignment(ui::VAttach::Top, ui::HAttach::Center) + .attach(&mut *background.borrow_mut()); + ui::FormattedBuilder::new() + .text(disconnect_reason.clone()) + .position(0.0, 18.0) + .max_width(600.0) + .alignment(ui::VAttach::Top, ui::HAttach::Center) + .attach(&mut *background.borrow_mut()); + Some(background) + } else { + None + }; self.elements = Some(UIElements { logo: logo, - elements: elements, servers: Vec::new(), + + _add_btn: add, + _refresh_btn: refresh, + _options_btn: options, + _disclaimer: disclaimer, + + _disconnected: disconnected, }); self.reload_server_list(renderer, ui_container); } - fn on_deactive(&mut self, renderer: &mut render::Renderer, ui_container: &mut 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); let mut tex = renderer.get_textures_ref().write().unwrap(); for server in &mut elements.servers { if let Some(ref icon) = server.icon_texture { tex.remove_dynamic(&icon); } - server.collection.remove_all(ui_container); } - elements.servers.clear(); } self.elements = None } @@ -470,18 +441,18 @@ impl super::Screen for ServerList { } let elements = self.elements.as_mut().unwrap(); - elements.logo.tick(renderer, ui_container); + elements.logo.tick(renderer); for s in &mut elements.servers { // Animate the entries { - let back = ui_container.get_mut(&s.back); - let dy = s.y - back.get_y(); + let mut back = s.back.borrow_mut(); + let dy = s.y - back.y; if dy * dy > 1.0 { - let y = back.get_y(); - back.set_y(y + delta * dy * 0.1); + let y = back.y; + back.y = y + delta * dy * 0.1; } else { - back.set_y(s.y); + back.y = s.y; } } @@ -491,45 +462,36 @@ impl super::Screen for ServerList { 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); - } + s.motd.borrow_mut().set_text(res.motd); + // 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, + }; + s.ping.borrow_mut().texture_coords.1 = y; if res.exists { { - let players = ui_container.get_mut(&s.players); + let mut players = s.players.borrow_mut(); let txt = if res.protocol_version == protocol::SUPPORTED_PROTOCOL { - players.set_g(255); - players.set_b(255); + players.colour.1 = 255; + players.colour.2 = 255; format!("{}/{}", res.online, res.max) } else { - players.set_g(85); - players.set_b(85); + players.colour.1 = 85; + players.colour.2 = 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); + players.text = txt; } + 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); + s.version.borrow_mut().set_text(msg); } if let Some(favicon) = res.favicon { let name: String = rand::thread_rng() @@ -541,16 +503,14 @@ impl super::Screen for ServerList { let icon_tex = tex.write() .unwrap() .put_dynamic(&name, favicon); - let icon = ui_container.get_mut(&s.icon); - icon.set_texture(icon_tex); + s.icon.borrow_mut().texture = icon_tex.name; } } 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)); + s.motd.borrow_mut().set_text(Component::Text(txt)); } _ => {} } diff --git a/src/screen/settings_menu.rs b/src/screen/settings_menu.rs index 566b81b..054269d 100644 --- a/src/screen/settings_menu.rs +++ b/src/screen/settings_menu.rs @@ -5,49 +5,13 @@ use settings; use std::rc::Rc; -pub fn new_submenu_button(text: &str, renderer: &mut render::Renderer, ui_container: &mut ui::Container, x: f64, y: f64) -> (ui::ElementRef, ui::ElementRef) { - let (mut btn, mut txt) = super::new_button_text(renderer, text, x, y, 300.0, 40.0); - btn.set_v_attach(ui::VAttach::Middle); - btn.set_h_attach(ui::HAttach::Center); - let ui_btn = ui_container.add(btn); - txt.set_parent(&ui_btn); - (ui_btn, ui_container.add(txt)) -} - -pub fn new_centered_button(text: &str, renderer: &mut render::Renderer, ui_container: &mut ui::Container, y: f64, vertical_attach: ui::VAttach) -> (ui::ElementRef, ui::ElementRef) { - let (mut btn, mut txt) = super::new_button_text(renderer, text, 0.0, y, 400.0, 40.0); - btn.set_v_attach(vertical_attach); - btn.set_h_attach(ui::HAttach::Center); - let ui_btn = ui_container.add(btn); - txt.set_parent(&ui_btn); - (ui_btn, ui_container.add(txt)) -} - -macro_rules! get_bool_str { - ($fmt:expr, $val:expr, $val_true:expr, $val_false:expr) => (format!($fmt, if $val { - $val_true - } else { - $val_false - }).as_ref()); - ($fmt:expr, $val:expr) => (get_bool_string!($fmt, $val, "true", "false")); -} - -macro_rules! get_matched_str { - ($fmt:expr, $val:expr, $($to_match:expr => $result:expr),*) => ( - format!($fmt, match $val { - $($to_match => $result.to_owned(), )* - _ => $val.to_string(), - }).as_ref() - ) -} - pub struct UIElements { - elements: ui::Collection, - background: ui::ElementRef, + background: ui::ImageRef, + _buttons: Vec, } pub struct SettingsMenu { - vars: Rc, + _vars: Rc, elements: Option, show_disconnect_button: bool } @@ -55,7 +19,7 @@ pub struct SettingsMenu { impl SettingsMenu { pub fn new(vars: Rc, show_disconnect_button: bool) -> SettingsMenu { SettingsMenu { - vars: vars, + _vars: vars, elements: None, show_disconnect_button: show_disconnect_button } @@ -63,77 +27,135 @@ impl SettingsMenu { } impl super::Screen for SettingsMenu { - fn on_active(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - let mut elements = ui::Collection::new(); + fn on_active(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { + let background = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, 0.0) + .size(854.0, 480.0) + .colour((0, 0, 0, 100)) + .create(ui_container); - let mut background = ui::Image::new( - render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid"), - 0.0, 0.0, 854.0, 480.0, - 0.0, 0.0, 1.0, 1.0, - 0, 0, 0 - ); - background.set_a(100); - let background = elements.add(ui_container.add(background)); + let mut buttons = vec![]; // From top and down - let (btn_audio_settings, txt_audio_settings) = new_submenu_button("Audio settings...", renderer, ui_container, -160.0, -50.0); - super::button_action(ui_container, btn_audio_settings.clone(), Some(txt_audio_settings.clone()), move |game, _| { - game.screen_sys.add_screen(Box::new(AudioSettingsMenu::new(game.vars.clone()))); - }); - elements.add(btn_audio_settings); - elements.add(txt_audio_settings); + let audio_settings = ui::ButtonBuilder::new() + .position(-160.0, -50.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut audio_settings = audio_settings.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Audio settings...") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *audio_settings); + audio_settings.add_text(txt); + audio_settings.add_click_func(|_, game| { + game.screen_sys.add_screen(Box::new(AudioSettingsMenu::new(game.vars.clone()))); + true + }); + } + buttons.push(audio_settings); - let (btn_video_settings, txt_video_settings) = new_submenu_button("Video settings...", renderer, ui_container, 160.0, -50.0); - super::button_action(ui_container, btn_video_settings.clone(), Some(txt_video_settings.clone()), move |game, _| { - game.screen_sys.add_screen(Box::new(VideoSettingsMenu::new(game.vars.clone()))); - }); - elements.add(btn_video_settings); - elements.add(txt_video_settings); + let video_settings = ui::ButtonBuilder::new() + .position(160.0, -50.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut video_settings = video_settings.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Video settings...") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *video_settings); + video_settings.add_text(txt); + video_settings.add_click_func(|_, game| { + game.screen_sys.add_screen(Box::new(VideoSettingsMenu::new(game.vars.clone()))); + true + }); + } + buttons.push(video_settings); - let (btn_controls_settings, txt_controls_settings) = new_submenu_button("Controls...", renderer, ui_container, 160.0, 0.0); - super::button_action(ui_container, btn_controls_settings.clone(), Some(txt_controls_settings.clone()), move |_, _| { - // TODO: Implement this... - }); - elements.add(btn_controls_settings); - elements.add(txt_controls_settings); + let controls_settings = ui::ButtonBuilder::new() + .position(160.0, 0.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut controls_settings = controls_settings.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Controls...") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *controls_settings); + controls_settings.add_text(txt); + } + buttons.push(controls_settings); - let (btn_locale_settings, txt_locale_settings) = new_submenu_button("Language...", renderer, ui_container, -160.0, 0.0); - super::button_action(ui_container, btn_locale_settings.clone(), Some(txt_locale_settings.clone()), move |_, _| { - // TODO: Implement this... - }); - elements.add(btn_locale_settings); - elements.add(txt_locale_settings); + let lang_settings = ui::ButtonBuilder::new() + .position(-160.0, 0.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut lang_settings = lang_settings.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Language...") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *lang_settings); + lang_settings.add_text(txt); + } + buttons.push(lang_settings); // Center bottom items - let (btn_back_to_game, txt_back_to_game) = new_centered_button("Done", renderer, ui_container, 50.0, ui::VAttach::Bottom); - super::button_action(ui_container, btn_back_to_game.clone(), Some(txt_back_to_game.clone()), move |game, _| { - game.screen_sys.pop_screen(); - game.focused = true; - }); - elements.add(btn_back_to_game); - elements.add(txt_back_to_game); + let done_button = ui::ButtonBuilder::new() + .position(0.0, 50.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Center) + .create(ui_container); + { + let mut done_button = done_button.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Done") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *done_button); + done_button.add_text(txt); + done_button.add_click_func(|_, game| { + game.screen_sys.pop_screen(); + game.focused = true; + true + }); + } + buttons.push(done_button); if self.show_disconnect_button { - let (btn_exit_game, txt_exit_game) = new_centered_button("Disconnect", renderer, ui_container, 100.0, ui::VAttach::Bottom); - super::button_action(ui_container, btn_exit_game.clone(), Some(txt_exit_game.clone()), move |game, _| { - game.server.disconnect(None); - game.screen_sys.replace_screen(Box::new(super::ServerList::new(None))); - }); - elements.add(btn_exit_game); - elements.add(txt_exit_game); + let disconnect_button = ui::ButtonBuilder::new() + .position(0.0, 100.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Center) + .create(ui_container); + { + let mut disconnect_button = disconnect_button.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Disconnect") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *disconnect_button); + disconnect_button.add_text(txt); + disconnect_button.add_click_func(|_, game| { + game.server.disconnect(None); + game.screen_sys.replace_screen(Box::new(super::ServerList::new(None))); + true + }); + } + buttons.push(disconnect_button); } self.elements = Some(UIElements { - elements: elements, background: background, + _buttons: buttons, }); } - fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - { - let elements = self.elements.as_mut().unwrap(); - elements.elements.remove_all(ui_container); - } + fn on_deactive(&mut self, _renderer: &mut render::Renderer, _ui_container: &mut ui::Container) { self.elements = None; } @@ -142,15 +164,15 @@ impl super::Screen for SettingsMenu { let elements = self.elements.as_mut().unwrap(); { let mode = ui_container.mode; - let background = ui_container.get_mut(&elements.background); - background.set_width(match mode { + let mut background = elements.background.borrow_mut(); + background.width = match mode { ui::Mode::Unscaled(scale) => 854.0 / scale, ui::Mode::Scaled => renderer.width as f64, - }); - background.set_height(match mode { + }; + background.height = match mode { ui::Mode::Unscaled(scale) => 480.0 / scale, ui::Mode::Scaled => renderer.height as f64, - }); + }; } None } @@ -180,17 +202,15 @@ impl VideoSettingsMenu { } impl super::Screen for VideoSettingsMenu { - fn on_active(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - let mut elements = ui::Collection::new(); + fn on_active(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { + let background = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, 0.0) + .size(854.0, 480.0) + .colour((0, 0, 0, 100)) + .create(ui_container); - let mut background = ui::Image::new( - render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid"), - 0.0, 0.0, 854.0, 480.0, - 0.0, 0.0, 1.0, 1.0, - 0, 0, 0 - ); - background.set_a(100); - let background = elements.add(ui_container.add(background)); + let mut buttons = vec![]; // Load defaults let r_max_fps = *self.vars.get(settings::R_MAX_FPS); @@ -199,42 +219,92 @@ impl super::Screen for VideoSettingsMenu { // Setting buttons // TODO: Slider - let (btn_fov, txt_fov) = new_submenu_button(get_matched_str!("FOV: {}", r_fov, 90 => "Normal", 110 => "Quake pro"), renderer, ui_container, -160.0, -50.0); - elements.add(btn_fov); - elements.add(txt_fov); + let fov_setting = ui::ButtonBuilder::new() + .position(160.0, -50.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut fov_setting = fov_setting.borrow_mut(); + let txt = ui::TextBuilder::new() + .text(format!("FOV: {}", match r_fov { + 90 => "Normal".into(), + 110 => "Quake pro".into(), + val => val.to_string(), + })) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *fov_setting); + fov_setting.add_text(txt); + } + buttons.push(fov_setting); - let (btn_vsync, txt_vsync) = new_submenu_button(get_bool_str!("VSync: {}", r_vsync, "Enabled", "Disabled"), renderer, ui_container, -160.0, 0.0); - elements.add(txt_vsync.clone()); - super::button_action(ui_container, btn_vsync.clone(), Some(txt_vsync.clone()), move | game, ui_container | { - let r_vsync = !*game.vars.get(settings::R_VSYNC); - let txt_vsync = ui_container.get_mut(&txt_vsync); - txt_vsync.set_text(&game.renderer, get_bool_str!("VSync: {}", r_vsync, "Enabled", "Disabled")); - game.vars.set(settings::R_VSYNC, r_vsync); - }); - elements.add(btn_vsync); + let vsync_setting = ui::ButtonBuilder::new() + .position(-160.0, 0.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut vsync_setting = vsync_setting.borrow_mut(); + let txt = ui::TextBuilder::new() + .text(format!("VSync: {}", if r_vsync { "Enabled" } else { "Disabled" })) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *vsync_setting); + let txt_vsync = txt.clone(); + vsync_setting.add_text(txt); + vsync_setting.add_click_func(move |_, game| { + let r_vsync = !*game.vars.get(settings::R_VSYNC); + txt_vsync.borrow_mut().text = format!("VSync: {}", if r_vsync { "Enabled" } else { "Disabled" }); + game.vars.set(settings::R_VSYNC, r_vsync); + true + }); + } + buttons.push(vsync_setting); // TODO: Slider - let (btn_fps_cap, txt_fps_cap) = new_submenu_button(get_matched_str!("FPS cap: {}", r_max_fps, 0 => "Unlimited", 15 => "Potato"), renderer, ui_container, 160.0, 0.0); - elements.add(btn_fps_cap); - elements.add(txt_fps_cap); + let fps_setting = ui::ButtonBuilder::new() + .position(160.0, 0.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .create(ui_container); + { + let mut fps_setting = fps_setting.borrow_mut(); + let txt = ui::TextBuilder::new() + .text(format!("FPS cap: {}", match r_max_fps { + 0 => "Unlimited".into(), + val => val.to_string(), + })) + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *fps_setting); + fps_setting.add_text(txt); + } + buttons.push(fps_setting); - let (btn_done, txt_done) = new_centered_button("Done", renderer, ui_container, 50.0, ui::VAttach::Bottom); - super::button_action(ui_container, btn_done.clone(), Some(txt_done.clone()), move | game, _ | { - game.screen_sys.pop_screen(); - }); - elements.add(btn_done); - elements.add(txt_done); + let done_button = ui::ButtonBuilder::new() + .position(0.0, 50.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Center) + .create(ui_container); + { + let mut done_button = done_button.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Done") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *done_button); + done_button.add_text(txt); + done_button.add_click_func(|_, game| { + game.screen_sys.pop_screen(); + game.focused = true; + true + }); + } + buttons.push(done_button); self.elements = Some(UIElements { - elements: elements, background: background, + _buttons: buttons, }); } - fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - { - let elements = self.elements.as_mut().unwrap(); - elements.elements.remove_all(ui_container); - } + fn on_deactive(&mut self, _renderer: &mut render::Renderer, _ui_container: &mut ui::Container) { self.elements = None; } @@ -243,15 +313,15 @@ impl super::Screen for VideoSettingsMenu { let elements = self.elements.as_mut().unwrap(); { let mode = ui_container.mode; - let background = ui_container.get_mut(&elements.background); - background.set_width(match mode { + let mut background = elements.background.borrow_mut(); + background.width = match mode { ui::Mode::Unscaled(scale) => 854.0 / scale, ui::Mode::Scaled => renderer.width as f64, - }); - background.set_height(match mode { + }; + background.height = match mode { ui::Mode::Unscaled(scale) => 480.0 / scale, ui::Mode::Scaled => renderer.height as f64, - }); + }; } None } @@ -267,56 +337,59 @@ impl super::Screen for VideoSettingsMenu { } pub struct AudioSettingsMenu { - vars: Rc, + _vars: Rc, elements: Option } impl AudioSettingsMenu { pub fn new(vars: Rc) -> AudioSettingsMenu { AudioSettingsMenu { - vars: vars, + _vars: vars, elements: None } } } impl super::Screen for AudioSettingsMenu { - fn on_active(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - let mut elements = ui::Collection::new(); + fn on_active(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { + let background = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, 0.0) + .size(854.0, 480.0) + .colour((0, 0, 0, 100)) + .create(ui_container); - let mut background = ui::Image::new( - render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid"), - 0.0, 0.0, 854.0, 480.0, - 0.0, 0.0, 1.0, 1.0, - 0, 0, 0 - ); - background.set_a(100); - let background = elements.add(ui_container.add(background)); + let mut buttons = vec![]; - let master_volume = *self.vars.get(settings::CL_MASTER_VOLUME); + // TODO - let (btn_master_volume, txt_master_volume) = new_centered_button(&master_volume.to_string(), renderer, ui_container, -150.0, ui::VAttach::Middle); - elements.add(btn_master_volume); - elements.add(txt_master_volume); - - let (btn_done, txt_done) = new_centered_button("Done", renderer, ui_container, 50.0, ui::VAttach::Bottom); - super::button_action(ui_container, btn_done.clone(), Some(txt_done.clone()), move | game, _ | { - game.screen_sys.pop_screen(); - }); - elements.add(btn_done); - elements.add(txt_done); + let done_button = ui::ButtonBuilder::new() + .position(0.0, 50.0) + .size(300.0, 40.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Center) + .create(ui_container); + { + let mut done_button = done_button.borrow_mut(); + let txt = ui::TextBuilder::new() + .text("Done") + .alignment(ui::VAttach::Middle, ui::HAttach::Center) + .attach(&mut *done_button); + done_button.add_text(txt); + done_button.add_click_func(|_, game| { + game.screen_sys.pop_screen(); + game.focused = true; + true + }); + } + buttons.push(done_button); self.elements = Some(UIElements { - elements: elements, background: background, + _buttons: buttons, }); } - fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - { - let elements = self.elements.as_mut().unwrap(); - elements.elements.remove_all(ui_container); - } + fn on_deactive(&mut self, _renderer: &mut render::Renderer, _ui_container: &mut ui::Container) { self.elements = None; } @@ -325,15 +398,15 @@ impl super::Screen for AudioSettingsMenu { let elements = self.elements.as_mut().unwrap(); { let mode = ui_container.mode; - let background = ui_container.get_mut(&elements.background); - background.set_width(match mode { + let mut background = elements.background.borrow_mut(); + background.width = match mode { ui::Mode::Unscaled(scale) => 854.0 / scale, ui::Mode::Scaled => renderer.width as f64, - }); - background.set_height(match mode { + }; + background.height = match mode { ui::Mode::Unscaled(scale) => 480.0 / scale, ui::Mode::Scaled => renderer.height as f64, - }); + }; } None } diff --git a/src/ui/batch.rs b/src/ui/batch.rs deleted file mode 100644 index caa1678..0000000 --- a/src/ui/batch.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2016 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. - -#[derive(Clone, Copy)] -pub struct BatchRef { - index: usize, - ty: PhantomData, -} - -ui_element!(Batch { - width: f64, - height: f64, - - 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, - - elements: Vec::new(), - }) - } - - fn update(&mut self, _: &mut render::Renderer) { - } - - fn draw(&mut self, - renderer: &mut render::Renderer, - r: &Region, - width: f64, - height: f64, - delta: f64) - -> &Vec { - if self.dirty { - self.dirty = false; - self.data.clear(); - - let sx = r.w / self.width; - let sy = r.h / self.height; - - for e in &mut self.elements { - let reg = e.get_draw_region(sx, sy, r); - e.set_dirty(true); - self.data.extend(e.draw(renderer, ®, width, height, delta)); - } - } - &self.data - } - - 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!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); -} - -impl UIElement for Batch { - fn wrap(self) -> Element { - Element::Batch(self) - } - - fn unwrap_ref<'a>(e: &'a Element) -> &'a Batch { - match e { - &Element::Batch(ref val) => val, - _ => panic!("Incorrect type"), - } - } - - fn unwrap_ref_mut<'a>(e: &'a mut Element) -> &'a mut Batch { - match e { - &mut Element::Batch(ref mut val) => val, - _ => panic!("Incorrect type"), - } - } - - fn get_attachment(&self) -> (VAttach, HAttach) { - (self.v_attach, self.h_attach) - } - - fn get_offset(&self) -> (f64, f64) { - (self.x, self.y) - } - - fn get_size(&self) -> (f64, f64) { - (self.width, self.height) - } -} diff --git a/src/ui/button.rs b/src/ui/button.rs deleted file mode 100644 index 348e670..0000000 --- a/src/ui/button.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2016 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. -// Copyright 2016 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. - -ui_element!(Button { - width: f64, - height: f64, - disabled: bool, -}); - -impl Button { - base_impl!(); - - pub fn new(x: f64, y: f64, w: f64, h: f64) -> Button { - let mut btn = ui_create!(Button { - x: x, - y: y, - width: w, - height: h, - disabled: false, - }); - btn.add_hover_func(|_,_,_|{}); // Force hover events to be called - btn - } - - 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 { - self.dirty = false; - let sx = r.w / self.width; - let sy = r.h / self.height; - - let offset = match (self.disabled, self.hovered) { - (true, _) => 46.0, - (false, true) => 86.0, - (false, false) => 66.0, - }; - let texture = render::Renderer::get_texture(renderer.get_textures_ref(), "gui/widgets") - .relative(0.0, offset / 256.0, 200.0 / 256.0, 20.0 / 256.0); - self.data.clear(); - - self.data.extend(render::ui::UIElement::new(&texture, r.x, r.y, 4.0 * sx, 4.0 * sy, 0.0, 0.0, 2.0/200.0, 2.0/20.0).bytes(width, height)); - self.data.extend(render::ui::UIElement::new(&texture, r.x + r.w - 4.0 * sx, r.y, 4.0 * sx, 4.0 * sy, 198.0/200.0, 0.0, 2.0/200.0, 2.0/20.0).bytes(width, height)); - self.data.extend(render::ui::UIElement::new(&texture, r.x, r.y + r.h - 6.0 * sy, 4.0 * sx, 6.0 * sy, 0.0, 17.0/20.0, 2.0/200.0, 3.0/20.0).bytes(width, height)); - self.data.extend(render::ui::UIElement::new(&texture, r.x + r.w - 4.0 * sx, r.y + r.h - 6.0 * sy, 4.0 * sx, 6.0 * sy, 198.0/200.0, 17.0/20.0, 2.0/200.0, 3.0/20.0).bytes(width, height)); - - let w = ((r.w / sx)/2.0) - 4.0; - self.data.extend(render::ui::UIElement::new( - &texture.relative(2.0/200.0, 0.0, 196.0/200.0, 2.0/20.0), - r.x+4.0*sx, r.y, r.w - 8.0 * sx, 4.0 * sy, 0.0, 0.0, w/196.0, 1.0).bytes(width, height) - ); - self.data.extend(render::ui::UIElement::new( - &texture.relative(2.0/200.0, 17.0/20.0, 196.0/200.0, 3.0/20.0), - r.x+4.0*sx, r.y+r.h-6.0*sy, r.w - 8.0 * sx, 6.0 * sy, 0.0, 0.0, w/196.0, 1.0).bytes(width, height) - ); - - let h = ((r.h / sy)/2.0) - 5.0; - self.data.extend(render::ui::UIElement::new( - &texture.relative(0.0/200.0, 2.0/20.0, 2.0/200.0, 15.0/20.0), - r.x, r.y + 4.0*sy, 4.0 * sx, r.h - 10.0*sy, 0.0, 0.0, 1.0, h/16.0).bytes(width, height) - ); - self.data.extend(render::ui::UIElement::new( - &texture.relative(198.0/200.0, 2.0/20.0, 2.0/200.0, 15.0/20.0), - r.x+r.w - 4.0 * sx, r.y + 4.0*sy, 4.0 * sx, r.h - 10.0*sy, 0.0, 0.0, 1.0, h/16.0).bytes(width, height) - ); - - - self.data.extend(render::ui::UIElement::new( - &texture.relative(2.0/200.0, 2.0/20.0, 196.0/200.0, 15.0/20.0), - r.x+4.0*sx, r.y+4.0*sy, r.w - 8.0 * sx, r.h - 10.0 * sy, 0.0, 0.0, w/196.0, h/16.0).bytes(width, height) - ); - } - &self.data - } - - lazy_field!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); - lazy_field!(disabled, bool, is_disabled, set_disabled); - -} - -impl UIElement for Button { - fn wrap(self) -> Element { - Element::Button(self) - } - - fn unwrap_ref<'a>(e: &'a Element) -> &'a Button { - match e { - &Element::Button(ref val) => val, - _ => panic!("Incorrect type"), - } - } - - fn unwrap_ref_mut<'a>(e: &'a mut Element) -> &'a mut Button { - match e { - &mut Element::Button(ref mut val) => val, - _ => panic!("Incorrect type"), - } - } - - fn get_attachment(&self) -> (VAttach, HAttach) { - (self.v_attach, self.h_attach) - } - - fn get_offset(&self) -> (f64, f64) { - (self.x, self.y) - } - - fn get_size(&self) -> (f64, f64) { - (self.width, self.height) - } -} diff --git a/src/ui/formatted.rs b/src/ui/formatted.rs deleted file mode 100644 index 24f5ce7..0000000 --- a/src/ui/formatted.rs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2016 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. - -ui_element!(Formatted { - val: format::Component, - width: f64, - height: f64, - scale_x: f64, - scale_y: f64, - - text: Vec, - max_width: f64, - lines: usize, -}); - -impl Formatted { - base_impl!(); - - 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, - scale_x: 1.0, - scale_y: 1.0, - - text: Vec::new(), - max_width: -1.0, - 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 = ui_create!(Formatted { - val: val, - x: x, - y: y, - width: 0.0, - height: 18.0, - scale_x: 1.0, - scale_y: 1.0, - - text: Vec::new(), - max_width: max_width, - 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 init_component(&mut self, renderer: &mut render::Renderer) { - self.text.clear(); - let mut state = FormatState { - lines: 0, - width: 0.0, - offset: 0.0, - text: Vec::new(), - max_width: self.max_width, - renderer: &renderer, - }; - state.build(&self.val, format::Color::White); - self.height = (state.lines + 1) as f64 * 18.0; - self.width = state.width; - self.lines = state.lines; - self.text = state.text; - self.dirty = true; - } - - fn update(&mut self, renderer: &mut render::Renderer) { - self.init_component(renderer); - } - - fn draw(&mut self, - renderer: &mut render::Renderer, - r: &Region, - width: f64, - height: f64, - delta: f64) - -> &Vec { - if self.dirty { - self.dirty = false; - self.data.clear(); - let sx = r.w / self.width; - let sy = r.h / self.height; - - for e in &mut self.text { - let reg = e.get_draw_region(sx, sy, r); - e.set_dirty(true); - self.data.extend(e.draw(renderer, ®, width, height, delta)); - } - } - &self.data - } - - lazy_field!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); - lazy_field!(scale_x, f64, get_scale_x, set_scale_x); - lazy_field!(scale_y, f64, get_scale_y, set_scale_y); - -} - -impl UIElement for Formatted { - fn wrap(self) -> Element { - Element::Formatted(self) - } - - fn unwrap_ref<'a>(e: &'a Element) -> &'a Formatted { - match e { - &Element::Formatted(ref val) => val, - _ => panic!("Incorrect type"), - } - } - - fn unwrap_ref_mut<'a>(e: &'a mut Element) -> &'a mut Formatted { - match e { - &mut Element::Formatted(ref mut val) => val, - _ => panic!("Incorrect type"), - } - } - - fn get_attachment(&self) -> (VAttach, HAttach) { - (self.v_attach, self.h_attach) - } - - fn get_offset(&self) -> (f64, f64) { - (self.x, self.y) - } - - fn get_size(&self) -> (f64, f64) { - ((self.width + 2.0) * self.scale_x, self.height * self.scale_y) - } -} - -struct FormatState<'a> { - max_width: f64, - lines: usize, - offset: f64, - width: f64, - text: Vec, - renderer: &'a render::Renderer, -} - -impl <'a> FormatState<'a> { - fn build(&mut self, c: &format::Component, color: format::Color) { - match c { - &format::Component::Text(ref txt) => { - let col = FormatState::get_color(&txt.modifier, color); - self.append_text(&txt.text, col); - let modi = &txt.modifier; - if let Some(ref extra) = modi.extra { - for e in extra { - self.build(e, col); - } - } - } - } - } - - fn append_text(&mut self, txt: &str, color: format::Color) { - let mut width = 0.0; - let mut last = 0; - 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(); - let text = Text::new(self.renderer, - &txt[last..i], - self.offset, - (self.lines * 18 + 1) as f64, - rr, - gg, - bb); - self.text.push(text.wrap()); - last = i; - if c == '\n' { - last += 1; - } - self.offset = 0.0; - self.lines += 1; - width = 0.0; - } - width += size; - if self.offset + width > self.width { - self.width = self.offset + width; - } - } - - if last != txt.len() { - let (rr, gg, bb) = color.to_rgb(); - let text = Text::new(self.renderer, - &txt[last..], - self.offset, - (self.lines * 18 + 1) as f64, - rr, - gg, - bb); - self.offset += text.width + 4.0; // TODO Why is this 4 not 2? - self.text.push(text.wrap()); - if self.offset > self.width { - self.width = self.offset; - } - } - } - - fn get_color(modi: &format::Modifier, color: format::Color) -> format::Color { - modi.color.unwrap_or(color) - } -} diff --git a/src/ui/image.rs b/src/ui/image.rs deleted file mode 100644 index 1eb3f11..0000000 --- a/src/ui/image.rs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2016 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. - -ui_element!(Image { - texture: render::Texture, - width: f64, - height: f64, - - t_x: f64, - t_y: f64, - t_width: f64, - t_height: f64, - - r: u8, - g: u8, - b: u8, - a: u8, -}); - -impl Image { - base_impl!(); - - 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, - x: x, - y: y, - width: w, - height: h, - - t_x: t_x, - t_y: t_y, - t_width: t_width, - t_height: t_height, - - r: r, - g: g, - b: b, - a: 255, - }) - } - - fn update(&mut self, _: &mut render::Renderer) { - } - - fn draw(&mut self, - renderer: &mut render::Renderer, - r: &Region, - width: f64, - height: f64, - _: f64) - -> &Vec { - if self.dirty { - self.dirty = false; - self.texture = renderer.check_texture(self.texture.clone()); - let mut e = render::ui::UIElement::new(&self.texture, - r.x, - r.y, - r.w, - r.h, - self.t_x, - self.t_y, - self.t_width, - self.t_height); - e.r = self.r; - e.g = self.g; - e.b = self.b; - e.a = self.a; - e.layer = self.layer; - self.data = e.bytes(width, height); - } - &self.data - } - - pub fn get_texture(&self) -> render::Texture { - self.texture.clone() - } - - pub fn set_texture(&mut self, val: render::Texture) { - self.texture = val; - self.dirty = true; - } - - lazy_field!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); - - 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_width, f64, get_t_width, set_t_width); - lazy_field!(t_height, f64, get_t_height, set_t_height); - - lazy_field!(r, u8, get_r, set_r); - lazy_field!(g, u8, get_g, set_g); - lazy_field!(b, u8, get_b, set_b); - lazy_field!(a, u8, get_a, set_a); -} - -impl UIElement for Image { - fn wrap(self) -> Element { - Element::Image(self) - } - - fn unwrap_ref<'a>(e: &'a Element) -> &'a Image { - match e { - &Element::Image(ref val) => val, - _ => panic!("Incorrect type"), - } - } - - fn unwrap_ref_mut<'a>(e: &'a mut Element) -> &'a mut Image { - match e { - &mut Element::Image(ref mut val) => val, - _ => panic!("Incorrect type"), - } - } - - fn get_attachment(&self) -> (VAttach, HAttach) { - (self.v_attach, self.h_attach) - } - - fn get_offset(&self) -> (f64, f64) { - (self.x, self.y) - } - - fn get_size(&self) -> (f64, f64) { - (self.width, self.height) - } -} diff --git a/src/ui/logo.rs b/src/ui/logo.rs index 8efa10c..af6321e 100644 --- a/src/ui/logo.rs +++ b/src/ui/logo.rs @@ -9,12 +9,10 @@ use rand; use rand::Rng; pub struct Logo { - resources: Arc>, + _shadow: ui::BatchRef, + _layer0: ui::BatchRef, - shadow: ui::ElementRef, - layer0: ui::ElementRef, - - text: ui::ElementRef, + text: ui::TextRef, text_base_scale: f64, text_orig_x: f64, text_index: isize, @@ -23,40 +21,27 @@ pub struct Logo { impl Logo { pub fn new(resources: Arc>, - renderer: &mut render::Renderer, ui_container: &mut ui::Container) -> Logo { - let mut l = Logo { - resources: resources, - shadow: Default::default(), - layer0: Default::default(), - text: Default::default(), - text_base_scale: 0.0, - text_orig_x: 0.0, - text_index: -1, - text_strings: Vec::new(), - }; - l.init(renderer, ui_container); - l - } - - fn init(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) { let logo_str = { - let res = self.resources.read().unwrap(); + let res = resources.read().unwrap(); let mut logo = res.open("steven", "logo/logo.txt").unwrap(); let mut logo_str = String::new(); logo.read_to_string(&mut logo_str).unwrap(); logo_str }; - let solid = render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid"); - let front = render::Renderer::get_texture(renderer.get_textures_ref(), "blocks/planks_oak"); - - let mut shadow_batch = ui::Batch::new(0.0, 8.0, 100.0, 100.0); - let mut layer0 = ui::Batch::new(0.0, 8.0, 100.0, 100.0); - - shadow_batch.set_h_attach(ui::HAttach::Center); - layer0.set_h_attach(ui::HAttach::Center); + let shadow_batch = ui::BatchBuilder::new() + .position(0.0, 8.0) + .size(100.0, 100.0) + .alignment(ui::VAttach::Top, ui::HAttach::Center) + .create(ui_container); + let layer0 = ui::BatchBuilder::new() + .position(0.0, 8.0) + .size(100.0, 100.0) + .draw_index(1) + .alignment(ui::VAttach::Top, ui::HAttach::Center) + .create(ui_container); let mut row = 0; for line in logo_str.lines() { @@ -74,96 +59,96 @@ impl Logo { } else { (170, 170, 170) }; - let mut shadow = ui::Image::new(solid.clone(), - (x + 2) as f64, - (y + 4) as f64, - 4.0, - 8.0, - 0.0, - 0.0, - 1.0, - 1.0, - 0, - 0, - 0); - shadow.set_a(100); - shadow_batch.add(shadow); + ui::ImageBuilder::new() + .texture("steven:solid") + .position((x + 2) as f64, (y + 4) as f64) + .size(4.0, 8.0) + .colour((0, 0, 0, 100)) + .attach(&mut *shadow_batch.borrow_mut()); - let img = ui::Image::new(front.clone(), - x as f64, - y as f64, - 4.0, - 8.0, - (x % 16) as f64 / 16.0, - (y % 16) as f64 / 16.0, - 4.0 / 16.0, - 8.0 / 16.0, - r, - g, - b); - layer0.add(img); + ui::ImageBuilder::new() + .texture("minecraft:blocks/planks_oak") + .position(x as f64, y as f64) + .size(4.0, 8.0) + .texture_coords(( + (x % 16) as f64 / 16.0, (y % 16) as f64 / 16.0, + 4.0 / 16.0, 8.0 / 16.0 + )) + .colour((r, g, b, 255)) + .attach(&mut *layer0.borrow_mut()); let width = (x + 4) as f64; - if shadow_batch.get_width() < width { - shadow_batch.set_width(width); - layer0.set_width(width); + if shadow_batch.borrow().width < width { + shadow_batch.borrow_mut().width = width; + layer0.borrow_mut().width = width; } } row += 1; } + + shadow_batch.borrow_mut().height = row as f64 * 8.0; + layer0.borrow_mut().height = row as f64 * 8.0; + + let mut text_strings = vec![]; { - let res = self.resources.read().unwrap(); + let res = resources.read().unwrap(); let mut splashes = res.open_all("minecraft", "texts/splashes.txt"); for file in &mut splashes { let mut texts = String::new(); file.read_to_string(&mut texts).unwrap(); for line in texts.lines() { - self.text_strings.push(line.to_owned().replace("\r", "")); + text_strings.push(line.to_owned().replace("\r", "")); } } let mut r: rand::XorShiftRng = rand::SeedableRng::from_seed([45, 64, 32, 12]); - r.shuffle(&mut self.text_strings[..]); + r.shuffle(&mut text_strings[..]); } - shadow_batch.set_height(row as f64 * 8.0); - layer0.set_height(row as f64 * 8.0); + let txt = ui::TextBuilder::new() + .text("") + .position(0.0, -8.0) + .colour((255, 255, 0, 255)) + .rotation(-consts::PI / 8.0) + .alignment(ui::VAttach::Bottom, ui::HAttach::Right) + .draw_index(1) + .create(&mut *layer0.borrow_mut()); - self.shadow = ui_container.add(shadow_batch); - self.layer0 = ui_container.add(layer0); - - let mut txt = ui::Text::new(renderer, "", 0.0, -8.0, 255, 255, 0); - txt.set_h_attach(ui::HAttach::Right); - txt.set_v_attach(ui::VAttach::Bottom); - txt.set_parent(&self.shadow); - txt.set_rotation(-consts::PI / 8.0); - - let width = txt.get_width(); - self.text_base_scale = 300.0 / width; - if self.text_base_scale > 1.0 { - self.text_base_scale = 1.0; + let width = txt.borrow().width; + let mut text_base_scale = 300.0 / width; + if text_base_scale > 1.0 { + text_base_scale = 1.0; + } + txt.borrow_mut().x = (-width / 2.0) * text_base_scale; + let text_orig_x = txt.borrow().x; + + Logo { + _shadow: shadow_batch, + _layer0: layer0, + text: txt, + text_base_scale: text_base_scale, + text_orig_x: text_orig_x, + text_index: -1, + text_strings: text_strings, } - txt.set_x((-width / 2.0) * self.text_base_scale); - self.text_orig_x = txt.get_x(); - self.text = ui_container.add(txt); } - pub fn tick(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) { + pub fn tick(&mut self, renderer: &mut render::Renderer) { let now = time::now().to_timespec(); // Splash text - let text = ui_container.get_mut(&self.text); let text_index = (now.sec / 15) as isize % self.text_strings.len() as isize; + let mut text = self.text.borrow_mut(); if self.text_index != text_index { self.text_index = text_index; - text.set_text(renderer, &self.text_strings[text_index as usize]); - let width = text.get_width(); + text.text = self.text_strings[text_index as usize].clone(); + let width = (renderer.ui.size_of_string(&text.text) + 2.0) * text.scale_x; self.text_base_scale = 300.0 / width; if self.text_base_scale > 1.0 { self.text_base_scale = 1.0; } - text.set_x((-width / 2.0) * self.text_base_scale); - self.text_orig_x = text.get_x(); + text.x =(-width / 2.0) * self.text_base_scale; + self.text_orig_x = text.x; } let timer = now.nsec as f64 / 1000000000.0; @@ -172,15 +157,9 @@ impl Logo { offset = 2.0 - offset; } offset = ((offset * consts::PI).cos() + 1.0) / 2.0; - text.set_scale_x((0.7 + (offset / 3.0)) * self.text_base_scale); - text.set_scale_y((0.7 + (offset / 3.0)) * self.text_base_scale); - let scale = text.get_scale_x(); - text.set_x(self.text_orig_x * scale * self.text_base_scale); - } - - pub fn remove(&self, ui_container: &mut ui::Container) { - ui_container.remove(&self.shadow); - ui_container.remove(&self.layer0); - ui_container.remove(&self.text); + text.scale_x = (0.7 + (offset / 3.0)) * self.text_base_scale; + text.scale_y = (0.7 + (offset / 3.0)) * self.text_base_scale; + let scale = text.scale_x; + text.x =self.text_orig_x * scale * self.text_base_scale; } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index a2a8d72..39343c3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -14,10 +14,8 @@ pub mod logo; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::rc::Rc; -use rand; +use std::rc::{Rc, Weak}; +use std::cell::{RefCell, RefMut}; use render; use format; use sdl2::keyboard::Keycode; @@ -25,173 +23,6 @@ use sdl2::keyboard::Keycode; const SCALED_WIDTH: f64 = 854.0; const SCALED_HEIGHT: f64 = 480.0; -pub enum Element { - Image(Image), - Batch(Batch), - Text(Text), - Formatted(Formatted), - TextBox(TextBox), - Button(Button), - None, -} - -pub type ClickFunc = Fn(&mut ::Game, &mut Container); -pub type HoverFunc = Fn(bool, &mut ::Game, &mut Container); - -macro_rules! element_impl { - ($($name:ident),+) => ( -impl Element { - fn can_focus(&self) -> bool { - match *self { - $( - Element::$name(ref val) => val.can_focus, - )+ - _ => unimplemented!(), - } - } - - fn is_focused(&self) -> bool { - match *self { - $( - Element::$name(ref val) => val.focused, - )+ - _ => unimplemented!(), - } - } - - fn set_focused(&mut self, f: bool) { - match *self { - $( - Element::$name(ref mut val) => val.focused = f, - )+ - _ => unimplemented!(), - } - } - - fn key_press(&mut self, game: &mut ::Game, key: Keycode, down: bool) -> Vec> { - match *self { - $( - Element::$name(ref mut val) => val.key_press(game, key, down), - )+ - _ => unimplemented!(), - } - } - - fn key_type(&mut self, game: &mut ::Game, c: char) -> Vec> { - match *self { - $( - Element::$name(ref mut val) => val.key_type(game, c), - )+ - _ => unimplemented!(), - } - } - - 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 { - $( - Element::$name(ref mut val) => { - let ret = val.hovered != new; - val.dirty = val.dirty || ret; - val.hovered = new; - ret - }, - )+ - _ => unimplemented!(), - } - } - - fn should_draw(&self) -> bool { - match *self { - $( - Element::$name(ref val) => val.should_draw, - )+ - _ => unimplemented!(), - } - } - - fn get_parent(&self) -> Option { - match *self { - $( - Element::$name(ref val) => val.parent, - )+ - _ => unimplemented!(), - } - } - - fn is_dirty(&self) -> bool { - match *self { - $( - Element::$name(ref val) => val.dirty, - )+ - _ => unimplemented!(), - } - } - - fn set_dirty(&mut self, dirty: bool) { - match *self { - $( - Element::$name(ref mut val) => val.dirty = dirty, - )+ - _ => unimplemented!(), - } - } - - fn update(&mut self, renderer: &mut render::Renderer) { - match *self { - $( - Element::$name(ref mut val) => val.update(renderer), - )+ - _ => unimplemented!(), - } - } - - fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, width: f64, height: f64, delta: f64) -> &Vec { - match *self { - $( - Element::$name(ref mut val) => val.draw(renderer, r, width, height, delta), - )+ - _ => unimplemented!(), - } - } - - fn get_draw_region(&self, sw: f64, sh: f64, super_region: &Region) -> Region { - match *self { - $( - Element::$name(ref val) => Container::get_draw_region_raw(val, sw, sh, super_region), - )+ - _ => unimplemented!(), - } - } -} - ) -} - -element_impl!( - Image, - Batch, - Text, - Formatted, - TextBox, - Button -); #[derive(Clone, Copy, PartialEq)] pub enum Mode { @@ -228,60 +59,201 @@ impl Region { } } -/// Reference to an element currently attached to a -/// container. -#[derive(Copy)] -pub struct ElementRef { - inner: ElementRefInner, - ty: PhantomData, -} - -impl Clone for ElementRef { - fn clone(&self) -> Self { - ElementRef { - inner: self.inner, - ty: PhantomData, +macro_rules! define_elements { + ( + $($name:ident,)* + ) => ( + #[doc(hidden)] + pub enum Element { + $($name(Rc>),)* } - } -} -#[derive(Hash, PartialEq, Eq, Clone, Copy)] -struct ElementRefInner { - index: usize, -} + impl Element { + fn get_draw_index(&self) -> isize { + match *self { + $( + Element::$name(ref inner) => inner.borrow().draw_index, + )* + } + } -impl Default for ElementRef { - fn default() -> Self { - ElementRef { - inner: ElementRefInner { index: 0 }, - ty: PhantomData, + fn is_unused(&self) -> bool { + match *self { + $( + Element::$name(ref inner) => Rc::would_unwrap(inner), + )* + } + } + + fn tick(&self, renderer: &mut render::Renderer) { + match *self { + $( + Element::$name(ref inner) => { + let mut el = inner.borrow_mut(); + el.tick(renderer); + }, + )* + } + } + + fn draw(&self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> RefMut<[u8]> { + match *self { + $( + Element::$name(ref inner) => { + let el = inner.borrow_mut(); + RefMut::map(el, |el| el.draw(renderer, r, sw, sh, width, height, delta)) + }, + )* + } + } + + fn check_rebuild(&self) -> bool { + match *self { + $( + Element::$name(ref inner) => { + let el = inner.borrow(); + el.check_rebuild() + }, + )* + } + } + + fn force_rebuild(&self) { + match *self { + $( + Element::$name(ref inner) => { + let mut el = inner.borrow_mut(); + el.needs_rebuild = true; + }, + )* + } + } + + fn get_size(&self) -> (f64, f64) { + match *self { + $( + Element::$name(ref inner) => { + let el = inner.borrow(); + el.get_size() + }, + )* + } + } + + fn get_position(&self) -> (f64, f64) { + match *self { + $( + Element::$name(ref inner) => { + let el = inner.borrow(); + (el.x, el.y) + }, + )* + } + } + fn get_attachment(&self) -> (VAttach, HAttach) { + match *self { + $( + Element::$name(ref inner) => { + let el = inner.borrow(); + (el.v_attach, el.h_attach) + }, + )* + } + } + + fn hover_at(&self, r: &Region, game: &mut ::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool { + match *self { + $( + Element::$name(ref inner) => { + let mut el = inner.borrow_mut(); + el.hover_at(r, game, mx, my, sw, sh) + }, + )* + } + } + + fn click_at(&self, r: &Region, game: &mut ::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool { + match *self { + $( + Element::$name(ref inner) => { + let mut el = inner.borrow_mut(); + el.click_at(r, game, mx, my, sw, sh) + }, + )* + } + } + + fn key_press(&self, game: &mut ::Game, key: Keycode, down: bool) { + match *self { + $( + Element::$name(ref inner) => { + let mut el = inner.borrow_mut(); + el.key_press(game, key, down); + }, + )* + } + } + fn key_type(&self, game: &mut ::Game, c: char) { + match *self { + $( + Element::$name(ref inner) => { + let mut el = inner.borrow_mut(); + el.key_type(game, c); + }, + )* + } + } + + fn is_focused(&self) -> bool { + match *self { + $( + Element::$name(ref inner) => { + let el = inner.borrow(); + el.focused + }, + )* + } + } + + fn set_focused(&self, val: bool) { + match *self { + $( + Element::$name(ref inner) => { + let mut el = inner.borrow_mut(); + el.focused = val; + }, + )* + } + } } - } -} -/// Allows for easy cleanup -pub struct Collection { - elements: Vec, -} - -impl Collection { - pub fn new() -> Collection { - Collection { elements: Vec::new() } - } - - pub fn add(&mut self, element: ElementRef) -> ElementRef { - self.elements.push(element.inner); - element - } - - pub fn remove_all(&mut self, container: &mut Container) { - for e in &self.elements { - container.remove_raw(e); + #[doc(hidden)] + enum WeakElement { + $($name(Weak>),)* } - self.elements.clear(); - } + + impl WeakElement { + fn upgrade(&self) -> Option { + match *self { + $( + WeakElement::$name(ref inner) => { + inner.upgrade().map(|v| Element::$name(v)) + }, + )* + } + } + } + ) } +define_elements! { + Image, + Batch, + Text, + Formatted, + Button, + TextBox, +} const SCREEN: Region = Region { x: 0.0, y: 0.0, @@ -289,12 +261,16 @@ const SCREEN: Region = Region { h: SCALED_HEIGHT, }; +pub trait ElementHolder { + fn add(&mut self, el: Element, auto_free: bool); +} + pub struct Container { + elements: Vec, + focusable_elements: Vec, + pub mode: Mode, last_mode: Mode, - elements: HashMap, - // We need the order - elements_list: Vec, version: usize, last_sw: f64, @@ -306,11 +282,13 @@ pub struct Container { impl Container { pub fn new() -> Container { Container { + elements: Vec::new(), + focusable_elements: Vec::new(), + mode: Mode::Scaled, last_mode: Mode::Scaled, - elements: HashMap::new(), - elements_list: Vec::new(), version: 0xFFFF, + last_sw: 0.0, last_sh: 0.0, last_width: 0.0, @@ -318,40 +296,6 @@ impl Container { } } - pub fn add(&mut self, e: T) -> ElementRef { - let mut r = ElementRefInner { index: rand::random() }; - while self.elements.contains_key(&r) { - r = ElementRefInner { index: rand::random() }; - } - self.elements.insert(r, e.wrap()); - self.elements_list.push(r); - ElementRef { - inner: r, - ty: PhantomData, - } - } - - pub fn get(&self, r: &ElementRef) -> &T { - T::unwrap_ref(self.elements.get(&r.inner).unwrap()) - } - - pub fn get_mut(&mut self, r: &ElementRef) -> &mut T { - T::unwrap_ref_mut(self.elements.get_mut(&r.inner).unwrap()) - } - - pub fn remove(&mut self, r: &ElementRef) { - self.remove_raw(&r.inner); - } - - fn remove_raw(&mut self, r: &ElementRefInner) { - self.elements.remove(&r); - self.elements_list - .iter() - .position(|&e| e.index == r.index) - .map(|e| self.elements_list.remove(e)) - .unwrap(); - } - pub fn tick(&mut self, renderer: &mut render::Renderer, delta: f64, width: f64, height: f64) { let (sw, sh) = match self.mode { Mode::Scaled => (SCALED_WIDTH / width, SCALED_HEIGHT / height), @@ -365,81 +309,33 @@ impl Container { self.last_width = width; self.last_height = height; self.last_mode = self.mode; - for (_, e) in &mut self.elements { - e.set_dirty(true); - if self.version != renderer.ui.version { - e.update(renderer); - } + for e in &self.elements { + e.force_rebuild(); } self.version = renderer.ui.version; } - // Try to make sure we have a focus - if !self.elements.iter().any(|(_, ref e)| e.is_focused()) { - self.cycle_focus(); + // Drop elements with no refs + self.elements.retain(|v| !v.is_unused()); + // Drop focusable elements that no longer exist + self.focusable_elements.retain(|v| v.upgrade().is_some()); + + // If we don't have an element focused, focus one + if !self.focusable_elements.is_empty() + && !self.focusable_elements.iter() + .flat_map(|v| v.upgrade()) + .any(|v| v.is_focused()) { + self.cycle_focus() } - // Borrow rules seem to prevent us from doing this in the first pass - // so we split it. - let regions = self.collect_elements(sw, sh); - for re in &self.elements_list { - let mut e = self.elements.get_mut(re).unwrap(); - if !e.should_draw() { - continue; - } - if let Some(&(ref r, ref dirty)) = regions.get(re) { - e.set_dirty(*dirty); - let data = e.draw(renderer, r, width, height, delta); - renderer.ui.add_bytes(data); - } + for e in &self.elements { + e.tick(renderer); } - } - - fn collect_elements(&self, sw: f64, sh: f64) -> HashMap { - let mut map = HashMap::new(); - for (re, e) in &self.elements { - if !e.should_draw() { - continue; - } - let r = self.get_draw_region(e, sw, sh); + for e in &self.elements { + let r = Self::compute_draw_region(e, sw, sh, &SCREEN); if r.intersects(&SCREEN) { - // Mark this as dirty if any of its - // parents are dirty too. - let mut dirty = e.is_dirty(); - let mut parent = e.get_parent(); - while !dirty && parent.is_some() { - let p = self.elements.get(&parent.unwrap()).unwrap(); - dirty = p.is_dirty(); - parent = p.get_parent(); - } - map.insert(*re, (r, dirty)); - } - } - map - } - - pub fn click_at(&mut self, game: &mut ::Game, 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(game, self); + let data = e.draw(renderer, &r, sw, sh, width, height, delta); + renderer.ui.add_bytes(&data); } } } @@ -451,28 +347,52 @@ impl Container { }; 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 e in &self.elements { + let r = Self::compute_draw_region(e, sw, sh, &SCREEN); + e.hover_at(&r, game, mx, my, sw, sh); + } + } + + pub fn click_at(&mut self, game: &mut ::Game, 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; + + for e in &self.elements { + let r = Self::compute_draw_region(e, sw, sh, &SCREEN); + if mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h { + e.click_at(&r, game, mx, my, sw, sh); } } - 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, game, self); - } - } + } + + fn add_focusable(&mut self, el: WeakElement) { + self.focusable_elements.push(el); + } + + pub fn cycle_focus(&mut self) { + if self.focusable_elements.is_empty() { + return; } + let focusables = self.focusable_elements.iter() + .flat_map(|v| v.upgrade()) + .collect::>(); + + // Find the last focused element if there is one + let last_focus = focusables.iter() + .position(|v| v.is_focused()); + let next_focus = last_focus.map_or(0, |v| v + 1) % focusables.len(); + + // Clear the last focus + if let Some(focus) = last_focus { + focusables[focus].set_focused(false); + } + + focusables[next_focus].set_focused(true); } pub fn key_press(&mut self, game: &mut ::Game, key: Keycode, down: bool) { @@ -482,16 +402,10 @@ impl Container { } return; } - let mut callbacks = None; - for (_, e) in &mut self.elements { - if e.is_focused() { - callbacks = Some(e.key_press(game, key, down)); - break; - } - } - if let Some(callbacks) = callbacks { - for cb in callbacks { - cb(game, self); + for el in self.focusable_elements.iter() + .flat_map(|v| v.upgrade()) { + if el.is_focused() { + el.key_press(game, key, down); } } } @@ -500,78 +414,26 @@ impl Container { if c < ' ' { return; } - let mut callbacks = None; - for (_, e) in &mut self.elements { - if e.is_focused() { - callbacks = Some(e.key_type(game, c)); - break; - } - } - if let Some(callbacks) = callbacks { - for cb in callbacks { - cb(game, self); + for el in self.focusable_elements.iter() + .flat_map(|v| v.upgrade()) { + if el.is_focused() { + el.key_type(game, c); } } } - pub fn set_focused(&mut self, r: &ElementRef) { - for (_, e) in &mut self.elements { - e.set_focused(false); - } - self.elements.get_mut(&r.inner).unwrap().set_focused(true); - } - - pub fn cycle_focus(&mut self) { - if self.elements_list.is_empty() { - return; - } - // Find the last focused element - let i = self.elements_list.iter() - .map(|v| self.elements.get(v).unwrap()) - .position(|v| v.is_focused()); - let mut current = i.map_or(0, |v| v + 1) % self.elements_list.len(); - - // Clear the old focus - if let Some(pos) = i { - let r = self.elements_list[pos]; - self.elements.get_mut(&r).unwrap().set_focused(false); - } - - let mut limit = 0; - while limit < self.elements_list.len() { - let r = self.elements_list[current]; - let e = self.elements.get_mut(&r).unwrap(); - if e.can_focus() { - e.set_focused(true); - return; - } - - limit += 1; - current += 1; - current %= self.elements_list.len(); - } - } - - 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), - None => SCREEN, - }; - e.get_draw_region(sw, sh, &super_region) - } - - fn get_draw_region_raw(e: &T, sw: f64, sh: f64, super_region: &Region) -> Region { + fn compute_draw_region(el: &Element, sw: f64, sh: f64, super_region: &Region) -> Region { let mut r = Region { x: 0.0, y: 0.0, w: 0.0, h: 0.0, }; - let (w, h) = e.get_size(); - let (ox, oy) = e.get_offset(); + let (w, h) = el.get_size(); + let (ox, oy) = el.get_position(); r.w = w * sw; r.h = h * sh; - let (v_attach, h_attach) = e.get_attachment(); + let (v_attach, h_attach) = el.get_attachment(); match h_attach { HAttach::Left => r.x = ox * sw, HAttach::Center => r.x = (super_region.w / 2.0) - (r.w / 2.0) + ox * sw, @@ -588,122 +450,948 @@ impl Container { } } -pub trait UIElement { - fn wrap(self) -> Element; - fn unwrap_ref(&Element) -> &Self; - fn unwrap_ref_mut(&mut Element) -> &mut Self; - - fn key_press(&mut self, _game: &mut ::Game, _key: Keycode, _down: bool) -> Vec> { - vec![] +impl ElementHolder for Container { + fn add(&mut self, el: Element, auto_free: bool) { + if !auto_free { + panic!("Auto free elements are not allowed on root"); + } + self.elements.push(el); + self.elements.sort_by_key(|v| v.get_draw_index()); } - - fn key_type(&mut self, _game: &mut ::Game, _c: char) -> Vec> { - vec![] - } - - fn get_attachment(&self) -> (VAttach, HAttach); - fn get_offset(&self) -> (f64, f64); - fn get_size(&self) -> (f64, f64); } -macro_rules! lazy_field { - ($name:ident, $t:ty, $get:ident, $set:ident) => ( - pub fn $get(&self) -> $t { - self.$name +trait UIElement { + fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8]; + fn get_size(&self) -> (f64, f64); + fn is_dirty(&self) -> bool; + fn post_init(_: Rc>) {} + fn key_press(&mut self, _game: &mut ::Game, _key: Keycode, _down: bool) {} + fn key_type(&mut self, _game: &mut ::Game, _c: char) {} + fn tick(&mut self, renderer: &mut render::Renderer); +} + +macro_rules! element { + ( + ref $nameref:ident + pub struct $name:ident { + $(pub $pfield_name:ident : $pfield_type:ty,)* + $(priv $field_name:ident : $field_type:ty,)* } - pub fn $set(&mut self, val: $t) { - if self.$name != val { - self.$name = val; - self.dirty = true; + builder $builder:ident { + $(hardcode $hname:ident = $hval:expr,)* + $(simple $sname:ident : $sty:ty,)* + $(optional $oname:ident : $oty:ty = $oval:expr,)* + $(noset $nname:ident : $nty:ty = |$bref:ident| $nval:expr,)* + } + ) => ( + pub struct $name { + $(pub $pfield_name : $pfield_type,)* + $($field_name : $field_type,)* + // Base fields + draw_index: isize, + elements: Vec<(bool, Element)>, + pub x: f64, + pub y: f64, + pub v_attach: VAttach, + pub h_attach: HAttach, + data: Vec, + needs_rebuild: bool, + + hover_funcs: Vec bool>>, + hover_state: bool, + click_funcs: Vec bool>>, + + focused: bool, + + // Change checking + last_x: f64, + last_y: f64, + last_v_attach: VAttach, + last_h_attach: HAttach, + last_width: f64, + last_height: f64, + } + + pub type $nameref = Rc>; + + impl ElementHolder for $name { + fn add(&mut self, el: Element, auto_free: bool) { + self.elements.push((auto_free, el)); + self.elements.sort_by_key(|v| v.1.get_draw_index()); + } + } + + impl $name { + fn check_rebuild(&self) -> bool { + if self.needs_rebuild { + return true; + } + // Check for changes that would cause child + // elements to need an update + let (w, h) = self.get_size(); + if self.last_x != self.x || self.last_y != self.y + || self.last_width != w || self.last_height != h + || self.last_v_attach != self.v_attach || self.last_h_attach != self.h_attach { + return true; + } + if self.is_dirty() { + return true; + } + for e in &self.elements { + if e.1.check_rebuild() { + return true; + } + } + false + } + + fn super_draw(&mut self, renderer: &mut render::Renderer, super_region: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) { + if !self.needs_rebuild { + let (w, h) = self.get_size(); + self.needs_rebuild = self.last_x != self.x || self.last_y != self.y + || self.last_width != w || self.last_height != h + || self.last_v_attach != self.v_attach || self.last_h_attach != self.h_attach; + } + self.elements.retain(|v| !v.0 || !v.1.is_unused()); + for &(_, ref e) in &self.elements { + if self.needs_rebuild { + e.force_rebuild(); + } + let r = Container::compute_draw_region(e, sw, sh, &super_region); + let data = e.draw(renderer, &r, sw, sh, width, height, delta); + self.data.extend_from_slice(&data); + } + self.needs_rebuild = false; + self.last_x = self.x; + self.last_y = self.y; + let (w, h) = self.get_size(); + self.last_width = w; + self.last_height = h; + self.last_v_attach = self.v_attach; + self.last_h_attach = self.h_attach; + } + + fn super_tick(&mut self, renderer: &mut render::Renderer) { + for &(_, ref e) in &self.elements { + e.tick(renderer); + } + } + + fn hover_at(&mut self, super_region: &Region, game: &mut ::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool { + use std::mem; + let mut handle_self = true; + for e in &self.elements { + let r = Container::compute_draw_region(&e.1, sw, sh, &super_region); + if e.1.hover_at(&r, game, mx, my, sw, sh) { + handle_self = false; + } + } + if handle_self { + let state = mx >= super_region.x && mx <= super_region.x + super_region.w && my >= super_region.y && my <= super_region.y + super_region.h; + if state != self.hover_state { + self.hover_state = state; + let len = self.hover_funcs.len(); + let mut temp = mem::replace(&mut self.hover_funcs, Vec::with_capacity(len)); + let mut block_prop = false; + for func in &temp { + block_prop |= (func)(self, state, game); + } + self.hover_funcs.append(&mut temp); + block_prop + } else { + false + } + } else { + true // Carry up + } + } + + pub fn add_hover_func bool + 'static>(&mut self, func: F) { + self.hover_funcs.push(Box::new(func)); + } + + fn click_at(&mut self, super_region: &Region, game: &mut ::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool { + use std::mem; + let mut handle_self = true; + for e in &self.elements { + let r = Container::compute_draw_region(&e.1, sw, sh, &super_region); + if mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h { + if e.1.click_at(&r, game, mx, my, sw, sh) { + handle_self = false; + } + } + } + if handle_self { + let len = self.click_funcs.len(); + let mut temp = mem::replace(&mut self.click_funcs, Vec::with_capacity(len)); + let mut block_prop = false; + for func in &temp { + block_prop |= (func)(self, game); + } + self.click_funcs.append(&mut temp); + block_prop + } else { + true // Carry up + } + } + + pub fn add_click_func bool + 'static>(&mut self, func: F) { + self.click_funcs.push(Box::new(func)); + } + + pub fn make_focusable(this: &$nameref, container: &mut Container) { + container.add_focusable(WeakElement::$name(Rc::downgrade(&this))); + } + } + + pub struct $builder { + $( + $sname: Option<$sty>, + )* + $( + $oname: Option<$oty>, + )* + $( + $nname: Option<$nty>, + )* + // Base fields + draw_index: isize, + x: Option, + y: Option, + v_attach: Option, + h_attach: Option, + } + + impl $builder { + $( + pub fn $sname>(mut self, val: T) -> Self { + self.$sname = Some(val.into()); + self + } + )* + $( + pub fn $oname>(mut self, val: T) -> Self { + self.$oname = Some(val.into()); + self + } + )* + + // Base fields + pub fn draw_index(mut self, draw_index: isize) -> Self { + self.draw_index = draw_index; + self + } + + pub fn position(mut self, x: f64, y: f64) -> Self { + self.x = Some(x); + self.y = Some(y); + self + } + + pub fn alignment(mut self, v_attach: VAttach, h_attach: HAttach) -> Self { + self.v_attach = Some(v_attach); + self.h_attach = Some(h_attach); + self + } + + pub fn new() -> Self { + $builder { + $( + $sname: None, + )* + $( + $oname: None, + )* + $( + $nname: None, + )* + draw_index: 0, + x: None, + y: None, + v_attach: None, + h_attach: None, + } + } + + pub fn create(self, ui: &mut H) -> $nameref { + self.create_internal(ui, true) + } + + pub fn attach(self, ui: &mut H) -> $nameref { + self.create_internal(ui, false) + } + + fn create_internal(self, ui: &mut H, auto_free: bool) -> $nameref { + $( + let $nname = {let $bref = &self; $nval}; + )* + let v = Rc::new(RefCell::new($name { + $( + $hname: $hval, + )* + $( + $sname: self.$sname.expect(concat!("Missing required field ", stringify!($sname))), + )* + $( + $oname: self.$oname.unwrap_or($oval), + )* + $( + $nname: $nname, + )* + // Base fields + draw_index: self.draw_index, + elements: Vec::new(), + x: self.x.unwrap_or(0.0), + y: self.y.unwrap_or(0.0), + v_attach: self.v_attach.unwrap_or(VAttach::Top), + h_attach: self.h_attach.unwrap_or(HAttach::Left), + last_x: self.x.unwrap_or(0.0), + last_y: self.y.unwrap_or(0.0), + last_v_attach: self.v_attach.unwrap_or(VAttach::Top), + last_h_attach: self.h_attach.unwrap_or(HAttach::Left), + last_width: 0.0, + last_height: 0.0, + data: vec![], + needs_rebuild: true, + + hover_funcs: vec![], + hover_state: false, + click_funcs: vec![], + + focused: false, + })); + $name::post_init(v.clone()); + ui.add(Element::$name(v.clone()), auto_free); + v } } ) } -macro_rules! ui_element { - ( - $name:ident { - $( - $field:ident : $field_ty:ty, - )* +element! { + ref ImageRef + pub struct Image { + pub texture: String, + pub width: f64, + pub height: f64, + pub colour: (u8, u8, u8, u8), + pub texture_coords: (f64, f64, f64, f64), + priv last_texture: String, + priv last_colour: (u8, u8, u8, u8), + priv last_texture_coords: (f64, f64, f64, f64), } - ) => ( - 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, - can_focus: bool, - focused: bool, - $( - $field: $field_ty, - )* + builder ImageBuilder { + hardcode last_texture = "".into(), + hardcode last_colour = (0, 0, 0, 0), + hardcode last_texture_coords = (0.0, 0.0, 0.0, 0.0), + simple texture: String, + optional colour: (u8, u8, u8, u8) = (255, 255, 255, 255), + optional texture_coords: (f64, f64, f64, f64) = (0.0, 0.0, 1.0, 1.0), + noset width: f64 = |b| b.width.expect("Missing required field width"), + noset height: f64 = |b| b.height.expect("Missing required field height"), } - ) } -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: F) { - self.click_funcs.push(Rc::new(f)); - } - - pub fn add_hover_func(&mut self, f: F) { - self.hover_funcs.push(Rc::new(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); - ) +impl ImageBuilder { + pub fn size(mut self, width: f64, height: f64) -> Self { + self.width = Some(width); + self.height = Some(height); + self + } } -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, - can_focus: false, - focused: false, - $($field: $e,)* +impl UIElement for Image { + fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] { + if self.check_rebuild() { + self.data.clear(); + let texture = render::Renderer::get_texture(renderer.get_textures_ref(), &self.texture); + let mut element = render::ui::UIElement::new( + &texture, + r.x, r.y, r.w, r.h, + self.texture_coords.0, self.texture_coords.1, self.texture_coords.2, self.texture_coords.3, + ); + element.r = self.colour.0; + element.g = self.colour.1; + element.b = self.colour.2; + element.a = self.colour.3; + self.data.extend_from_slice(&element.bytes(width, height)); + self.super_draw(renderer, r, sw, sh, width, height, delta); + self.last_texture = self.texture.clone(); + self.last_colour = self.colour; + self.last_texture_coords = self.texture_coords; } - ) + &mut self.data + } + + fn tick(&mut self, renderer: &mut render::Renderer) { + self.super_tick(renderer); + } + + fn get_size(&self) -> (f64, f64) { + (self.width, self.height) + } + + fn is_dirty(&self) -> bool { + self.last_texture != self.texture + || self.last_colour != self.colour + || self.last_texture_coords != self.texture_coords + } } -// Include instead of mod so we can access private parts. -// Its a bit ew doing it this way but it saves us making -// fields public that should be private or having a huge -// file. -include!("image.rs"); -include!("batch.rs"); -include!("text.rs"); -include!("formatted.rs"); -include!("textbox.rs"); -include!("button.rs"); +element! { + ref BatchRef + pub struct Batch { + pub width: f64, + pub height: f64, + } + builder BatchBuilder { + noset width: f64 = |b| b.width.expect("Missing required field width"), + noset height: f64 = |b| b.height.expect("Missing required field height"), + } +} + +impl BatchBuilder { + pub fn size(mut self, width: f64, height: f64) -> Self { + self.width = Some(width); + self.height = Some(height); + self + } +} + +impl UIElement for Batch { + fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] { + if self.check_rebuild() { + self.data.clear(); + self.super_draw(renderer, r, sw, sh, width, height, delta); + } + &mut self.data + } + + fn tick(&mut self, renderer: &mut render::Renderer) { + self.super_tick(renderer); + } + + fn get_size(&self) -> (f64, f64) { + (self.width, self.height) + } + + fn is_dirty(&self) -> bool { + false + } +} + +element! { + ref TextRef + pub struct Text { + pub text: String, + pub width: f64, + pub height: f64, + pub scale_x: f64, + pub scale_y: f64, + pub colour: (u8, u8, u8, u8), + pub rotation: f64, + priv last_text: String, + priv last_scale_x: f64, + priv last_scale_y: f64, + priv last_colour: (u8, u8, u8, u8), + priv last_rotation: f64, + } + builder TextBuilder { + hardcode width = 0.0, + hardcode height = 18.0, + hardcode last_text = "".into(), + hardcode last_scale_x = 0.0, + hardcode last_scale_y = 0.0, + hardcode last_colour = (0, 0, 0, 0), + hardcode last_rotation = 0.0, + simple text: String, + optional scale_x: f64 = 1.0, + optional scale_y: f64 = 1.0, + optional colour: (u8, u8, u8, u8) = (255, 255, 255, 255), + optional rotation: f64 = 0.0, + } +} + +impl UIElement for Text { + fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] { + if self.check_rebuild() { + self.data.clear(); + + let mut text = if self.rotation == 0.0 { + renderer.ui.new_text_scaled( + &self.text, + r.x, r.y, sw * self.scale_x, sh * self.scale_y, + self.colour.0, self.colour.1, self.colour.2, + ) + } else { + let c = self.rotation.cos(); + let s = self.rotation.sin(); + let tmpx = r.w / 2.0; + let tmpy = r.h / 2.0; + let w = (tmpx * c - tmpy * s).abs(); + let h = (tmpy * c + tmpx * s).abs(); + renderer.ui.new_text_rotated( + &self.text, + r.x + w - (r.w / 2.0), r.y + h - (r.h / 2.0), + sw * self.scale_x, sh * self.scale_y, + self.rotation, + self.colour.0, self.colour.1, self.colour.2, + ) + }; + for e in &mut text.elements { + e.a = self.colour.3; + } + self.data.extend_from_slice(&text.bytes(width, height)); + self.super_draw(renderer, r, sw, sh, width, height, delta); + + self.last_text = self.text.clone(); + self.last_colour = self.colour; + self.last_scale_x = self.scale_x; + self.last_scale_y = self.scale_y; + self.last_rotation = self.rotation; + } + &mut self.data + } + + fn tick(&mut self, renderer: &mut render::Renderer) { + self.super_tick(renderer); + if self.is_dirty() { + self.width = renderer.ui.size_of_string(&self.text); + } + } + + fn get_size(&self) -> (f64, f64) { + ((self.width + 2.0) * self.scale_x, self.height * self.scale_y) + } + + fn is_dirty(&self) -> bool { + self.last_text != self.text + || self.last_colour != self.colour + || self.last_scale_x != self.scale_x + || self.last_scale_y != self.scale_y + || self.last_rotation != self.rotation + } +} + + + +element! { + ref FormattedRef + pub struct Formatted { + pub width: f64, + pub height: f64, + pub scale_x: f64, + pub scale_y: f64, + pub max_width: f64, + priv text: format::Component, + priv text_elements: Vec, + priv last_text: format::Component, + priv last_scale_x: f64, + priv last_scale_y: f64, + priv last_max_width: f64, + priv dirty: bool, + } + builder FormattedBuilder { + hardcode width = 0.0, + hardcode height = 18.0, + hardcode text_elements = vec![], + hardcode last_text = Default::default(), + hardcode last_scale_x = 0.0, + hardcode last_scale_y = 0.0, + hardcode last_max_width = -1.0, + hardcode dirty = true, + simple text: format::Component, + optional scale_x: f64 = 1.0, + optional scale_y: f64 = 1.0, + optional max_width: f64 = -1.0, + } +} + +impl UIElement for Formatted { + fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] { + if self.check_rebuild() { + self.data.clear(); + + self.elements.clear(); + { + let mut state = FormatState { + lines: 0, + width: 0.0, + offset: 0.0, + text: Vec::new(), + max_width: self.max_width, + renderer: &renderer, + }; + state.build(&self.text, format::Color::White); + self.text_elements = state.text; + } + + for e in &self.text_elements { + if self.needs_rebuild { + e.force_rebuild(); + } + let r = Container::compute_draw_region(e, sw, sh, &r); + let data = e.draw(renderer, &r, sw, sh, width, height, delta); + self.data.extend_from_slice(&data); + } + self.super_draw(renderer, r, sw, sh, width, height, delta); + + self.last_text = self.text.clone(); + self.last_scale_x = self.scale_x; + self.last_scale_y = self.scale_y; + self.last_max_width = self.max_width; + self.dirty = false; + } + &mut self.data + } + + fn tick(&mut self, renderer: &mut render::Renderer) { + self.super_tick(renderer); + if self.is_dirty() { + let (w, h) = Self::compute_size(renderer, &self.text, self.max_width); + self.width = w; + self.height = h; + } + } + + fn get_size(&self) -> (f64, f64) { + ((self.width + 2.0) * self.scale_x, self.height * self.scale_y) + } + + fn is_dirty(&self) -> bool { + self.dirty + || self.last_scale_x != self.scale_x + || self.last_scale_y != self.scale_y + || self.last_max_width != self.max_width + } +} + +impl Formatted { + pub fn set_text(&mut self, val: format::Component) { + self.text = val; + self.dirty = true; + } + + pub fn compute_size(renderer: &render::Renderer, text: &format::Component, max_width: f64) -> (f64, f64) { + let mut state = FormatState { + lines: 0, + width: 0.0, + offset: 0.0, + text: Vec::new(), + max_width: max_width, + renderer: renderer, + }; + state.build(&text, format::Color::White); + (state.width + 2.0, (state.lines + 1) as f64 * 18.0) + } +} + +struct FormatState<'a> { + max_width: f64, + lines: usize, + offset: f64, + width: f64, + text: Vec, + renderer: &'a render::Renderer, +} + + +impl <'a> ElementHolder for FormatState<'a> { + fn add(&mut self, el: Element, _: bool) { + self.text.push(el); + } +} + +impl <'a> FormatState<'a> { + fn build(&mut self, c: &format::Component, color: format::Color) { + match c { + &format::Component::Text(ref txt) => { + let col = FormatState::get_color(&txt.modifier, color); + self.append_text(&txt.text, col); + let modi = &txt.modifier; + if let Some(ref extra) = modi.extra { + for e in extra { + self.build(e, col); + } + } + } + } + } + + fn append_text(&mut self, txt: &str, color: format::Color) { + let mut width = 0.0; + let mut last = 0; + 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(); + TextBuilder::new() + .text(&txt[last..i]) + .position(self.offset, (self.lines * 18 + 1) as f64) + .colour((rr, gg, bb, 255)) + .create(self); + last = i; + if c == '\n' { + last += 1; + } + self.offset = 0.0; + self.lines += 1; + width = 0.0; + } + width += size; + if self.offset + width > self.width { + self.width = self.offset + width; + } + } + + if last != txt.len() { + let (rr, gg, bb) = color.to_rgb(); + TextBuilder::new() + .text(&txt[last..]) + .position(self.offset, (self.lines * 18 + 1) as f64) + .colour((rr, gg, bb, 255)) + .create(self); + self.offset += self.renderer.ui.size_of_string(&txt[last..]) + 2.0; + if self.offset > self.width { + self.width = self.offset; + } + } + } + + fn get_color(modi: &format::Modifier, color: format::Color) -> format::Color { + modi.color.unwrap_or(color) + } +} + +element! { + ref ButtonRef + pub struct Button { + pub disabled: bool, + pub width: f64, + pub height: f64, + priv hovered: bool, + priv last_hovered: bool, + priv last_disabled: bool, + priv texts: Vec, + } + builder ButtonBuilder { + hardcode hovered = false, + hardcode last_hovered = false, + hardcode last_disabled = false, + hardcode texts = vec![], + optional disabled: bool = false, + noset width: f64 = |b| b.width.expect("Missing required field width"), + noset height: f64 = |b| b.height.expect("Missing required field height"), + } +} + +impl ButtonBuilder { + pub fn size(mut self, width: f64, height: f64) -> Self { + self.width = Some(width); + self.height = Some(height); + self + } +} + +impl UIElement for Button { + fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] { + if self.check_rebuild() { + self.data.clear(); + let offset = match (self.disabled, self.hovered) { + (true, _) => 46.0, + (false, true) => 86.0, + (false, false) => 66.0, + }; + let texture = render::Renderer::get_texture(renderer.get_textures_ref(), "gui/widgets") + .relative(0.0, offset / 256.0, 200.0 / 256.0, 20.0 / 256.0); + + self.data.extend(render::ui::UIElement::new(&texture, r.x, r.y, 4.0 * sw, 4.0 * sh, 0.0, 0.0, 2.0/200.0, 2.0/20.0).bytes(width, height)); + self.data.extend(render::ui::UIElement::new(&texture, r.x + r.w - 4.0 * sw, r.y, 4.0 * sw, 4.0 * sh, 198.0/200.0, 0.0, 2.0/200.0, 2.0/20.0).bytes(width, height)); + self.data.extend(render::ui::UIElement::new(&texture, r.x, r.y + r.h - 6.0 * sh, 4.0 * sw, 6.0 * sh, 0.0, 17.0/20.0, 2.0/200.0, 3.0/20.0).bytes(width, height)); + self.data.extend(render::ui::UIElement::new(&texture, r.x + r.w - 4.0 * sw, r.y + r.h - 6.0 * sh, 4.0 * sw, 6.0 * sh, 198.0/200.0, 17.0/20.0, 2.0/200.0, 3.0/20.0).bytes(width, height)); + + let w = ((r.w / sw)/2.0) - 4.0; + self.data.extend(render::ui::UIElement::new( + &texture.relative(2.0/200.0, 0.0, 196.0/200.0, 2.0/20.0), + r.x+4.0*sw, r.y, r.w - 8.0 * sw, 4.0 * sh, 0.0, 0.0, w/196.0, 1.0).bytes(width, height) + ); + self.data.extend(render::ui::UIElement::new( + &texture.relative(2.0/200.0, 17.0/20.0, 196.0/200.0, 3.0/20.0), + r.x+4.0*sw, r.y+r.h-6.0*sh, r.w - 8.0 * sw, 6.0 * sh, 0.0, 0.0, w/196.0, 1.0).bytes(width, height) + ); + + let h = ((r.h / sh)/2.0) - 5.0; + self.data.extend(render::ui::UIElement::new( + &texture.relative(0.0/200.0, 2.0/20.0, 2.0/200.0, 15.0/20.0), + r.x, r.y + 4.0*sh, 4.0 * sw, r.h - 10.0*sh, 0.0, 0.0, 1.0, h/16.0).bytes(width, height) + ); + self.data.extend(render::ui::UIElement::new( + &texture.relative(198.0/200.0, 2.0/20.0, 2.0/200.0, 15.0/20.0), + r.x+r.w - 4.0 * sw, r.y + 4.0*sh, 4.0 * sw, r.h - 10.0*sh, 0.0, 0.0, 1.0, h/16.0).bytes(width, height) + ); + + + self.data.extend(render::ui::UIElement::new( + &texture.relative(2.0/200.0, 2.0/20.0, 196.0/200.0, 15.0/20.0), + r.x+4.0*sw, r.y+4.0*sh, r.w - 8.0 * sw, r.h - 10.0 * sh, 0.0, 0.0, w/196.0, h/16.0).bytes(width, height) + ); + self.super_draw(renderer, r, sw, sh, width, height, delta); + self.last_disabled = self.disabled; + self.last_hovered = self.hovered; + } + &mut self.data + } + + fn tick(&mut self, renderer: &mut render::Renderer) { + self.super_tick(renderer); + } + + fn get_size(&self) -> (f64, f64) { + (self.width, self.height) + } + + fn is_dirty(&self) -> bool { + self.last_disabled != self.disabled + || self.last_hovered != self.hovered + } + + + fn post_init(s: Rc>) { + s.borrow_mut().add_hover_func(move |this, hover, _| { + this.hovered = hover; + for text in &this.texts { + text.borrow_mut().colour.2 = if hover { 160 } else { 255 }; + } + true + }) + } +} + +impl Button { + pub fn add_text(&mut self, text: TextRef) { + self.texts.push(text); + } +} + +element! { + ref TextBoxRef + pub struct TextBox { + pub input: String, + pub password: bool, + pub width: f64, + pub height: f64, + priv button: Option, + priv text: Option, + priv was_focused: bool, + priv cursor_tick: f64, + priv submit_funcs: Vec>, + } + builder TextBoxBuilder { + hardcode button = None, + hardcode text = None, + hardcode was_focused = false, + hardcode cursor_tick = 0.0, + hardcode submit_funcs = vec![], + optional input: String = "".into(), + optional password: bool = false, + noset width: f64 = |b| b.width.expect("Missing required field width"), + noset height: f64 = |b| b.height.expect("Missing required field height"), + } +} + +impl TextBoxBuilder { + pub fn size(mut self, width: f64, height: f64) -> Self { + self.width = Some(width); + self.height = Some(height); + self + } +} + +impl UIElement for TextBox { + fn key_press(&mut self, game: &mut ::Game, key: Keycode, down: bool) { + match (key, down) { + (Keycode::Backspace, false) => {self.input.pop();}, + (Keycode::Return, false) => { + use std::mem; + let len = self.submit_funcs.len(); + let mut temp = mem::replace(&mut self.submit_funcs, Vec::with_capacity(len)); + for func in &temp { + (func)(self, game); + } + self.submit_funcs.append(&mut temp); + }, + _ => {}, + } + } + + fn key_type(&mut self, _game: &mut ::Game, c: char) { + self.input.push(c); + } + + fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] { + if self.check_rebuild() { + self.data.clear(); + self.cursor_tick += delta; + if self.cursor_tick > 3000.0 { + self.cursor_tick -= 3000.0; + } + let mut text = self.transform_input(); + { + let mut btn = self.button.as_mut().unwrap().borrow_mut(); + btn.width = self.width; + btn.height = self.height; + let mut txt = self.text.as_mut().unwrap().borrow_mut(); + if self.focused && ((self.cursor_tick / 30.0) as i32) % 2 == 0 { + text.push('|'); + } + txt.text = text; + } + self.super_draw(renderer, r, sw, sh, width, height, delta); + + self.was_focused = self.focused; + } + &mut self.data + } + + fn tick(&mut self, renderer: &mut render::Renderer) { + self.super_tick(renderer); + } + + fn get_size(&self) -> (f64, f64) { + (self.width, self.height) + } + + fn is_dirty(&self) -> bool { + self.focused || self.was_focused + } + + fn post_init(s: Rc>) { + let mut textbox = s.borrow_mut(); + textbox.button = Some(ButtonBuilder::new() + .position(0.0, 0.0) + .size(textbox.width, textbox.height) + .disabled(true) + .attach(&mut *textbox)); + textbox.text = Some(TextBuilder::new() + .text("") + .position(5.0, 0.0) + .draw_index(1) + .alignment(VAttach::Middle, HAttach::Left) + .attach(&mut *textbox)); + } +} + +impl TextBox { + pub fn add_submit_func(&mut self, f: F) { + self.submit_funcs.push(Box::new(f)); + } + + fn transform_input(&self) -> String { + if self.password { + ::std::iter::repeat('*').take(self.input.len()).collect() + } else { + self.input.clone() + } + } +} diff --git a/src/ui/text.rs b/src/ui/text.rs deleted file mode 100644 index e9a8aa0..0000000 --- a/src/ui/text.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2016 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. - -ui_element!(Text { - val: String, - width: f64, - height: f64, - scale_x: f64, - scale_y: f64, - rotation: f64, - r: u8, - g: u8, - b: u8, - a: u8, -}); - -impl Text { - base_impl!(); - - 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, - scale_x: 1.0, - scale_y: 1.0, - rotation: 0.0, - r: r, - g: g, - b: b, - a: 255, - }) - } - - fn update(&mut self, renderer: &mut render::Renderer) { - self.width = renderer.ui.size_of_string(&self.val); - } - - fn draw(&mut self, - renderer: &mut render::Renderer, - r: &Region, - width: f64, - height: f64, - _: f64) - -> &Vec { - if self.dirty { - self.dirty = false; - let sx = r.w / self.width; - let sy = r.h / self.height; - let mut text = if self.rotation == 0.0 { - renderer.ui.new_text_scaled(&self.val, - r.x, - r.y, - sx * self.scale_x, - sy * self.scale_y, - self.r, - self.g, - self.b) - } else { - let c = self.rotation.cos(); - let s = self.rotation.sin(); - let tmpx = r.w / 2.0; - let tmpy = r.h / 2.0; - let w = (tmpx * c - tmpy * s).abs(); - let h = (tmpy * c + tmpx * s).abs(); - renderer.ui.new_text_rotated(&self.val, - r.x + w - (r.w / 2.0), - r.y + h - (r.h / 2.0), - sx * self.scale_x, - sy * self.scale_y, - self.rotation, - self.r, - self.g, - self.b) - }; - for e in &mut text.elements { - e.a = self.a; - e.layer = self.layer; - } - self.data = text.bytes(width, height); - } - &self.data - } - - pub fn get_text(&self) -> &str { - &self.val - } - - pub fn set_text(&mut self, renderer: &render::Renderer, val: &str) { - self.dirty = true; - self.val = val.to_owned(); - self.width = renderer.ui.size_of_string(val); - } - - lazy_field!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); - 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); - lazy_field!(r, u8, get_r, set_r); - lazy_field!(g, u8, get_g, set_g); - lazy_field!(b, u8, get_b, set_b); - -} - -impl UIElement for Text { - fn wrap(self) -> Element { - Element::Text(self) - } - - fn unwrap_ref<'a>(e: &'a Element) -> &'a Text { - match e { - &Element::Text(ref val) => val, - _ => panic!("Incorrect type"), - } - } - - fn unwrap_ref_mut<'a>(e: &'a mut Element) -> &'a mut Text { - match e { - &mut Element::Text(ref mut val) => val, - _ => panic!("Incorrect type"), - } - } - - fn get_attachment(&self) -> (VAttach, HAttach) { - (self.v_attach, self.h_attach) - } - - fn get_offset(&self) -> (f64, f64) { - (self.x, self.y) - } - - fn get_size(&self) -> (f64, f64) { - ((self.width + 2.0) * self.scale_x, self.height * self.scale_y) - } -} diff --git a/src/ui/textbox.rs b/src/ui/textbox.rs deleted file mode 100644 index c6ab3ec..0000000 --- a/src/ui/textbox.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2016 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. -// Copyright 2016 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. - -ui_element!(TextBox { - input: String, - width: f64, - height: f64, - password: bool, - button: Button, - text: Text, - cursor_tick: f64, - was_focused: bool, - submit_funcs: Vec>, -}); - -impl TextBox { - base_impl!(); - - pub fn new(renderer: &render::Renderer, - input: &str, - x: f64, y: f64, w: f64, h: f64 - ) -> TextBox { - let mut btn = Button::new(0.0, 0.0, w, h); - btn.set_disabled(true); - let mut txt = Text::new(renderer, input, 5.0, 0.0, 255, 255, 255); - txt.set_v_attach(VAttach::Middle); - let mut tbox = ui_create!(TextBox { - input: input.to_owned(), - x: x, - y: y, - width: w, - height: h, - password: false, - button: btn, - text: txt, - cursor_tick: 0.0, - was_focused: false, - submit_funcs: vec![], - }); - tbox.can_focus = true; - tbox - } - - fn update(&mut self, renderer: &mut render::Renderer) { - self.text.update(renderer); - } - - fn draw(&mut self, - renderer: &mut render::Renderer, - r: &Region, - width: f64, - height: f64, - delta: f64) - -> &Vec { - if self.dirty || self.focused || self.was_focused { - self.was_focused = self.focused; - self.data.clear(); - self.dirty = false; - - self.cursor_tick += delta; - if self.cursor_tick > 3000.0 { - self.cursor_tick -= 3000.0; - } - - let mut txt = self.transform_input(); - if self.focused && ((self.cursor_tick / 30.0) as i32) % 2 == 0 { - txt.push('|'); - } - self.text.set_text(renderer, &txt); - - let sx = r.w / self.width; - let sy = r.h / self.height; - let reg = Container::get_draw_region_raw(&self.button, sx, sy, r); - self.button.dirty = true; - self.data.extend(self.button.draw(renderer, ®, width, height, delta)); - - let reg = Container::get_draw_region_raw(&self.text, sx, sy, r); - self.text.dirty = true; - self.data.extend(self.text.draw(renderer, ®, width, height, delta)); - } - &self.data - } - - pub fn get_input(&self) -> String { - self.input.clone() - } - - pub fn set_input(&mut self, renderer: &render::Renderer, input: &str) { - self.dirty = true; - self.input = input.to_owned(); - let txt = self.transform_input(); - self.text.set_text(renderer, &txt); - } - - pub fn add_submit_func(&mut self, f: F) { - self.submit_funcs.push(Rc::new(f)); - } - - fn transform_input(&self) -> String { - if self.password { - ::std::iter::repeat('*').take(self.input.len()).collect() - } else { - self.input.clone() - } - } - - lazy_field!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); - lazy_field!(password, bool, is_password, set_password); -} - -impl UIElement for TextBox { - - fn key_press(&mut self, _game: &mut ::Game, key: Keycode, down: bool) -> Vec> { - match (key, down) { - (Keycode::Backspace, false) => {self.input.pop();}, - (Keycode::Return, false) => return self.submit_funcs.clone(), - _ => {}, - } - vec![] - } - - fn key_type(&mut self, _game: &mut ::Game, c: char) -> Vec> { - self.input.push(c); - vec![] - } - - fn wrap(self) -> Element { - Element::TextBox(self) - } - - fn unwrap_ref<'a>(e: &'a Element) -> &'a TextBox { - match e { - &Element::TextBox(ref val) => val, - _ => panic!("Incorrect type"), - } - } - - fn unwrap_ref_mut<'a>(e: &'a mut Element) -> &'a mut TextBox { - match e { - &mut Element::TextBox(ref mut val) => val, - _ => panic!("Incorrect type"), - } - } - - fn get_attachment(&self) -> (VAttach, HAttach) { - (self.v_attach, self.h_attach) - } - - fn get_offset(&self) -> (f64, f64) { - (self.x, self.y) - } - - fn get_size(&self) -> (f64, f64) { - (self.width, self.height) - } -}