From b418625a4833f7ce22a5565f788bb696f352abdc Mon Sep 17 00:00:00 2001 From: Thinkofname Date: Sun, 20 Mar 2016 20:17:21 +0000 Subject: [PATCH] Work on login screen, added ui buttons and textboxes (plus tab fixes) --- src/auth.rs | 63 +++ src/console/mod.rs | 64 ++-- src/main.rs | 21 +- src/protocol/packet.rs | 46 +-- src/render/atlas.rs | 8 +- src/render/shaders.rs | 84 ++-- src/render/shaders/chunk_frag.glsl | 32 +- src/render/shaders/chunk_vertex.glsl | 16 +- src/render/shaders/get_light.glsl | 42 +- src/render/shaders/lookup_texture.glsl | 12 +- src/render/shaders/trans_frag.glsl | 32 +- src/render/shaders/ui_frag.glsl | 8 +- src/render/shaders/ui_vertex.glsl | 12 +- src/render/ui.rs | 36 +- src/screen/connecting.rs | 4 +- src/screen/login.rs | 52 ++- src/screen/mod.rs | 230 ++--------- src/screen/server_list.rs | 70 ++-- src/server.rs | 6 +- src/ui/batch.rs | 12 +- src/ui/button.rs | 136 +++++++ src/ui/formatted.rs | 26 +- src/ui/image.rs | 44 +-- src/ui/logo.rs | 2 +- src/ui/mod.rs | 508 ++++++++++++++++--------- src/ui/text.rs | 38 +- src/ui/textbox.rs | 185 +++++++++ 27 files changed, 1078 insertions(+), 711 deletions(-) create mode 100644 src/auth.rs create mode 100644 src/ui/button.rs create mode 100644 src/ui/textbox.rs diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..d5e5f07 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,63 @@ +// 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. + +use console; +use std::marker::PhantomData; + +pub const CL_USERNAME: console::CVar = console::CVar { + ty: PhantomData, + name: "cl_username", + description: r#"cl_username is the username that the client will use to connect +to servers."#, + mutable: false, + serializable: true, + default: &|| "".to_owned(), +}; + +pub const CL_UUID: console::CVar = console::CVar { + ty: PhantomData, + name: "cl_uuid", + description: r#"cl_uuid is the uuid of the client. This is unique to a player +unlike their username."#, + mutable: false, + serializable: true, + default: &|| "".to_owned(), +}; + +pub const AUTH_TOKEN: console::CVar = console::CVar { + ty: PhantomData, + name: "auth_token", + description: r#"auth_token is the token used for this session to auth to servers +or relogin to this account."#, + mutable: false, + serializable: true, + default: &|| "".to_owned(), +}; + +pub const AUTH_CLIENT_TOKEN: console::CVar = console::CVar { + ty: PhantomData, + name: "auth_client_token", + description: r#"auth_client_token is a token that stays static between sessions. +Used to identify this client vs others."#, + mutable: false, + serializable: true, + default: &|| "".to_owned(), +}; + +pub fn register_vars(console: &mut console::Console) { + console.register(CL_USERNAME); + console.register(CL_UUID); + console.register(AUTH_TOKEN); + console.register(AUTH_CLIENT_TOKEN); +} diff --git a/src/console/mod.rs b/src/console/mod.rs index 05b73eb..87112fc 100644 --- a/src/console/mod.rs +++ b/src/console/mod.rs @@ -98,7 +98,7 @@ impl Console { pub fn get(&self, var: CVar) -> &T where CVar: Var { - // Should never fail + // Should never fail self.var_values.get(var.name).unwrap().downcast_ref::().unwrap() } @@ -122,9 +122,9 @@ impl Console { renderer: &mut 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. + // 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 { return; @@ -238,34 +238,34 @@ impl Console { self.history.remove(0); let mut msg = TextComponent::new(""); msg.modifier.extra = Some(vec![ - Component::Text(TextComponent::new("[")), - { - let mut msg = TextComponent::new(file); - msg.modifier.color = Some(Color::Green); - Component::Text(msg) - }, - Component::Text(TextComponent::new(":")), - { - let mut msg = TextComponent::new(&format!("{}", record.location().line())); - msg.modifier.color = Some(Color::Aqua); - Component::Text(msg) - }, - Component::Text(TextComponent::new("]")), - Component::Text(TextComponent::new("[")), - { - let mut msg = TextComponent::new(&format!("{}", record.level())); - msg.modifier.color = Some(match record.level() { - log::LogLevel::Debug => Color::Green, - log::LogLevel::Error => Color::Red, - log::LogLevel::Warn => Color::Yellow, - log::LogLevel::Info => Color::Aqua, - log::LogLevel::Trace => Color::Blue, - }); - Component::Text(msg) - }, - Component::Text(TextComponent::new("] ")), - Component::Text(TextComponent::new(&format!("{}", record.args()))) - ]); + Component::Text(TextComponent::new("[")), + { + let mut msg = TextComponent::new(file); + msg.modifier.color = Some(Color::Green); + Component::Text(msg) + }, + Component::Text(TextComponent::new(":")), + { + let mut msg = TextComponent::new(&format!("{}", record.location().line())); + msg.modifier.color = Some(Color::Aqua); + Component::Text(msg) + }, + Component::Text(TextComponent::new("]")), + Component::Text(TextComponent::new("[")), + { + let mut msg = TextComponent::new(&format!("{}", record.level())); + msg.modifier.color = Some(match record.level() { + log::LogLevel::Debug => Color::Green, + log::LogLevel::Error => Color::Red, + log::LogLevel::Warn => Color::Yellow, + log::LogLevel::Info => Color::Aqua, + log::LogLevel::Trace => Color::Blue, + }); + Component::Text(msg) + }, + Component::Text(TextComponent::new("] ")), + Component::Text(TextComponent::new(&format!("{}", record.args()))) + ]); self.history.push(Component::Text(msg)); } } diff --git a/src/main.rs b/src/main.rs index 205eaf0..d4bb213 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,7 @@ pub mod console; pub mod server; pub mod world; pub mod chunk_builder; +pub mod auth; use std::sync::{Arc, RwLock, Mutex}; use std::marker::PhantomData; @@ -128,6 +129,7 @@ fn main() { { let mut con = con.lock().unwrap(); con.register(CL_BRAND); + auth::register_vars(&mut con); con.load_config(); con.save_config(); } @@ -170,7 +172,7 @@ fn main() { let frame_time = (time::Duration::seconds(1).num_nanoseconds().unwrap() as f64) / 60.0; let mut screen_sys = screen::ScreenSystem::new(); - screen_sys.add_screen(Box::new(screen::Login::new())); + screen_sys.add_screen(Box::new(screen::Login::new(con.clone()))); let textures = renderer.get_textures(); let mut game = Game { @@ -240,14 +242,12 @@ fn handle_window_event(window: &glutin::Window, ui_container.hover_at(game, x as f64, y as f64, width as f64, height as f64); } - Event::MouseInput(glutin::ElementState::Released, glutin::MouseButton::Left) => { let (x, y) = game.mouse_pos; let (width, height) = window.get_inner_size_pixels().unwrap(); ui_container.click_at(game, x as f64, y as f64, width as f64, height as f64); } - Event::MouseWheel(delta) => { let (x, y) = match delta { glutin::MouseScrollDelta::LineDelta(x, y) => (x, y), @@ -256,18 +256,15 @@ fn handle_window_event(window: &glutin::Window, game.screen_sys.on_scroll(x as f64, y as f64); } - - Event::KeyboardInput(glutin::ElementState::Pressed, 41 /* ` GRAVE */, _) - | Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(VirtualKeyCode::Grave)) => { + Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(VirtualKeyCode::Grave)) => { game.console.lock().unwrap().toggle(); } - Event::KeyboardInput(glutin::ElementState::Pressed, key, virt) => { - println!("Key: {:?} {:?}", key, virt); - if virt == Some(VirtualKeyCode::H) { - game.server.world.flag_dirty_all(); - } + Event::KeyboardInput(state, key, virt) => { + ui_container.key_press(game, virt, key, state == glutin::ElementState::Pressed); + } + Event::ReceivedCharacter(c) => { + ui_container.key_type(game, c); } - _ => (), } } diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index a67d979..f126a41 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -511,20 +511,20 @@ state_packets!( // JoinGame is sent after completing the login process. This // sets the initial state for the client. JoinGame { - // The entity id the client will be referenced by + // The entity id the client will be referenced by entity_id: i32 =, - // The starting gamemode of the client + // The starting gamemode of the client gamemode: u8 =, - // The dimension the client is starting in + // The dimension the client is starting in dimension: i8 =, - // The difficuilty setting for the server + // The difficuilty setting for the server difficulty: u8 =, - // The max number of players on the server + // The max number of players on the server max_players: u8 =, - // The level type of the server + // The level type of the server level_type: String =, - // Whether the client should reduce the amount of debug - // information it displays in F3 mode + // Whether the client should reduce the amount of debug + // information it displays in F3 mode reduced_debug_info: bool =, } // Maps updates a single map's contents @@ -879,21 +879,21 @@ state_packets!( // and optionally a favicon. // // The structure is as follows - // { - // "version": { - // "name": "1.8.3", - // "protocol": 47, - // }, - // "players": { - // "max": 20, - // "online": 1, - // "sample": [ - // {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} - // ] - // }, - // "description": "Hello world", - // "favicon": "data:image/png;base64," - // } + // { + // "version": { + // "name": "1.8.3", + // "protocol": 47, + // }, + // "players": { + // "max": 20, + // "online": 1, + // "sample": [ + // {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} + // ] + // }, + // "description": "Hello world", + // "favicon": "data:image/png;base64," + // } StatusResponse { status: String =, } diff --git a/src/render/atlas.rs b/src/render/atlas.rs index 236ef3e..9eda5b1 100644 --- a/src/render/atlas.rs +++ b/src/render/atlas.rs @@ -40,7 +40,7 @@ impl Atlas { let mut priority = usize::max_value(); let mut target: Option = None; let mut target_index = 0; - // Search through and find the best fit for this texture + // Search through and find the best fit for this texture for (index, free) in self.free_space.iter().enumerate() { if free.width >= width && free.height >= height { let current_priority = (free.width - width) * (free.height - height); @@ -49,7 +49,7 @@ impl Atlas { priority = current_priority; target_index = index; } - // Perfect match, we can break early + // Perfect match, we can break early if priority == 0 { break; } @@ -70,14 +70,14 @@ impl Atlas { t.y += height; t.height -= height; if t.height == 0 { - // Remove empty sections + // Remove empty sections self.free_space.remove(target_index); } else { self.free_space[target_index] = t; } } else { if t.height > height { - // Split by height + // Split by height self.free_space.insert(0, Rect { x: t.x, diff --git a/src/render/shaders.rs b/src/render/shaders.rs index 3056570..fbb7a63 100644 --- a/src/render/shaders.rs +++ b/src/render/shaders.rs @@ -41,50 +41,50 @@ macro_rules! get_shader { #[macro_export] macro_rules! init_shader { - ( - Program $name:ident { - vert = $vert:expr, $(#$vdef:ident)* - frag = $frag:expr, $(#$fdef:ident)* - attribute = { - $( - $field:ident => $glname:expr, - )* - }, - uniform = { - $( - $ufield:ident => $uglname:expr, - )* - }, - } - ) => ( + ( + Program $name:ident { + vert = $vert:expr, $(#$vdef:ident)* + frag = $frag:expr, $(#$fdef:ident)* + attribute = { + $( + $field:ident => $glname:expr, + )* + }, + uniform = { + $( + $ufield:ident => $uglname:expr, + )* + }, + } + ) => ( #[allow(dead_code)] - struct $name { - program: gl::Program, - $( - $field: gl::Attribute, - )+ - $( - $ufield: gl::Uniform, - )+ - } + struct $name { + program: gl::Program, + $( + $field: gl::Attribute, + )+ + $( + $ufield: gl::Uniform, + )+ + } - impl $name { - pub fn new(reg: &glsl::Registry) -> $name { - let v = get_shader!(reg, $vert $(,stringify!($vdef))*); - let f = get_shader!(reg, $frag $(,stringify!($fdef))*); - let shader = shaders::create_program(&v, &f); - $name { - $( - $field: shader.attribute_location($glname), - )+ - $( - $ufield: shader.uniform_location($uglname), - )+ - program: shader, - } - } - } - ) + impl $name { + pub fn new(reg: &glsl::Registry) -> $name { + let v = get_shader!(reg, $vert $(,stringify!($vdef))*); + let f = get_shader!(reg, $frag $(,stringify!($fdef))*); + let shader = shaders::create_program(&v, &f); + $name { + $( + $field: shader.attribute_location($glname), + )+ + $( + $ufield: shader.uniform_location($uglname), + )+ + program: shader, + } + } + } + ) } pub fn create_program(vertex: &str, fragment: &str) -> gl::Program { diff --git a/src/render/shaders/chunk_frag.glsl b/src/render/shaders/chunk_frag.glsl index 9bff17e..adba0e6 100644 --- a/src/render/shaders/chunk_frag.glsl +++ b/src/render/shaders/chunk_frag.glsl @@ -16,21 +16,21 @@ out float revealage; #include lookup_texture void main() { - vec4 col = atlasTexture(); - #ifndef alpha - if (col.a < 0.5) discard; - #endif - col *= vec4(vColor, 1.0); - col.rgb *= vLighting; + vec4 col = atlasTexture(); + #ifndef alpha + if (col.a < 0.5) discard; + #endif + col *= vec4(vColor, 1.0); + col.rgb *= vLighting; - #ifndef alpha - fragColor = col; - #else - float z = gl_FragCoord.z; - float al = col.a; - float weight = pow(al + 0.01f, 4.0f) + - max(0.01f, min(3000.0f, 0.3f / (0.00001f + pow(abs(z) / 800.0f, 4.0f)))); - accum = vec4(col.rgb * al * weight, al); - revealage = weight * al; - #endif + #ifndef alpha + fragColor = col; + #else + float z = gl_FragCoord.z; + float al = col.a; + float weight = pow(al + 0.01f, 4.0f) + + max(0.01f, min(3000.0f, 0.3f / (0.00001f + pow(abs(z) / 800.0f, 4.0f)))); + accum = vec4(col.rgb * al * weight, al); + revealage = weight * al; + #endif } diff --git a/src/render/shaders/chunk_vertex.glsl b/src/render/shaders/chunk_vertex.glsl index 1a0f080..7efb06a 100644 --- a/src/render/shaders/chunk_vertex.glsl +++ b/src/render/shaders/chunk_vertex.glsl @@ -19,14 +19,14 @@ out vec3 vLighting; #include get_light void main() { - vec3 pos = vec3(aPosition.x, -aPosition.y, aPosition.z); - vec3 o = vec3(offset.x, -offset.y / 4096.0, offset.z); - gl_Position = perspectiveMatrix * cameraMatrix * vec4(pos + o * 16.0, 1.0); + vec3 pos = vec3(aPosition.x, -aPosition.y, aPosition.z); + vec3 o = vec3(offset.x, -offset.y / 4096.0, offset.z); + gl_Position = perspectiveMatrix * cameraMatrix * vec4(pos + o * 16.0, 1.0); - vColor = aColor; - vTextureInfo = aTextureInfo; - vTextureOffset = aTextureOffset.xy / 16.0; - vAtlas = aTextureOffset.z; + vColor = aColor; + vTextureInfo = aTextureInfo; + vTextureOffset = aTextureOffset.xy / 16.0; + vAtlas = aTextureOffset.z; - vLighting = getLight(aLighting / (4000.0)); + vLighting = getLight(aLighting / (4000.0)); } diff --git a/src/render/shaders/get_light.glsl b/src/render/shaders/get_light.glsl index 6d0e021..06cbffa 100644 --- a/src/render/shaders/get_light.glsl +++ b/src/render/shaders/get_light.glsl @@ -1,28 +1,28 @@ vec3 getLight(vec2 light) { - vec2 li = pow(vec2(lightLevel), 15.0 - light); - float skyTint = skyOffset * 0.95 + 0.05; - float bl = li.x; - float sk = li.y * skyTint; + vec2 li = pow(vec2(lightLevel), 15.0 - light); + float skyTint = skyOffset * 0.95 + 0.05; + float bl = li.x; + float sk = li.y * skyTint; - float skyRed = sk * (skyOffset * 0.65 + 0.35); - float skyGreen = sk * (skyOffset * 0.65 + 0.35); - float blockGreen = bl * ((bl * 0.6 + 0.4) * 0.6 + 0.4); - float blockBlue = bl * (bl * bl * 0.6 + 0.4); + float skyRed = sk * (skyOffset * 0.65 + 0.35); + float skyGreen = sk * (skyOffset * 0.65 + 0.35); + float blockGreen = bl * ((bl * 0.6 + 0.4) * 0.6 + 0.4); + float blockBlue = bl * (bl * bl * 0.6 + 0.4); - vec3 col = vec3( - skyRed + bl, - skyGreen + blockGreen, - sk + blockBlue - ); + vec3 col = vec3( + skyRed + bl, + skyGreen + blockGreen, + sk + blockBlue + ); - col = col * 0.96 + 0.03; + col = col * 0.96 + 0.03; - float gamma = 0.0; - vec3 invCol = 1.0 - col; - invCol = 1.0 - invCol * invCol * invCol * invCol; - col = col * (1.0 - gamma) + invCol * gamma; - col = col * 0.96 + 0.03; + float gamma = 0.0; + vec3 invCol = 1.0 - col; + invCol = 1.0 - invCol * invCol * invCol * invCol; + col = col * (1.0 - gamma) + invCol * gamma; + col = col * 0.96 + 0.03; - return clamp(col, 0.0, 1.0); -} \ No newline at end of file + return clamp(col, 0.0, 1.0); +} \ No newline at end of file diff --git a/src/render/shaders/lookup_texture.glsl b/src/render/shaders/lookup_texture.glsl index 8d460b4..3bb6a9f 100644 --- a/src/render/shaders/lookup_texture.glsl +++ b/src/render/shaders/lookup_texture.glsl @@ -1,8 +1,8 @@ const float invAtlasSize = 1.0 / 1024; vec4 atlasTexture() { - vec2 tPos = vTextureOffset; - tPos = mod(tPos, vTextureInfo.zw); - tPos += vTextureInfo.xy; - tPos *= invAtlasSize; - return texture(textures, vec3(tPos, vAtlas)); -} \ No newline at end of file + vec2 tPos = vTextureOffset; + tPos = mod(tPos, vTextureInfo.zw); + tPos += vTextureInfo.xy; + tPos *= invAtlasSize; + return texture(textures, vec3(tPos, vAtlas)); +} \ No newline at end of file diff --git a/src/render/shaders/trans_frag.glsl b/src/render/shaders/trans_frag.glsl index 5b83741..123e4c3 100644 --- a/src/render/shaders/trans_frag.glsl +++ b/src/render/shaders/trans_frag.glsl @@ -7,22 +7,22 @@ uniform int samples; out vec4 fragColor; void main() { - ivec2 C = ivec2(gl_FragCoord.xy); - vec4 accum = texelFetch(taccum, C, 0); - float aa = texelFetch(trevealage, C, 0).r; - vec4 col = texelFetch(tcolor, C, 0); + ivec2 C = ivec2(gl_FragCoord.xy); + vec4 accum = texelFetch(taccum, C, 0); + float aa = texelFetch(trevealage, C, 0).r; + vec4 col = texelFetch(tcolor, C, 0); - for (int i = 1; i < samples; i++) { - col += texelFetch(tcolor, C, i); - } - col /= float(samples); + for (int i = 1; i < samples; i++) { + col += texelFetch(tcolor, C, i); + } + col /= float(samples); - float r = accum.a; - accum.a = aa; - if (r >= 1.0) { - fragColor = vec4(col.rgb, 0.0); - } else { - vec3 alp = clamp(accum.rgb / clamp(accum.a, 1e-4, 5e4), 0.0, 1.0); - fragColor = vec4(col.rgb * r + alp * (1.0 - r), 0.0); - } + float r = accum.a; + accum.a = aa; + if (r >= 1.0) { + fragColor = vec4(col.rgb, 0.0); + } else { + vec3 alp = clamp(accum.rgb / clamp(accum.a, 1e-4, 5e4), 0.0, 1.0); + fragColor = vec4(col.rgb * r + alp * (1.0 - r), 0.0); + } } diff --git a/src/render/shaders/ui_frag.glsl b/src/render/shaders/ui_frag.glsl index 00a0a83..806fe62 100644 --- a/src/render/shaders/ui_frag.glsl +++ b/src/render/shaders/ui_frag.glsl @@ -10,8 +10,8 @@ out vec4 fragColor; #include lookup_texture void main() { - vec4 col = atlasTexture(); - col *= vColor; - if (col.a == 0.0) discard; - fragColor = col; + vec4 col = atlasTexture(); + col *= vColor; + if (col.a == 0.0) discard; + fragColor = col; } \ No newline at end of file diff --git a/src/render/shaders/ui_vertex.glsl b/src/render/shaders/ui_vertex.glsl index eb0ec9b..2984682 100644 --- a/src/render/shaders/ui_vertex.glsl +++ b/src/render/shaders/ui_vertex.glsl @@ -11,10 +11,10 @@ out float vAtlas; uniform vec2 screenSize; void main() { - vec2 pos = aPosition.xy / screenSize; - gl_Position = vec4((pos.x-0.5)*2.0, -(pos.y-0.5)*2.0, float(-aPosition.z) / float(0xFFFF-1), 1.0); - vColor = aColor; - vTextureInfo = aTextureInfo; - vTextureOffset = aTextureOffset.xy / 16.0; - vAtlas = aTextureOffset.z; + vec2 pos = aPosition.xy / screenSize; + gl_Position = vec4((pos.x-0.5)*2.0, -(pos.y-0.5)*2.0, float(-aPosition.z) / float(0xFFFF-1), 1.0); + vColor = aColor; + vTextureInfo = aTextureInfo; + vTextureOffset = aTextureOffset.xy / 16.0; + vAtlas = aTextureOffset.z; } \ No newline at end of file diff --git a/src/render/ui.rs b/src/render/ui.rs index 9782fde..3fb4cde 100644 --- a/src/render/ui.rs +++ b/src/render/ui.rs @@ -53,20 +53,20 @@ pub struct UIState { } init_shader! { - Program UIShader { - vert = "ui_vertex", - frag = "ui_frag", - attribute = { - position => "aPosition", - texture_info => "aTextureInfo", - texture_offset => "aTextureOffset", - color => "aColor", - }, - uniform = { - texture => "textures", - screensize => "screenSize", - }, - } + Program UIShader { + vert = "ui_vertex", + frag = "ui_frag", + attribute = { + position => "aPosition", + texture_info => "aTextureInfo", + texture_offset => "aTextureOffset", + color => "aColor", + }, + uniform = { + texture => "textures", + screensize => "screenSize", + }, + } } impl UIState { @@ -143,7 +143,7 @@ impl UIState { self.load_font(); } } - // Prevent clipping with the world + // Prevent clipping with the world gl::clear(gl::ClearFlags::Depth); gl::depth_func(gl::LESS_OR_EQUAL); gl::enable(gl::BLEND); @@ -187,7 +187,7 @@ impl UIState { pub fn character_texture(&mut self, c: char) -> render::Texture { let raw = c as u32; let page = raw >> 8; - // Lazy load fonts to size memory + // Lazy load fonts to size memory if self.font_pages[page as usize].is_none() { let name = if page == 0 { "font/ascii".to_owned() @@ -254,8 +254,8 @@ impl UIState { let mut data = Vec::with_capacity(0x10000); info.read_to_end(&mut data).unwrap(); for (i, info) in self.font_character_info.iter_mut().enumerate() { - // Top nibble - start position - // Bottom nibble - end position + // Top nibble - start position + // Bottom nibble - end position info.0 = (data[i] >> 4) as i32; info.1 = (data[i] & 0xF) as i32 + 1; } diff --git a/src/screen/connecting.rs b/src/screen/connecting.rs index 14bda45..f3b6859 100644 --- a/src/screen/connecting.rs +++ b/src/screen/connecting.rs @@ -63,7 +63,7 @@ impl super::Screen for Connecting { server_msg.set_h_attach(ui::HAttach::Center); elements.add(ui_container.add(server_msg)); - // Disclaimer + // Disclaimer let mut warn = ui::Text::new(renderer, "Not affiliated with Mojang/Minecraft", 5.0, @@ -81,7 +81,7 @@ impl super::Screen for Connecting { }); } fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - // Clean up + // Clean up { let elements = self.elements.as_mut().unwrap(); elements.logo.remove(ui_container); diff --git a/src/screen/login.rs b/src/screen/login.rs index eeb3412..e084580 100644 --- a/src/screen/login.rs +++ b/src/screen/login.rs @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::rc::Rc; +use std::sync::{Arc, Mutex}; use ui; use render; +use console; pub struct Login { elements: Option, + console: Arc>, } struct UIElements { @@ -28,8 +30,8 @@ struct UIElements { impl Login { - pub fn new() -> Login { - Login { elements: None } + pub fn new(console: Arc>) -> Login { + Login { elements: None, console: console } } } @@ -38,13 +40,8 @@ impl super::Screen for Login { let logo = ui::logo::Logo::new(renderer.resources.clone(), renderer, ui_container); let mut elements = ui::Collection::new(); - // Login - let (mut login, mut txt) = super::new_button_text(renderer, - "Login", - 0.0, - 100.0, - 400.0, - 40.0); + // 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); @@ -53,14 +50,41 @@ impl super::Screen for Login { super::button_action(ui_container, re.clone(), Some(tre.clone()), - Some(Rc::new(|game, _| { + |game, _| { game.screen_sys .replace_screen(Box::new(super::ServerList::new(None))); - }))); + }); elements.add(re); elements.add(tre); - // Disclaimer + // 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); + elements.add(ure); + elements.add(ui_container.add(username_label)); + + // 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); + password.add_submit_func(|_, _| { + println!("Submit!"); + }); + 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); + 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, @@ -78,7 +102,7 @@ impl super::Screen for Login { }); } fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - // Clean up + // Clean up { let elements = self.elements.as_mut().unwrap(); elements.logo.remove(ui_container); diff --git a/src/screen/mod.rs b/src/screen/mod.rs index 2db8b6a..04d9e15 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -18,30 +18,28 @@ mod login; pub use self::login::*; pub mod connecting; -use std::rc::Rc; - use render; use ui; #[allow(unused_variables)] pub trait Screen { - // Called once + // Called once fn init(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { } fn deinit(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { } - // May be called multiple times + // May be called multiple times fn on_active(&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); - // Called every frame the screen is active + // Called every frame the screen is active fn tick(&mut self, delta: f64, renderer: &mut render::Renderer, ui_container: &mut ui::Container); - // Events + // Events fn on_scroll(&mut self, x: f64, y: f64) { } } @@ -100,7 +98,7 @@ impl ScreenSystem { if self.screens.is_empty() { return; } - // Update state for screens + // Update state for screens let len = self.screens.len(); for screen in &mut self.screens[..len - 1] { if screen.active { @@ -118,7 +116,7 @@ impl ScreenSystem { current.screen.on_active(renderer, ui_container); } - // Handle current + // Handle current current.screen.tick(delta, renderer, ui_container); } @@ -131,213 +129,35 @@ impl ScreenSystem { } } -pub fn new_button(renderer: &mut render::Renderer, x: f64, y: f64, w: f64, h: f64) -> ui::Batch { - let mut batch = ui::Batch::new(x, y, w, h); - - let texture = render::Renderer::get_texture(renderer.get_textures_ref(), "gui/widgets") - .relative(0.0, 66.0 / 256.0, 200.0 / 256.0, 20.0 / 256.0); - - // Corners - batch.add(ui::Image::new(texture.clone(), - 0.0, - 0.0, - 4.0, - 4.0, - 0.0, - 0.0, - 2.0 / 200.0, - 2.0 / 20.0, - 255, - 255, - 255)); - batch.add(ui::Image::new(texture.clone(), - w - 4.0, - 0.0, - 4.0, - 4.0, - 198.0 / 200.0, - 0.0, - 2.0 / 200.0, - 2.0 / 20.0, - 255, - 255, - 255)); - batch.add(ui::Image::new(texture.clone(), - 0.0, - h - 6.0, - 4.0, - 6.0, - 0.0, - 17.0 / 20.0, - 2.0 / 200.0, - 3.0 / 20.0, - 255, - 255, - 255)); - batch.add(ui::Image::new(texture.clone(), - w - 4.0, - h - 6.0, - 4.0, - 6.0, - 198.0 / 200.0, - 17.0 / 20.0, - 2.0 / 200.0, - 3.0 / 20.0, - 255, - 255, - 255)); - - // Widths - batch.add(ui::Image::new(texture.clone() - .relative(2.0 / 200.0, 0.0, 196.0 / 200.0, 2.0 / 20.0), - 4.0, - 0.0, - w - 8.0, - 4.0, - 0.0, - 0.0, - 1.0, - 1.0, - 255, - 255, - 255)); - batch.add(ui::Image::new(texture.clone().relative(2.0 / 200.0, - 17.0 / 20.0, - 196.0 / 200.0, - 3.0 / 20.0), - 4.0, - h - 6.0, - w - 8.0, - 6.0, - 0.0, - 0.0, - 1.0, - 1.0, - 255, - 255, - 255)); - - // Heights - batch.add(ui::Image::new(texture.clone().relative(0.0, 2.0 / 20.0, 2.0 / 200.0, 15.0 / 20.0), - 0.0, - 4.0, - 4.0, - h - 10.0, - 0.0, - 0.0, - 1.0, - 1.0, - 255, - 255, - 255)); - batch.add(ui::Image::new(texture.clone().relative(198.0 / 200.0, - 2.0 / 20.0, - 2.0 / 200.0, - 15.0 / 20.0), - w - 4.0, - 4.0, - 4.0, - h - 10.0, - 0.0, - 0.0, - 1.0, - 1.0, - 255, - 255, - 255)); - - // Center - batch.add(ui::Image::new(texture.clone().relative(2.0 / 200.0, - 2.0 / 20.0, - 196.0 / 200.0, - 15.0 / 20.0), - 4.0, - 4.0, - w - 8.0, - h - 10.0, - 0.0, - 0.0, - 1.0, - 1.0, - 255, - 255, - 255)); - - batch -} - pub fn new_button_text(renderer: &mut render::Renderer, val: &str, x: f64, y: f64, w: f64, h: f64) - -> (ui::Batch, ui::Text) { - let batch = new_button(renderer, x, y, w, h); + -> (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); - (batch, text) + (btn, text) } -pub fn button_action(ui_container: &mut ui::Container, - btn: ui::ElementRef, +pub fn button_action(ui_container: &mut ui::Container, + btn: ui::ElementRef, txt: Option>, - click: Option) { - let batch = ui_container.get_mut(&btn); - batch.add_hover_func(Rc::new(move |over, game, ui_container| { - let texture = render::Renderer::get_texture(game.renderer.get_textures_ref(), - "gui/widgets") - .relative(0.0, - (if over { - 86.0 - } else { - 66.0 - }) / 256.0, - 200.0 / 256.0, - 20.0 / 256.0); - - { - let batch = ui_container.get_mut(&btn); - for i in 0..batch.len() { - let img = batch.get_mut_at::(i); - match i { - _i @ 0 ...3 => img.set_texture(texture.clone()), - 4 => img.set_texture(texture.clone().relative(2.0 / 200.0, - 0.0, - 196.0 / 200.0, - 2.0 / 20.0)), - 5 => img.set_texture(texture.clone().relative(2.0 / 200.0, - 17.0 / 20.0, - 196.0 / 200.0, - 3.0 / 20.0)), - 6 => img.set_texture(texture.clone().relative(0.0, - 2.0 / 20.0, - 2.0 / 200.0, - 15.0 / 20.0)), - 7 => img.set_texture(texture.clone().relative(198.0 / 200.0, - 2.0 / 20.0, - 2.0 / 200.0, - 15.0 / 20.0)), - 8 => img.set_texture(texture.clone().relative(2.0 / 200.0, - 2.0 / 20.0, - 196.0 / 200.0, - 15.0 / 20.0)), - _ => unreachable!(), - } - } - } - let txt = txt.clone(); - if let Some(txt) = txt { - let text = ui_container.get_mut(&txt); - text.set_b(if over { - 160 - } else { - 255 - }); - } - })); - if let Some(click) = click { - batch.add_click_func(click); - } + click: F) { + let button = ui_container.get_mut(&btn); + button.add_hover_func(move |over, _, ui_container| { + let txt = txt.clone(); + if let Some(txt) = txt { + let text = ui_container.get_mut(&txt); + text.set_b(if over { + 160 + } else { + 255 + }); + } + }); + button.add_click_func(click); } diff --git a/src/screen/server_list.rs b/src/screen/server_list.rs index 07988fc..5e687c9 100644 --- a/src/screen/server_list.rs +++ b/src/screen/server_list.rs @@ -98,7 +98,7 @@ 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 entries and icons. let mut tex = renderer.get_textures_ref().write().unwrap(); for server in &mut elements.servers { server.collection.remove_all(ui_container); @@ -117,10 +117,10 @@ 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 + // 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 + // General gui icons let icons = render::Renderer::get_texture(renderer.get_textures_ref(), "gui/icons"); for svr in servers { @@ -129,7 +129,7 @@ impl ServerList { let solid = render::Renderer::get_texture(renderer.get_textures_ref(), "steven:solid"); - // Everything is attached to this + // Everything is attached to this let mut back = ui::Image::new(solid, 0.0, offset * 100.0, @@ -165,32 +165,32 @@ impl ServerList { }; server.collection.add(server.back.clone()); server.update_position(); - // Make whole entry interactable + // Make whole entry interactable { let back = ui_container.get_mut(&server.back); let back_ref = server.back.clone(); let address = address.clone(); - back.add_hover_func(Rc::new(move |over, _, ui_container| { + 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(Rc::new(move |game, _| { + back.add_click_func(move |game, _| { game.screen_sys.replace_screen(Box::new(super::connecting::Connecting::new(&address))); game.connect_to(&address); - })); + }); } - // Server name + // 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 + // Server icon let mut icon = ui::Image::new(default_icon.clone(), 5.0, 5.0, @@ -206,7 +206,7 @@ impl ServerList { icon.set_parent(&server.back); server.icon = server.collection.add(ui_container.add(icon)); - // Ping indicator + // Ping indicator let mut ping = ui::Image::new(icons.clone(), 5.0, 5.0, @@ -223,13 +223,13 @@ impl ServerList { ping.set_parent(&server.back); server.ping = server.collection.add(ui_container.add(ping)); - // Player count + // 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 + // Server's message of the day let mut motd = ui::Formatted::with_width_limit(renderer, Component::Text(TextComponent::new("Connecting.\ @@ -240,7 +240,7 @@ impl ServerList { motd.set_parent(&server.back); server.motd = server.collection.add(ui_container.add(motd)); - // Version information + // Version information let mut version = ui::Formatted::with_width_limit(renderer, Component::Text(TextComponent::new("")), @@ -251,7 +251,7 @@ impl ServerList { version.set_parent(&server.back); server.version = server.collection.add(ui_container.add(version)); - // Delete entry button + // 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); @@ -259,11 +259,11 @@ impl ServerList { let re = ui_container.add(del); txt.set_parent(&re); let tre = ui_container.add(txt); - super::button_action(ui_container, re.clone(), Some(tre.clone()), None); + super::button_action(ui_container, re.clone(), Some(tre.clone()), |_,_| {}); server.collection.add(re); server.collection.add(tre); - // Edit entry button + // 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); @@ -271,14 +271,14 @@ impl ServerList { let re = ui_container.add(edit); txt.set_parent(&re); let tre = ui_container.add(txt); - super::button_action(ui_container, re.clone(), Some(tre.clone()), None); + super::button_action(ui_container, re.clone(), Some(tre.clone()), |_,_|{}); server.collection.add(re); server.collection.add(tre); elements.servers.push(server); offset += 1.0; - // Don't block the main thread whilst pinging the server + // Don't block the main thread whilst pinging the server thread::spawn(move || { match protocol::Conn::new(&address).and_then(|conn| conn.do_status()) { Ok(res) => { @@ -329,7 +329,7 @@ impl super::Screen for ServerList { let logo = ui::logo::Logo::new(renderer.resources.clone(), renderer, ui_container); let mut elements = ui::Collection::new(); - // Refresh the server list + // Refresh the server list let (mut refresh, mut txt) = super::new_button_text(renderer, "Refresh", 300.0, @@ -345,13 +345,13 @@ impl super::Screen for ServerList { super::button_action(ui_container, re.clone(), Some(tre.clone()), - Some(Rc::new(move |_, _| { + move |_, _| { *nr.borrow_mut() = true; - }))); + }); elements.add(re); elements.add(tre); - // Add a new server to the list + // Add a new server to the list let (mut add, mut txt) = super::new_button_text(renderer, "Add", 200.0, @@ -363,12 +363,12 @@ impl super::Screen for ServerList { let re = ui_container.add(add); txt.set_parent(&re); let tre = ui_container.add(txt); - super::button_action(ui_container, re.clone(), Some(tre.clone()), None); + super::button_action(ui_container, re.clone(), Some(tre.clone()), |_, _|{}); elements.add(re); elements.add(tre); - // Options menu - let mut options = super::new_button(renderer, 5.0, 25.0, 40.0, 40.0); + // 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); @@ -388,11 +388,11 @@ impl super::Screen for ServerList { cog.set_parent(&re); cog.set_v_attach(ui::VAttach::Middle); cog.set_h_attach(ui::HAttach::Center); - super::button_action(ui_container, re.clone(), None, None); + super::button_action(ui_container, re.clone(), None, |_, _| {}); elements.add(re); elements.add(ui_container.add(cog)); - // Disclaimer + // Disclaimer let mut warn = ui::Text::new(renderer, "Not affiliated with Mojang/Minecraft", 5.0, @@ -404,7 +404,7 @@ impl super::Screen for ServerList { warn.set_h_attach(ui::HAttach::Right); elements.add(ui_container.add(warn)); - // If we are kicked from a server display the reason + // 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); @@ -443,7 +443,7 @@ impl super::Screen for ServerList { self.reload_server_list(renderer, ui_container); } fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { - // Clean up + // Clean up { let elements = self.elements.as_mut().unwrap(); elements.logo.remove(ui_container); @@ -468,7 +468,7 @@ impl super::Screen for ServerList { elements.logo.tick(renderer, ui_container); for s in &mut elements.servers { - // Animate the entries + // Animate the entries { let back = ui_container.get_mut(&s.back); let dy = s.y - back.get_y(); @@ -480,8 +480,8 @@ impl super::Screen for ServerList { } } - // Keep checking to see if the server has finished being - // pinged + // Keep checking to see if the server has finished being + // pinged if !s.done_ping { match s.recv.try_recv() { Ok(res) => { @@ -492,7 +492,7 @@ impl super::Screen for ServerList { } { let ping = ui_container.get_mut(&s.ping); - // Selects the icon for the given ping range + // 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, diff --git a/src/server.rs b/src/server.rs index 05ea3a1..c6c93c0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -47,10 +47,8 @@ impl Server { let packet = match conn.read_packet().unwrap() { protocol::packet::Packet::EncryptionRequest(val) => val, - protocol::packet::Packet::LoginDisconnect(val) => { - return Err(protocol::Error::Disconnect(val.reason)); - }, - val => panic!("Wrong packet: {:?}", val), + protocol::packet::Packet::LoginDisconnect(val) => return Err(protocol::Error::Disconnect(val.reason)), + val => return Err(protocol::Error::Err(format!("Wrong packet: {:?}", val))), }; unimplemented!(); diff --git a/src/ui/batch.rs b/src/ui/batch.rs index af85113..b177669 100644 --- a/src/ui/batch.rs +++ b/src/ui/batch.rs @@ -19,14 +19,14 @@ pub struct BatchRef { } ui_element!(Batch { - width: f64, - height: f64, + width: f64, + height: f64, - elements: Vec + elements: Vec, }); impl Batch { - base_impl!(); + base_impl!(); pub fn new(x: f64, y: f64, w: f64, h: f64) -> Batch { ui_create!(Batch { @@ -95,8 +95,8 @@ impl Batch { self.elements.len() } - lazy_field!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); + lazy_field!(width, f64, get_width, set_width); + lazy_field!(height, f64, get_height, set_height); } impl UIElement for Batch { diff --git a/src/ui/button.rs b/src/ui/button.rs new file mode 100644 index 0000000..92d166e --- /dev/null +++ b/src/ui/button.rs @@ -0,0 +1,136 @@ +// 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 + } + + pub fn get_size(&self) -> (f64, f64) { + (self.width, self.height) + } + + 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"), + } + } +} diff --git a/src/ui/formatted.rs b/src/ui/formatted.rs index b2ee1ea..69b1e01 100644 --- a/src/ui/formatted.rs +++ b/src/ui/formatted.rs @@ -13,19 +13,19 @@ // limitations under the License. ui_element!(Formatted { - val: format::Component, - width: f64, - height: f64, - scale_x: f64, - scale_y: f64, + val: format::Component, + width: f64, + height: f64, + scale_x: f64, + scale_y: f64, - text: Vec, - max_width: f64, - lines: usize + text: Vec, + max_width: f64, + lines: usize, }); impl Formatted { - base_impl!(); + base_impl!(); pub fn new(renderer: &mut render::Renderer, val: format::Component, @@ -126,10 +126,10 @@ impl Formatted { self.height * self.scale_y) } - 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!(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); } diff --git a/src/ui/image.rs b/src/ui/image.rs index b368951..cddfe63 100644 --- a/src/ui/image.rs +++ b/src/ui/image.rs @@ -13,23 +13,23 @@ // limitations under the License. ui_element!(Image { - texture: render::Texture, - width: f64, - height: f64, + texture: render::Texture, + width: f64, + height: f64, - t_x: f64, - t_y: f64, - t_width: f64, - t_height: f64, + t_x: f64, + t_y: f64, + t_width: f64, + t_height: f64, - r: u8, - g: u8, - b: u8, - a: u8 + r: u8, + g: u8, + b: u8, + a: u8, }); impl Image { - base_impl!(); + base_impl!(); pub fn new(texture: render::Texture, x: f64, @@ -108,18 +108,18 @@ impl Image { self.dirty = true; } - lazy_field!(width, f64, get_width, set_width); - lazy_field!(height, f64, get_height, set_height); + 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!(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); + 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 { diff --git a/src/ui/logo.rs b/src/ui/logo.rs index b9472bc..445dd75 100644 --- a/src/ui/logo.rs +++ b/src/ui/logo.rs @@ -148,7 +148,7 @@ impl Logo { pub fn tick(&mut self, renderer: &mut render::Renderer, ui_container: &mut ui::Container) { let now = time::now().to_timespec(); - // Splash text + // Splash text let text = ui_container.get_mut(&self.text); let text_index = (now.sec / 15) as isize % self.text_strings.len() as isize; if self.text_index != text_index { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index aa58ea3..f002f88 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -20,6 +20,7 @@ use std::rc::Rc; use rand; use render; use format; +use glutin::VirtualKeyCode; const SCALED_WIDTH: f64 = 854.0; const SCALED_HEIGHT: f64 = 480.0; @@ -29,135 +30,185 @@ pub enum Element { Batch(Batch), Text(Text), Formatted(Formatted), + TextBox(TextBox), + Button(Button), None, } -pub type ClickFunc = Rc; -pub type HoverFunc = Rc; +pub type ClickFunc = Fn(&mut ::Game, &mut Container); +pub type HoverFunc = Fn(bool, &mut ::Game, &mut Container); macro_rules! element_impl { - ($($name:ident),+) => ( + ($($name:ident),+) => ( impl Element { - fn get_click_funcs(&self) -> Vec { - match *self { - $( - Element::$name(ref val) => val.click_funcs.clone(), - )+ - _ => unimplemented!(), - } - } + fn can_focus(&self) -> bool { + match *self { + $( + Element::$name(ref val) => val.can_focus, + )+ + _ => unimplemented!(), + } + } - fn get_hover_funcs(&self) -> Vec { - match *self { - $( - Element::$name(ref val) => val.hover_funcs.clone(), - )+ - _ => unimplemented!(), - } - } + fn is_focused(&self) -> bool { + match *self { + $( + Element::$name(ref val) => val.focused, + )+ + _ => unimplemented!(), + } + } - fn should_call_hover(&mut self, new: bool) -> bool{ - match *self { - $( - Element::$name(ref mut val) => { - let ret = val.hovered != new; - val.hovered = new; - ret - }, - )+ - _ => unimplemented!(), - } - } + fn set_focused(&mut self, f: bool) { + match *self { + $( + Element::$name(ref mut val) => val.focused = f, + )+ + _ => unimplemented!(), + } + } - fn should_draw(&self) -> bool { - match *self { - $( - Element::$name(ref val) => val.should_draw, - )+ - _ => unimplemented!(), - } - } + fn key_press(&mut self, game: &mut ::Game, key: Option, raw: u8, down: bool) -> Vec> { + match *self { + $( + Element::$name(ref mut val) => val.key_press(game, key, raw, down), + )+ + _ => unimplemented!(), + } + } - fn get_parent(&self) -> Option { - match *self { - $( - Element::$name(ref val) => val.parent, - )+ - _ => 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_attachment(&self) -> (VAttach, HAttach) { - match *self { - $( - Element::$name(ref val) => (val.v_attach, val.h_attach), - )+ - _ => unimplemented!(), - } - } + fn get_click_funcs(&self) -> Vec> { + match *self { + $( + Element::$name(ref val) => val.click_funcs.clone(), + )+ + _ => unimplemented!(), + } + } - fn get_offset(&self) -> (f64, f64) { - match *self { - $( - Element::$name(ref val) => (val.x, val.y), - )+ - _ => unimplemented!(), - } - } + fn get_hover_funcs(&self) -> Vec> { + match *self { + $( + Element::$name(ref val) => val.hover_funcs.clone(), + )+ + _ => unimplemented!(), + } + } - fn get_size(&self) -> (f64, f64) { - match *self { - $( - Element::$name(ref val) => val.get_size(), - )+ - _ => 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 is_dirty(&self) -> bool { - match *self { - $( - Element::$name(ref val) => val.dirty, - )+ - _ => unimplemented!(), - } - } + fn should_draw(&self) -> bool { + match *self { + $( + Element::$name(ref val) => val.should_draw, + )+ + _ => unimplemented!(), + } + } - fn set_dirty(&mut self, dirty: bool) { - match *self { - $( - Element::$name(ref mut val) => val.dirty = dirty, - )+ - _ => unimplemented!(), - } - } + fn get_parent(&self) -> Option { + match *self { + $( + Element::$name(ref val) => val.parent, + )+ + _ => unimplemented!(), + } + } - fn update(&mut self, renderer: &mut render::Renderer) { - match *self { - $( - Element::$name(ref mut val) => val.update(renderer), - )+ - _ => unimplemented!(), - } - } + fn get_attachment(&self) -> (VAttach, HAttach) { + match *self { + $( + Element::$name(ref val) => (val.v_attach, val.h_attach), + )+ + _ => 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_offset(&self) -> (f64, f64) { + match *self { + $( + Element::$name(ref val) => (val.x, val.y), + )+ + _ => unimplemented!(), + } + } + + fn get_size(&self) -> (f64, f64) { + match *self { + $( + Element::$name(ref val) => val.get_size(), + )+ + _ => 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!(), + } + } } - ) + ) } element_impl!( - Image, - Batch, - Text, - Formatted + Image, + Batch, + Text, + Formatted, + TextBox, + Button ); pub enum Mode { @@ -337,8 +388,13 @@ impl Container { self.version = renderer.ui.version; } - // Borrow rules seem to prevent us from doing this in the first pass - // so we split it. + // Try to make sure we have a focus + if !self.elements.iter().any(|(_, ref e)| e.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(); @@ -361,8 +417,8 @@ impl Container { } let r = self.get_draw_region(e, sw, sh); if r.intersects(&SCREEN) { - // Mark this as dirty if any of its - // parents are dirty too. + // 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() { @@ -433,6 +489,80 @@ impl Container { } } + pub fn key_press(&mut self, game: &mut ::Game, key: Option, raw: u8, down: bool) { + if key == Some(VirtualKeyCode::Tab) { + if !down { + self.cycle_focus(); + } + return; + } + let mut callbacks = None; + for (_, e) in &mut self.elements { + if e.is_focused() { + callbacks = Some(e.key_press(game, key, raw, down)); + break; + } + } + if let Some(callbacks) = callbacks { + for cb in callbacks { + cb(game, self); + } + } + } + + pub fn key_type(&mut self, game: &mut ::Game, c: char) { + 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); + } + } + } + + 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) { + // 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(|v| v + 1).unwrap_or(0) % 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), @@ -473,93 +603,105 @@ 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: Option, _raw: u8, _down: bool) -> Vec> { + vec![] + } + + fn key_type(&mut self, _game: &mut ::Game, _c: char) -> Vec> { + vec![] + } } macro_rules! lazy_field { - ($name:ident, $t:ty, $get:ident, $set:ident) => ( - pub fn $get(&self) -> $t { - self.$name - } + ($name:ident, $t:ty, $get:ident, $set:ident) => ( + pub fn $get(&self) -> $t { + self.$name + } - pub fn $set(&mut self, val: $t) { - if self.$name != val { - self.$name = val; - self.dirty = true; - } - } - ) + pub fn $set(&mut self, val: $t) { + if self.$name != val { + self.$name = val; + self.dirty = true; + } + } + ) } macro_rules! ui_element { - ( - $name:ident { - $( - $field:ident : $field_ty:ty - ),+ - } - ) => ( - pub struct $name { - dirty: bool, - data: Vec, - parent: Option, - should_draw: bool, - layer: isize, - x: f64, - y: f64, - v_attach: VAttach, - h_attach: HAttach, - click_funcs: Vec, - hover_funcs: Vec, - hovered: bool, - $( - $field: $field_ty - ),+ - } - ) + ( + $name:ident { + $( + $field:ident : $field_ty:ty, + )* + } + ) => ( + pub struct $name { + dirty: bool, + data: Vec, + parent: Option, + should_draw: bool, + layer: isize, + x: f64, + y: f64, + v_attach: VAttach, + h_attach: HAttach, + click_funcs: Vec>, + hover_funcs: Vec>, + hovered: bool, + can_focus: bool, + focused: bool, + $( + $field: $field_ty, + )* + } + ) } macro_rules! base_impl { - () => ( - pub fn set_parent(&mut self, other: &ElementRef) { - self.parent = Some(other.inner); - self.dirty = true; - } + () => ( + pub fn set_parent(&mut self, other: &ElementRef) { + self.parent = Some(other.inner); + self.dirty = true; + } - pub fn add_click_func(&mut self, f: ClickFunc) { - self.click_funcs.push(f); - } + pub fn add_click_func(&mut self, f: F) { + self.click_funcs.push(Rc::new(f)); + } - pub fn add_hover_func(&mut self, f: HoverFunc) { - self.hover_funcs.push(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); - ) + lazy_field!(layer, isize, get_layer, set_layer); + lazy_field!(x, f64, get_x, set_x); + lazy_field!(y, f64, get_y, set_y); + lazy_field!(v_attach, VAttach, get_v_attach, set_v_attach); + lazy_field!(h_attach, HAttach, get_h_attach, set_h_attach); + ) } macro_rules! ui_create { - ($name:ident { - $($field:ident: $e:expr,)+ - }) => ( - $name { - dirty: true, - data: Vec::new(), + ($name:ident { + $($field:ident: $e:expr,)* + }) => ( + $name { + dirty: true, + data: Vec::new(), - parent: None, - should_draw: true, - layer: 0, - v_attach: VAttach::Top, - h_attach: HAttach::Left, - click_funcs: Vec::new(), - hover_funcs: Vec::new(), - hovered: false, - $($field: $e),+ - } - ) + 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,)* + } + ) } // Include instead of mod so we can access private parts. @@ -570,3 +712,5 @@ include!("image.rs"); include!("batch.rs"); include!("text.rs"); include!("formatted.rs"); +include!("textbox.rs"); +include!("button.rs"); diff --git a/src/ui/text.rs b/src/ui/text.rs index b88515f..60653dc 100644 --- a/src/ui/text.rs +++ b/src/ui/text.rs @@ -13,20 +13,20 @@ // 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 + 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!(); + base_impl!(); pub fn new(renderer: &render::Renderer, val: &str, @@ -117,14 +117,14 @@ impl Text { 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); + 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); } diff --git a/src/ui/textbox.rs b/src/ui/textbox.rs new file mode 100644 index 0000000..797b077 --- /dev/null +++ b/src/ui/textbox.rs @@ -0,0 +1,185 @@ +// 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 { + use std::mem; + 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 mut btn = mem::replace(&mut self.button, unsafe { mem::uninitialized() }).wrap(); + let reg = Container::get_draw_region_raw(&btn, sx, sy, r); + btn.set_dirty(true); + self.data.extend(btn.draw(renderer, ®, width, height, delta)); + mem::forget(mem::replace(&mut self.button, match btn { + Element::Button(btn)=> btn, + _ => unreachable!(), + })); + let mut txt = mem::replace(&mut self.text, unsafe { mem::uninitialized() }).wrap(); + let reg = Container::get_draw_region_raw(&txt, sx, sy, r); + txt.set_dirty(true); + self.data.extend(txt.draw(renderer, ®, width, height, delta)); + mem::forget(mem::replace(&mut self.text, match txt { + Element::Text(txt)=> txt, + _ => unreachable!(), + })); + } + &self.data + } + + pub fn get_size(&self) -> (f64, f64) { + (self.width, self.height) + } + + pub fn get_input(&self) -> &str { + &self.input + } + + 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: Option, _raw: u8, down: bool) -> Vec> { + if let Some(key) = key { + match (key, down) { + (VirtualKeyCode::Back, false) => {self.input.pop();}, + (VirtualKeyCode::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"), + } + } +}