diff --git a/Cargo.lock b/Cargo.lock index e572ded28b..323b04845d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2285,6 +2285,8 @@ version = "0.2.0" dependencies = [ "bincode 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "dot_vox 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/common/Cargo.toml b/common/Cargo.toml index 7c6a403538..7cbb23e2d8 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -10,6 +10,7 @@ sphynx = { git = "https://gitlab.com/veloren/sphynx.git", features = ["serde1"] specs = { version = "0.14", features = ["serde", "nightly"] } vek = { version = "0.9", features = ["serde"] } dot_vox = "4.0" +image = "0.21" threadpool = "1.7" mio = "0.6" mio-extras = "2.0" @@ -19,3 +20,4 @@ bincode = "1.0" log = "0.4" rand = "0.5" rayon = "1.0" +lazy_static = "1.3" diff --git a/common/src/assets/mod.rs b/common/src/assets/mod.rs index c5e2ea2eec..685cb5654b 100644 --- a/common/src/assets/mod.rs +++ b/common/src/assets/mod.rs @@ -1,50 +1,116 @@ -use std::env; -use std::fs; -use std::fs::File; -use std::io; -use std::io::prelude::*; +use dot_vox::DotVoxData; +use image::DynamicImage; +use lazy_static::lazy_static; +use std::{ + any::Any, + collections::HashMap, + fs::File, + io::Read, + sync::{Arc, RwLock}, +}; -fn try_load(name: &str) -> Option { - let basepaths = [ - // if it's stupid and it works.., +#[derive(Debug, Clone)] +pub enum Error { + /// An asset of a different type has already been loaded with this specifier + InvalidType, + /// Asset does not exist + NotFound(String), +} + +impl From> for Error { + fn from(_: Arc) -> Self { + Error::InvalidType + } +} + +impl From for Error { + fn from(err: std::io::Error) -> Self { + Error::NotFound(format!("{:?}", err)) + } +} + +lazy_static! { + static ref ASSETS: RwLock>> = + RwLock::new(HashMap::new()); +} + +/// Function used to load assets +/// loaded assets are cached in a global singleton hashmap +/// Example usage: +/// ```no_run +/// use image::DynamicImage; +/// use veloren_common::assets; +/// +/// let my_image = assets::load::("core.ui.backgrounds.city").unwrap(); +/// ``` +pub fn load(specifier: &str) -> Result, Error> { + Ok(ASSETS + .write() + .unwrap() + .entry(specifier.to_string()) + .or_insert(Arc::new(A::load(specifier)?)) + .clone() + .downcast()?) +} + +/// Function used to load assets that will panic if the asset is not found +/// Use this to load essential assets +/// loaded assets are cached in a global singleton hashmap +/// Example usage: +/// ```no_run +/// use image::DynamicImage; +/// use veloren_common::assets; +/// +/// let my_image = assets::load_expect::("core.ui.backgrounds.city"); +/// ``` +pub fn load_expect(specifier: &str) -> Arc { + load(specifier).expect(&format!("Failed loading essential asset: {}", specifier)) +} + +/// Asset Trait +pub trait Asset: Send + Sync + Sized { + fn load(specifier: &str) -> Result; +} + +impl Asset for DynamicImage { + fn load(specifier: &str) -> Result { + Ok(image::load_from_memory(load_from_path(specifier)?.as_slice()).unwrap()) + } +} + +impl Asset for DotVoxData { + fn load(specifier: &str) -> Result { + Ok(dot_vox::load_bytes(load_from_path(specifier)?.as_slice()).unwrap()) + } +} + +// TODO: System to load file from specifiers (eg "core.ui.backgrounds.city") +fn try_open_with_path(name: &str) -> Option { + debug!("Trying to access \"{}\"", name); + // TODO: don't do this? + // if it's stupid and it works.., + [ "assets".to_string(), - "../../assets".to_string(), "../assets".to_string(), /* optimizations */ + "../../assets".to_string(), + [env!("CARGO_MANIFEST_DIR"), "/../assets"].concat(), [env!("CARGO_MANIFEST_DIR"), "/assets"].concat(), [env!("CARGO_MANIFEST_DIR"), "/../../assets"].concat(), - [env!("CARGO_MANIFEST_DIR"), "/../assets"].concat(), "../../../assets".to_string(), [env!("CARGO_MANIFEST_DIR"), "/../../../assets"].concat(), - ]; - for bp in &basepaths { - let filename = [bp, name].concat(); - match File::open(&filename) { - Ok(f) => { - debug!("loading {} succedeed", filename); - return Some(f); - } - Err(e) => { - debug!("loading {} did not work with error: {}", filename, e); - } - }; - } - return None; + ] + .into_iter() + .map(|bp| [bp, name].concat()) + .find_map(|ref filename| File::open(filename).ok()) } -pub fn load(name: &str) -> Result, ()> { - return match try_load(name) { +pub fn load_from_path(name: &str) -> Result, Error> { + match try_open_with_path(name) { Some(mut f) => { - let mut content: Vec = vec![]; - f.read_to_end(&mut content); - info!("loaded asset successful: {}", name); + let mut content = Vec::::new(); + f.read_to_end(&mut content)?; Ok(content) } - None => { - warn!( - "Loading asset failed, wanted to load {} but could not load it, check debug log!", - name - ); - Err(()) - } - }; + None => Err(Error::NotFound(name.to_owned())), + } } diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index 10e52cabcb..5d71237f58 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -18,8 +18,8 @@ use self::cell::Cell; /// Figures are used to represent things like characters, NPCs, mobs, etc. pub type Segment = Dyna; -impl From for Segment { - fn from(dot_vox_data: DotVoxData) -> Self { +impl From<&DotVoxData> for Segment { + fn from(dot_vox_data: &DotVoxData) -> Self { if let Some(model) = dot_vox_data.models.get(0) { let palette = dot_vox_data .palette diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs new file mode 100644 index 0000000000..47002be63d --- /dev/null +++ b/voxygen/src/hud/bag.rs @@ -0,0 +1,117 @@ +use super::{font_ids::Fonts, img_ids::Imgs}; +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle, Scrollbar}, + widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +widget_ids! { + struct Ids { + bag_close, + bag_contents, + inv_alignment, + inv_grid_1, + inv_grid_2, + inv_scrollbar, + inv_slot_0, + map_title, + } +} + +#[derive(WidgetCommon)] +pub struct Bag<'a> { + inventory_space: u32, + + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> Bag<'a> { + pub fn new(inventory_space: u32, imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + inventory_space, + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +pub enum Event { + Close, +} + +impl<'a> Widget for Bag<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + // Contents + Image::new(self.imgs.bag_contents) + .w_h(68.0 * 4.0, 123.0 * 4.0) + .bottom_right_with_margins_on(ui.window, 60.0, 5.0) + .set(state.ids.bag_contents, ui); + + // Alignment for Grid + Rectangle::fill_with([58.0 * 4.0 - 5.0, 100.0 * 4.0], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.bag_contents, 11.0 * 4.0, 5.0 * 4.0) + .scroll_kids() + .scroll_kids_vertically() + .set(state.ids.inv_alignment, ui); + // Grid + Image::new(self.imgs.inv_grid) + .w_h(58.0 * 4.0, 111.0 * 4.0) + .mid_top_with_margin_on(state.ids.inv_alignment, 0.0) + .set(state.ids.inv_grid_1, ui); + Image::new(self.imgs.inv_grid) + .w_h(58.0 * 4.0, 111.0 * 4.0) + .mid_top_with_margin_on(state.ids.inv_alignment, 110.0 * 4.0) + .set(state.ids.inv_grid_2, ui); + Scrollbar::y_axis(state.ids.inv_alignment) + .thickness(5.0) + .rgba(0.33, 0.33, 0.33, 1.0) + .set(state.ids.inv_scrollbar, ui); + + if self.inventory_space > 0 { + // First Slot + Button::image(self.imgs.inv_slot) + .top_left_with_margins_on(state.ids.inv_grid_1, 4.0, 4.0) + .w_h(10.0 * 4.0, 10.0 * 4.0) + .set(state.ids.inv_slot_0, ui); + } + + // X-button + if Button::image(self.imgs.close_button) + .w_h(28.0, 28.0) + .hover_image(self.imgs.close_button_hover) + .press_image(self.imgs.close_button_press) + .top_right_with_margins_on(state.ids.bag_contents, 0.0, 0.0) + .set(state.ids.bag_close, ui) + .was_clicked() + { + Some(Event::Close) + } else { + None + } + } +} diff --git a/voxygen/src/hud/buttons.rs b/voxygen/src/hud/buttons.rs new file mode 100644 index 0000000000..1f0357f386 --- /dev/null +++ b/voxygen/src/hud/buttons.rs @@ -0,0 +1,245 @@ +use conrod_core::{ + widget::{self, Button, Image, Text}, + widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +use super::{font_ids::Fonts, img_ids::Imgs, small_window::SmallWindowType, Windows, TEXT_COLOR}; +use crate::ui::ToggleButton; + +widget_ids! { + struct Ids { + bag, + bag_text, + bag_show_map, + character_button, + character_button_bg, + map_button, + qlog_button, + qlog_button_bg, + settings_button, + social_button, + social_button_bg, + spellbook_button, + spellbook_button_bg, + } +} + +#[derive(WidgetCommon)] +pub struct Buttons<'a> { + open_windows: &'a Windows, + show_map: bool, + show_bag: bool, + + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> Buttons<'a> { + pub fn new( + open_windows: &'a Windows, + show_map: bool, + show_bag: bool, + imgs: &'a Imgs, + fonts: &'a Fonts, + ) -> Self { + Self { + open_windows, + show_map, + show_bag, + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +pub enum Event { + ToggleBag, + ToggleSettings, + ToggleMap, + ToggleSmall(SmallWindowType), + ToggleCharacter, +} + +impl<'a> Widget for Buttons<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + // Bag + if !self.show_map { + if self.show_bag + != ToggleButton::new(self.show_bag, self.imgs.bag, self.imgs.bag_open) + .bottom_right_with_margins_on(ui.window, 5.0, 5.0) + .hover_images(self.imgs.bag_hover, self.imgs.bag_open_hover) + .press_images(self.imgs.bag_press, self.imgs.bag_open_press) + .w_h(420.0 / 10.0, 480.0 / 10.0) + .set(state.ids.bag, ui) + { + return Some(Event::ToggleBag); + } + + Text::new("B") + .bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0) + .font_size(10) + .color(TEXT_COLOR) + .set(state.ids.bag_text, ui); + } else { + Image::new(self.imgs.bag) + .bottom_right_with_margins_on(ui.window, 5.0, 5.0) + .w_h(420.0 / 10.0, 480.0 / 10.0) + .set(state.ids.bag_show_map, ui); + Text::new("B") + .bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0) + .font_size(10) + .color(TEXT_COLOR) + .set(state.ids.bag_text, ui); + } + + // 0 Settings + if Button::image(self.imgs.settings) + .w_h(29.0, 25.0) + .bottom_right_with_margins_on(ui.window, 5.0, 57.0) + .hover_image(self.imgs.settings_hover) + .press_image(self.imgs.settings_press) + .label("N") + .label_font_size(10) + .label_color(TEXT_COLOR) + .label_y(conrod_core::position::Relative::Scalar(-7.0)) + .label_x(conrod_core::position::Relative::Scalar(10.0)) + .set(state.ids.settings_button, ui) + .was_clicked() + { + return Some(Event::ToggleSettings); + }; + + // 2 Map + if Button::image(self.imgs.map_button) + .w_h(22.0, 25.0) + .left_from(state.ids.social_button, 10.0) + .hover_image(self.imgs.map_hover) + .press_image(self.imgs.map_press) + .label("M") + .label_font_size(10) + .label_color(TEXT_COLOR) + .label_y(conrod_core::position::Relative::Scalar(-7.0)) + .label_x(conrod_core::position::Relative::Scalar(10.0)) + .set(state.ids.map_button, ui) + .was_clicked() + { + return Some(Event::ToggleMap); + }; + + // Other Windows can only be accessed, when Settings are closed. + // Opening Settings will close all other Windows including the Bag. + // Opening the Map won't close the windows displayed before. + Image::new(self.imgs.social_button) + .w_h(25.0, 25.0) + .left_from(state.ids.settings_button, 10.0) + .set(state.ids.social_button_bg, ui); + Image::new(self.imgs.spellbook_button) + .w_h(28.0, 25.0) + .left_from(state.ids.map_button, 10.0) + .set(state.ids.spellbook_button_bg, ui); + Image::new(self.imgs.character_button) + .w_h(27.0, 25.0) + .left_from(state.ids.spellbook_button, 10.0) + .set(state.ids.character_button_bg, ui); + Image::new(self.imgs.qlog_button) + .w_h(23.0, 25.0) + .left_from(state.ids.character_button, 10.0) + .set(state.ids.qlog_button_bg, ui); + + if !(*self.open_windows == Windows::Settings) && self.show_map == false { + // 1 Social + if Button::image(self.imgs.social_button) + .w_h(25.0, 25.0) + .left_from(state.ids.settings_button, 10.0) + .hover_image(self.imgs.social_hover) + .press_image(self.imgs.social_press) + .label("O") + .label_font_size(10) + .label_color(TEXT_COLOR) + .label_y(conrod_core::position::Relative::Scalar(-7.0)) + .label_x(conrod_core::position::Relative::Scalar(10.0)) + .set(state.ids.social_button, ui) + .was_clicked() + { + return Some(Event::ToggleSmall(SmallWindowType::Social)); + } + + // 3 Spellbook + if Button::image(self.imgs.spellbook_button) + .w_h(28.0, 25.0) + .left_from(state.ids.map_button, 10.0) + .hover_image(self.imgs.spellbook_hover) + .press_image(self.imgs.spellbook_press) + .label("P") + .label_font_size(10) + .label_color(TEXT_COLOR) + .label_y(conrod_core::position::Relative::Scalar(-7.0)) + .label_x(conrod_core::position::Relative::Scalar(10.0)) + .set(state.ids.spellbook_button, ui) + .was_clicked() + { + return Some(Event::ToggleSmall(SmallWindowType::Spellbook)); + } + + // 4 Char-Window + if Button::image(self.imgs.character_button) + .w_h(27.0, 25.0) + .left_from(state.ids.spellbook_button, 10.0) + .hover_image(self.imgs.character_hover) + .press_image(self.imgs.character_press) + .label("C") + .label_font_size(10) + .label_color(TEXT_COLOR) + .label_y(conrod_core::position::Relative::Scalar(-7.0)) + .label_x(conrod_core::position::Relative::Scalar(10.0)) + .set(state.ids.character_button, ui) + .was_clicked() + { + return Some(Event::ToggleCharacter); + } + + // 5 Quest-Log + if Button::image(self.imgs.qlog_button) + .w_h(23.0, 25.0) + .left_from(state.ids.character_button, 10.0) + .hover_image(self.imgs.qlog_hover) + .press_image(self.imgs.qlog_press) + .label("L") + .label_font_size(10) + .label_color(TEXT_COLOR) + .label_y(conrod_core::position::Relative::Scalar(-7.0)) + .label_x(conrod_core::position::Relative::Scalar(10.0)) + .set(state.ids.qlog_button, ui) + .was_clicked() + { + return Some(Event::ToggleSmall(SmallWindowType::QuestLog)); + } + } + + None + } +} diff --git a/voxygen/src/hud/character_window.rs b/voxygen/src/hud/character_window.rs new file mode 100644 index 0000000000..4b969ae325 --- /dev/null +++ b/voxygen/src/hud/character_window.rs @@ -0,0 +1,188 @@ +use super::{font_ids::Fonts, img_ids::Imgs, TEXT_COLOR, XP_COLOR}; +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle, Text}, + widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +widget_ids! { + pub struct Ids { + charwindow, + charwindow_bg, + charwindow_close, + charwindow_exp_progress_rectangle, + charwindow_exp_rectangle, + charwindow_frame, + charwindow_icon, + charwindow_rectangle, + charwindow_tab1, + charwindow_tab1_exp, + charwindow_tab1_expbar, + charwindow_tab1_level, + charwindow_tab1_statnames, + charwindow_tab1_stats, + charwindow_tab_bg, + charwindow_title, + } +} + +#[derive(WidgetCommon)] +pub struct CharacterWindow<'a> { + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> CharacterWindow<'a> { + pub fn new(imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub enum Event { + Close, +} + +impl<'a> Widget for CharacterWindow<'a> { + type State = Ids; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + Ids::new(id_gen) + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { id, state, ui, .. } = args; + + // TODO: Read from parameter / character struct + let xp_percentage = 0.4; + + // Frame + Image::new(self.imgs.window_frame) + .middle_of(id) + .top_left_with_margins_on(ui.window, 200.0, 215.0) + .w_h(107.0 * 4.0, 125.0 * 4.0) + .set(state.charwindow_frame, ui); + + // Icon + Image::new(self.imgs.charwindow_icon) + .w_h(40.0, 40.0) + .top_left_with_margins_on(state.charwindow_frame, 4.0, 4.0) + .set(state.charwindow_icon, ui); + + // X-Button + if Button::image(self.imgs.close_button) + .w_h(28.0, 28.0) + .hover_image(self.imgs.close_button_hover) + .press_image(self.imgs.close_button_press) + .top_right_with_margins_on(state.charwindow_frame, 12.0, 0.0) + .set(state.charwindow_close, ui) + .was_clicked() + { + return Some(Event::Close); + } + + // Title + Text::new("Character Name") // Add in actual Character Name + .mid_top_with_margin_on(state.charwindow_frame, 17.0) + .font_id(self.fonts.metamorph) + .font_size(14) + .color(TEXT_COLOR) + .set(state.charwindow_title, ui); + + // Tab BG + Image::new(self.imgs.charwindow_tab_bg) + .w_h(205.0, 412.0) + .mid_left_with_margin_on(state.charwindow_frame, -205.0) + .set(state.charwindow_tab_bg, ui); + + // Tab Rectangle + Rectangle::fill_with([192.0, 371.0], color::rgba(0.0, 0.0, 0.0, 0.8)) + .top_right_with_margins_on(state.charwindow_tab_bg, 20.0, 0.0) + .set(state.charwindow_rectangle, ui); + + // Tab Button + Button::image(self.imgs.charwindow_tab) + .w_h(65.0, 23.0) + .top_left_with_margins_on(state.charwindow_tab_bg, -18.0, 2.0) + .label("Stats") + .label_color(TEXT_COLOR) + .label_font_size(14) + .set(state.charwindow_tab1, ui); + + Text::new("1") //Add in actual Character Level + .mid_top_with_margin_on(state.charwindow_rectangle, 10.0) + .font_id(self.fonts.opensans) + .font_size(30) + .color(TEXT_COLOR) + .set(state.charwindow_tab1_level, ui); + + // Exp-Bar Background + Rectangle::fill_with([170.0, 10.0], color::BLACK) + .mid_top_with_margin_on(state.charwindow_rectangle, 50.0) + .set(state.charwindow_exp_rectangle, ui); + + // Exp-Bar Progress + Rectangle::fill_with([170.0 * (xp_percentage), 6.0], XP_COLOR) // 0.8 = Experience percentage + .mid_left_with_margin_on(state.charwindow_tab1_expbar, 1.0) + .set(state.charwindow_exp_progress_rectangle, ui); + + // Exp-Bar Foreground Frame + Image::new(self.imgs.progress_frame) + .w_h(170.0, 10.0) + .middle_of(state.charwindow_exp_rectangle) + .set(state.charwindow_tab1_expbar, ui); + + // Exp-Text + Text::new("120/170") // Shows the Exp / Exp to reach the next level + .mid_top_with_margin_on(state.charwindow_tab1_expbar, 10.0) + .font_id(self.fonts.opensans) + .font_size(15) + .color(TEXT_COLOR) + .set(state.charwindow_tab1_exp, ui); + + // Stats + Text::new( + "Stamina\n\ + \n\ + Strength\n\ + \n\ + Dexterity\n\ + \n\ + Intelligence", + ) + .top_left_with_margins_on(state.charwindow_rectangle, 100.0, 20.0) + .font_id(self.fonts.opensans) + .font_size(16) + .color(TEXT_COLOR) + .set(state.charwindow_tab1_statnames, ui); + + Text::new( + "1234\n\ + \n\ + 12312\n\ + \n\ + 12414\n\ + \n\ + 124124", + ) + .right_from(state.charwindow_tab1_statnames, 10.0) + .font_id(self.fonts.opensans) + .font_size(16) + .color(TEXT_COLOR) + .set(state.charwindow_tab1_stats, ui); + + None + } +} diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 9b7e08b147..da66630814 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -1,11 +1,9 @@ -use crate::ui::Ui; +use super::{font_ids::Fonts, img_ids::Imgs, TEXT_COLOR}; use conrod_core::{ - color, input::Key, position::Dimension, - text::font::Id as FontId, - widget::{Button, Id, List, Rectangle, Text, TextEdit}, - widget_ids, Colorable, Positionable, Sizeable, UiCell, Widget, + widget::{self, Button, Id, List, Rectangle, Text, TextEdit}, + widget_ids, Colorable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, }; use std::collections::VecDeque; @@ -18,49 +16,33 @@ widget_ids! { chat_arrow, } } -// Chat Behaviour: -// Input Window is only shown when the player presses Enter (graphical overlay to make it look better?) -// Instead of a Scrollbar it could have 3 Arrows at it's left side -// First two: Scroll the chat up and down -// Last one: Gets back to the bottom of the chat -// Consider making this a custom Widget -pub struct Chat { - ids: Ids, - messages: VecDeque, - input: String, - new_messages: bool, +#[derive(WidgetCommon)] +pub struct Chat<'a> { + new_messages: &'a mut VecDeque, + + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, } -impl Chat { - pub fn new(ui: &mut Ui) -> Self { - Chat { - ids: Ids::new(ui.id_generator()), - messages: VecDeque::new(), - input: String::new(), - new_messages: false, + +impl<'a> Chat<'a> { + pub fn new(new_messages: &'a mut VecDeque, imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + new_messages, + imgs, + fonts, + common: widget::CommonBuilder::default(), } } - pub fn input_box_id(&self) -> Id { - self.ids.input - } - pub fn new_message(&mut self, msg: String) { - self.messages.push_back(msg); - self.new_messages = true; - } - // Determine if the message box is scrolled to the bottom - // (i.e. the player is viewing new messages) - // If so scroll down when new messages are added - fn scroll_new_messages(&self, ui_widgets: &mut UiCell) { - // If previously scrolled to the bottom stay there - if self.scrolled_to_bottom(ui_widgets) { - self.scroll_to_bottom(ui_widgets); - } - } - fn scrolled_to_bottom(&self, ui_widgets: &UiCell) -> bool { + + fn scrolled_to_bottom(state: &State, ui: &UiCell) -> bool { // could be more efficient to cache result and update it when a scroll event has occurred instead of every frame - if let Some(scroll) = ui_widgets + if let Some(scroll) = ui .widget_graph() - .widget(self.ids.message_box) + .widget(state.ids.message_box) .and_then(|widget| widget.maybe_y_scroll_state) { scroll.offset >= scroll.offset_bounds.start @@ -68,50 +50,75 @@ impl Chat { false } } - fn scroll_to_bottom(&self, ui_widgets: &mut UiCell) { - ui_widgets.scroll_widget(self.ids.message_box, [0.0, std::f64::MAX]); - } - pub(super) fn update_layout( - &mut self, - ui_widgets: &mut UiCell, - font: FontId, - imgs: &super::Imgs, - ) -> Option { - // Maintain scrolling - if self.new_messages { - self.scroll_new_messages(ui_widgets); - self.new_messages = false; +} + +pub struct State { + messages: VecDeque, + input: String, + + ids: Ids, +} + +pub enum Event { + SendMessage(String), + Focus(Id), +} + +impl<'a> Widget for Chat<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + messages: VecDeque::new(), + input: "".to_owned(), + ids: Ids::new(id_gen), } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { id, state, ui, .. } = args; + + // Maintain scrolling + if !self.new_messages.is_empty() { + state.update(|s| s.messages.extend(self.new_messages.drain(..))); + ui.scroll_widget(state.ids.message_box, [0.0, std::f64::MAX]); + } + + let keyboard_capturer = ui.global_input().current.widget_capturing_keyboard; + let input_focused = keyboard_capturer == Some(state.ids.input); // Only show if it has the keyboard captured // Chat input with rectangle as background - let keyboard_captured = ui_widgets - .global_input() - .current - .widget_capturing_keyboard - .map_or(false, |id| id == self.ids.input); - if keyboard_captured { - let text_edit = TextEdit::new(&self.input) + if input_focused { + let text_edit = TextEdit::new(&state.input) .w(460.0) .restrict_to_height(false) + .color(TEXT_COLOR) .line_spacing(2.0) .font_size(15) - .font_id(font); - let y = match text_edit.get_y_dimension(ui_widgets) { + .font_id(self.fonts.opensans); + let y = match text_edit.get_y_dimension(ui) { Dimension::Absolute(y) => y + 6.0, _ => 0.0, }; Rectangle::fill([470.0, y]) .rgba(0.0, 0.0, 0.0, 0.8) - .bottom_left_with_margins_on(ui_widgets.window, 10.0, 10.0) + .bottom_left_with_margins_on(ui.window, 10.0, 10.0) .w(470.0) - .set(self.ids.input_bg, ui_widgets); + .set(state.ids.input_bg, ui); if let Some(str) = text_edit - .top_left_with_margins_on(self.ids.input_bg, 1.0, 1.0) - .set(self.ids.input, ui_widgets) + .top_left_with_margins_on(state.ids.input_bg, 1.0, 1.0) + .set(state.ids.input, ui) { - self.input = str.to_string(); - self.input.retain(|c| c != '\n'); + let mut input = str.to_owned(); + input.retain(|c| c != '\n'); + state.update(|s| s.input = input); } } @@ -119,29 +126,29 @@ impl Chat { Rectangle::fill([470.0, 174.0]) .rgba(0.0, 0.0, 0.0, 0.4) .and(|r| { - if keyboard_captured { - r.up_from(self.ids.input_bg, 0.0) + if input_focused { + r.up_from(state.ids.input_bg, 0.0) } else { - r.bottom_left_with_margins_on(ui_widgets.window, 10.0, 10.0) + r.bottom_left_with_margins_on(ui.window, 10.0, 10.0) } }) - .set(self.ids.message_box_bg, ui_widgets); - let (mut items, _) = List::flow_down(self.messages.len() + 1) - .top_left_of(self.ids.message_box_bg) + .set(state.ids.message_box_bg, ui); + let (mut items, _) = List::flow_down(state.messages.len() + 1) + .top_left_of(state.ids.message_box_bg) .w_h(470.0, 174.0) .scroll_kids_vertically() - .set(self.ids.message_box, ui_widgets); - while let Some(item) = items.next(ui_widgets) { + .set(state.ids.message_box, ui); + while let Some(item) = items.next(ui) { // This would be easier if conrod used the v-metrics from rusttype - let widget = if item.i < self.messages.len() { - let text = Text::new(&self.messages[item.i]) + let widget = if item.i < state.messages.len() { + let text = Text::new(&state.messages[item.i]) .font_size(15) - .font_id(font) + .font_id(self.fonts.opensans) .w(470.0) - .rgba(1.0, 1.0, 1.0, 1.0) + .color(TEXT_COLOR) .line_spacing(2.0); // Add space between messages - let y = match text.get_y_dimension(ui_widgets) { + let y = match text.get_y_dimension(ui) { Dimension::Absolute(y) => y + 2.0, _ => 0.0, }; @@ -149,38 +156,46 @@ impl Chat { } else { // Spacer at bottom of the last message so that it is not cut off // Needs to be larger than the space above - Text::new("").font_size(6).font_id(font).w(470.0) + Text::new("") + .font_size(6) + .font_id(self.fonts.opensans) + .w(470.0) }; - item.set(widget, ui_widgets); + item.set(widget, ui); } // Chat Arrow - if !self.scrolled_to_bottom(ui_widgets) { - if Button::image(imgs.chat_arrow) + // Check if already at bottom + if !Self::scrolled_to_bottom(state, ui) { + if Button::image(self.imgs.chat_arrow) .w_h(20.0, 20.0) - .hover_image(imgs.chat_arrow_mo) - .press_image(imgs.chat_arrow_press) - .bottom_right_with_margins_on(self.ids.message_box_bg, 0.0, -22.0) - .set(self.ids.chat_arrow, ui_widgets) + .hover_image(self.imgs.chat_arrow_mo) + .press_image(self.imgs.chat_arrow_press) + .bottom_right_with_margins_on(state.ids.message_box_bg, 0.0, -22.0) + .set(state.ids.chat_arrow, ui) .was_clicked() { - self.scroll_to_bottom(ui_widgets); + ui.scroll_widget(state.ids.message_box, [0.0, std::f64::MAX]); } } + // If the chat widget is focused return a focus event to pass the focus to the input box + if keyboard_capturer == Some(id) { + Some(Event::Focus(state.ids.input)) + } // If enter is pressed and the input box is not empty send the current message - if ui_widgets - .widget_input(self.ids.input) + else if ui + .widget_input(state.ids.input) .presses() .key() .any(|key_press| match key_press.key { - Key::Return if !self.input.is_empty() => true, + Key::Return if !state.input.is_empty() => true, _ => false, }) { - let new_message = self.input.clone(); - self.input.clear(); - Some(new_message) + let msg = state.input.clone(); + state.update(|s| s.input.clear()); + Some(Event::SendMessage(msg)) } else { None } diff --git a/voxygen/src/hud/esc_menu.rs b/voxygen/src/hud/esc_menu.rs new file mode 100644 index 0000000000..820df50cb6 --- /dev/null +++ b/voxygen/src/hud/esc_menu.rs @@ -0,0 +1,156 @@ +use conrod_core::{ + widget::{self, Button, Image}, + widget_ids, Labelable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +use super::{font_ids::Fonts, img_ids::Imgs, TEXT_COLOR}; + +widget_ids! { + struct Ids { + esc_bg, + fireplace, + menu_button_1, + menu_button_2, + menu_button_3, + menu_button_4, + menu_button_5, + } +} + +#[derive(WidgetCommon)] +pub struct EscMenu<'a> { + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> EscMenu<'a> { + pub fn new(imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +pub enum Event { + OpenSettings, + Logout, + Quit, + Close, +} + +impl<'a> Widget for EscMenu<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + Image::new(self.imgs.esc_bg) + .w_h(228.0, 450.0) + .middle_of(ui.window) + .set(state.ids.esc_bg, ui); + + Image::new(self.imgs.fireplace) + .w_h(180.0, 60.0) + .mid_top_with_margin_on(state.ids.esc_bg, 50.0) + .set(state.ids.fireplace, ui); + + // Settings + if Button::image(self.imgs.button) + .mid_top_with_margin_on(state.ids.esc_bg, 115.0) + .w_h(170.0, 50.0) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .label("Settings") + .label_y(conrod_core::position::Relative::Scalar(2.0)) + .label_color(TEXT_COLOR) + .label_font_size(17) + .set(state.ids.menu_button_1, ui) + .was_clicked() + { + return Some(Event::OpenSettings); + }; + // Controls + if Button::image(self.imgs.button) + .mid_top_with_margin_on(state.ids.esc_bg, 175.0) + .w_h(170.0, 50.0) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .label("Controls") + .label_y(conrod_core::position::Relative::Scalar(2.0)) + .label_color(TEXT_COLOR) + .label_font_size(17) + .set(state.ids.menu_button_2, ui) + .was_clicked() + { + // TODO: Show controls window + }; + // Servers + if Button::image(self.imgs.button) + .mid_top_with_margin_on(state.ids.esc_bg, 235.0) + .w_h(170.0, 50.0) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .label("Servers") + .label_y(conrod_core::position::Relative::Scalar(2.0)) + .label_color(TEXT_COLOR) + .label_font_size(17) + .set(state.ids.menu_button_3, ui) + .was_clicked() + { + // TODO: Show servers window (is needed in-game?) + }; + // Logout + if Button::image(self.imgs.button) + .mid_top_with_margin_on(state.ids.esc_bg, 295.0) + .w_h(170.0, 50.0) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .label("Logout") + .label_y(conrod_core::position::Relative::Scalar(2.0)) + .label_color(TEXT_COLOR) + .label_font_size(17) + .set(state.ids.menu_button_4, ui) + .was_clicked() + { + return Some(Event::Logout); + }; + // Quit + if Button::image(self.imgs.button) + .mid_top_with_margin_on(state.ids.esc_bg, 355.0) + .w_h(170.0, 50.0) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .label("Quit") + .label_y(conrod_core::position::Relative::Scalar(2.0)) + .label_color(TEXT_COLOR) + .label_font_size(17) + .set(state.ids.menu_button_5, ui) + .was_clicked() + { + return Some(Event::Quit); + }; + + None + } +} diff --git a/voxygen/src/hud/font_ids.rs b/voxygen/src/hud/font_ids.rs new file mode 100644 index 0000000000..8033800253 --- /dev/null +++ b/voxygen/src/hud/font_ids.rs @@ -0,0 +1,6 @@ +font_ids! { + pub struct Fonts { + opensans: "/voxygen/font/OpenSans-Regular.ttf", + metamorph: "/voxygen/font/Metamorphous-Regular.ttf", + } +} diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs new file mode 100644 index 0000000000..a773a977e6 --- /dev/null +++ b/voxygen/src/hud/img_ids.rs @@ -0,0 +1,157 @@ +use crate::ui::{BlankGraphic, ImageGraphic, VoxelGraphic}; + +image_ids! { + pub struct Imgs { + + // Bag + bag_contents: "/voxygen/element/frames/bag.vox", + inv_grid: "/voxygen/element/frames/inv_grid.vox", + inv_slot: "/voxygen/element/buttons/inv_slot.vox", + + // Buttons + mmap_closed: "/voxygen/element/buttons/button_mmap_closed.vox", + mmap_closed_hover: "/voxygen/element/buttons/button_mmap_closed_hover.vox", + mmap_closed_press: "/voxygen/element/buttons/button_mmap_closed_press.vox", + mmap_open: "/voxygen/element/buttons/button_mmap_open.vox", + mmap_open_hover: "/voxygen/element/buttons/button_mmap_open_hover.vox", + mmap_open_press: "/voxygen/element/buttons/button_mmap_open_press.vox", + + settings: "/voxygen/element/buttons/settings.vox", + settings_hover: "/voxygen/element/buttons/settings_hover.vox", + settings_press: "/voxygen/element/buttons/settings_press.vox", + + social_button: "/voxygen/element/buttons/social.vox", + social_hover: "/voxygen/element/buttons/social_hover.vox", + social_press: "/voxygen/element/buttons/social_press.vox", + + map_button: "/voxygen/element/buttons/map.vox", + map_hover: "/voxygen/element/buttons/map_hover.vox", + map_press: "/voxygen/element/buttons/map_press.vox", + + spellbook_button: "/voxygen/element/buttons/spellbook.vox", + spellbook_hover: "/voxygen/element/buttons/spellbook_hover.vox", + spellbook_press: "/voxygen/element/buttons/spellbook_press.vox", + + character_button: "/voxygen/element/buttons/character.vox", + character_hover: "/voxygen/element/buttons/character_hover.vox", + character_press: "/voxygen/element/buttons/character_press.vox", + + qlog_button: "/voxygen/element/buttons/qlog.vox", + qlog_hover: "/voxygen/element/buttons/qlog_hover.vox", + qlog_press: "/voxygen/element/buttons/qlog_press.vox", + + + // Close button + close_button: "/voxygen/element/buttons/x.vox", + close_button_hover: "/voxygen/element/buttons/x_hover.vox", + close_button_press: "/voxygen/element/buttons/x_press.vox", + + // Esc-Menu + fireplace: "/voxygen/element/misc_bg/fireplace.vox", + button: "/voxygen/element/buttons/button.vox", + button_hover: "/voxygen/element/buttons/button_hover.vox", + button_press: "/voxygen/element/buttons/button_press.vox", + + // MiniMap + mmap_frame: "/voxygen/element/frames/mmap.vox", + mmap_frame_closed: "/voxygen/element/frames/mmap_closed.vox", + + + // Missing: Buff Frame Animation .gif ?! we could do animation in ui.maintain, or in shader? + window_frame: "/voxygen/element/frames/window2.vox", + + // Settings Window + settings_frame_r: "/voxygen/element/frames/settings_r.vox", + settings_frame_l: "/voxygen/element/frames/settings_l.vox", + settings_button: "/voxygen/element/buttons/settings_button.vox", + settings_button_pressed: "/voxygen/element/buttons/settings_button_pressed.vox", + settings_button_hover: "/voxygen/element/buttons/settings_button_hover.vox", + settings_button_press: "/voxygen/element/buttons/settings_button_press.vox", + check: "/voxygen/element/buttons/check/no.vox", + check_mo: "/voxygen/element/buttons/check/no_mo.vox", + check_press: "/voxygen/element/buttons/check/press.vox", + check_checked: "/voxygen/element/buttons/check/yes.vox", + check_checked_mo: "/voxygen/element/buttons/check/yes_mo.vox", + slider: "/voxygen/element/slider/track.vox", + slider_indicator: "/voxygen/element/slider/indicator.vox", + + + // Map Window + map_frame_l: "/voxygen/element/frames/map_l.vox", + map_frame_r: "/voxygen/element/frames/map_r.vox", + map_frame_bl: "/voxygen/element/frames/map_bl.vox", + map_frame_br: "/voxygen/element/frames/map_br.vox", + + + // Chat-Arrows + chat_arrow: "/voxygen/element/buttons/arrow_down.vox", + chat_arrow_mo: "/voxygen/element/buttons/arrow_down_hover.vox", + chat_arrow_press: "/voxygen/element/buttons/arrow_down_press.vox", + + + + // Spell Book Window + spellbook_bg: "/voxygen/element/misc_bg/small_bg.png", + spellbook_icon: "/voxygen/element/icons/spellbook.png", + + // Bag + bag: "/voxygen/element/buttons/bag/closed.png", + bag_hover: "/voxygen/element/buttons/bag/closed_hover.png", + bag_press: "/voxygen/element/buttons/bag/closed_press.png", + bag_open: "/voxygen/element/buttons/bag/open.png", + bag_open_hover: "/voxygen/element/buttons/bag/open_hover.png", + bag_open_press: "/voxygen/element/buttons/bag/open_press.png", + + map_bg: "/voxygen/element/misc_bg/small_bg.png", + map_icon: "/voxygen/element/icons/map.png", + + grid_button: "/voxygen/element/buttons/border.png", + grid_button_hover: "/voxygen/element/buttons/border_mo.png", + grid_button_press: "/voxygen/element/buttons/border_press.png", + grid_button_open: "/voxygen/element/buttons/border_pressed.png", + + // Skillbar Module + sb_grid: "/voxygen/element/skill_bar/sbar_grid.png", + sb_grid_bg: "/voxygen/element/skill_bar/sbar_grid_bg.png", + l_click: "/voxygen/element/skill_bar/l.png", + r_click: "/voxygen/element/skill_bar/r.png", + mana_bar: "/voxygen/element/skill_bar/mana_bar.png", + health_bar: "/voxygen/element/skill_bar/health_bar.png", + xp_bar: "/voxygen/element/skill_bar/xp_bar.png", + + esc_bg: "/voxygen/element/frames/menu.png", + + window_frame_2: "/voxygen/element/frames/window_2.png", + + settings_bg: "/voxygen/element/frames/settings.png", + settings_icon: "/voxygen/element/icons/settings.png", + settings_button_mo: "/voxygen/element/buttons/blue_mo.png", + + // Char Window + charwindow: "/voxygen/element/misc_bg/charwindow.png", + charwindow_icon: "/voxygen/element/icons/charwindow.png", + charwindow_tab_bg: "/voxygen/element/frames/tab.png", + charwindow_tab: "/voxygen/element/buttons/tab.png", + charwindow_expbar: "/voxygen/element/misc_bg/small_bg.png", + progress_frame: "/voxygen/element/frames/progress_bar.png", + progress: "/voxygen/element/misc_bg/progress.png", + + // Quest-Log Window + questlog_bg: "/voxygen/element/misc_bg/small_bg.png", + questlog_icon: "/voxygen/element/icons/questlog.png", + + button_blue_mo: "/voxygen/element/buttons/blue_mo.png", + button_blue_press: "/voxygen/element/buttons/blue_press.png", + + // Window BG + window_bg: "/voxygen/element/misc_bg/window_bg.png", + + // Social Window + social_bg: "/voxygen/element/misc_bg/small_bg.png", + social_icon: "/voxygen/element/icons/social.png", + + + + blank: (), + } +} diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs new file mode 100644 index 0000000000..81b5ec55a5 --- /dev/null +++ b/voxygen/src/hud/map.rs @@ -0,0 +1,118 @@ +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle}, + widget_ids, Positionable, Sizeable, Widget, WidgetCommon, +}; + +use super::{font_ids::Fonts, img_ids::Imgs}; + +widget_ids! { + struct Ids { + map_frame, + map_bg, + map_icon, + map_close, + map_title, + map_frame_l, + map_frame_r, + map_frame_bl, + map_frame_br, + } +} + +#[derive(WidgetCommon)] +pub struct Map<'a> { + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> Map<'a> { + pub fn new(imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +pub enum Event { + Close, +} + +impl<'a> Widget for Map<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + // BG + Rectangle::fill_with([824.0, 976.0], color::TRANSPARENT) + .mid_top_with_margin_on(ui.window, 15.0) + .scroll_kids() + .scroll_kids_vertically() + .set(state.ids.map_bg, ui); + // Frame + Image::new(self.imgs.map_frame_l) + .top_left_with_margins_on(state.ids.map_bg, 0.0, 0.0) + .w_h(412.0, 488.0) + .set(state.ids.map_frame_l, ui); + Image::new(self.imgs.map_frame_r) + .right_from(state.ids.map_frame_l, 0.0) + .w_h(412.0, 488.0) + .set(state.ids.map_frame_r, ui); + Image::new(self.imgs.map_frame_br) + .down_from(state.ids.map_frame_r, 0.0) + .w_h(412.0, 488.0) + .set(state.ids.map_frame_br, ui); + Image::new(self.imgs.map_frame_bl) + .down_from(state.ids.map_frame_l, 0.0) + .w_h(412.0, 488.0) + .set(state.ids.map_frame_bl, ui); + + // Icon + Image::new(self.imgs.map_icon) + .w_h(224.0 / 3.0, 224.0 / 3.0) + .top_left_with_margins_on(state.ids.map_frame, -10.0, -10.0) + .set(state.ids.map_icon, ui); + + // Icon + Image::new(self.imgs.map_icon) + .w_h(224.0 / 3.0, 224.0 / 3.0) + .top_left_with_margins_on(state.ids.map_frame, -10.0, -10.0) + .set(state.ids.map_icon, ui); + + // X-Button + if Button::image(self.imgs.close_button) + .w_h(28.0, 28.0) + .hover_image(self.imgs.close_button_hover) + .press_image(self.imgs.close_button_press) + .top_right_with_margins_on(state.ids.map_frame_r, 0.0, 0.0) + .set(state.ids.map_close, ui) + .was_clicked() + { + return Some(Event::Close); + } + + None + } +} diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs new file mode 100644 index 0000000000..34cc17bd55 --- /dev/null +++ b/voxygen/src/hud/minimap.rs @@ -0,0 +1,115 @@ +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle, Text}, + widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +use super::{font_ids::Fonts, img_ids::Imgs, Show, TEXT_COLOR}; + +widget_ids! { + struct Ids { + mmap_frame, + mmap_frame_bg, + mmap_location, + mmap_button, + } +} + +#[derive(WidgetCommon)] +pub struct MiniMap<'a> { + show: &'a Show, + + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> MiniMap<'a> { + pub fn new(show: &'a Show, imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + show, + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +pub enum Event { + Toggle, +} + +impl<'a> Widget for MiniMap<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + if self.show.mini_map { + Image::new(self.imgs.mmap_frame) + .w_h(100.0 * 2.0, 100.0 * 2.0) + .top_right_with_margins_on(ui.window, 5.0, 5.0) + .set(state.ids.mmap_frame, ui); + + Rectangle::fill_with([92.0 * 2.0, 82.0 * 2.0], color::TRANSPARENT) + .mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 2.0 + 2.0) + .set(state.ids.mmap_frame_bg, ui); + } else { + Image::new(self.imgs.mmap_frame_closed) + .w_h(100.0 * 2.0, 11.0 * 2.0) + .top_right_with_margins_on(ui.window, 5.0, 5.0) + .set(state.ids.mmap_frame, ui); + } + + if Button::image(if self.show.mini_map { + self.imgs.mmap_open + } else { + self.imgs.mmap_closed + }) + .w_h(100.0 * 0.2, 100.0 * 0.2) + .hover_image(if self.show.mini_map { + self.imgs.mmap_open_hover + } else { + self.imgs.mmap_closed_hover + }) + .press_image(if self.show.mini_map { + self.imgs.mmap_open_press + } else { + self.imgs.mmap_closed_press + }) + .top_right_with_margins_on(state.ids.mmap_frame, 0.0, 0.0) + .set(state.ids.mmap_button, ui) + .was_clicked() + { + return Some(Event::Toggle); + } + + // Title + // Make it display the actual location + Text::new("Uncanny Valley") + .mid_top_with_margin_on(state.ids.mmap_frame, 3.0) + .font_size(14) + .color(TEXT_COLOR) + .set(state.ids.mmap_location, ui); + + None + } +} diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 9beae93afe..8a89eb6c6f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1,477 +1,88 @@ +mod bag; +mod buttons; +mod character_window; mod chat; +mod esc_menu; +mod font_ids; +mod img_ids; +mod map; +mod minimap; +mod settings_window; +mod skillbar; +mod small_window; + +use bag::Bag; +use buttons::Buttons; +use character_window::CharacterWindow; +use chat::Chat; +use esc_menu::EscMenu; +use font_ids::Fonts; +use img_ids::Imgs; +use map::Map; +use minimap::MiniMap; +use settings_window::SettingsWindow; +use skillbar::Skillbar; +use small_window::{SmallWindow, SmallWindowType}; use crate::{ render::Renderer, settings::{ControlSettings, Settings}, - ui::{self, ScaleMode, ToggleButton, Ui}, + ui::{ScaleMode, Ui}, window::{Event as WinEvent, Key, Window}, GlobalState, }; -use common::{assets, figure::Segment}; - use conrod_core::{ - color, - image::Id as ImgId, - text::{self, font::Id as FontId}, - widget::{Button, Image, Rectangle, Scrollbar, Text}, + color, graph, + widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; +use std::collections::VecDeque; + +const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0); +const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); +const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0); +const MANA_COLOR: Color = Color::Rgba(0.42, 0.41, 0.66, 1.0); widget_ids! { struct Ids { // Test bag_space_add, - inventorytest_button, - inventorytest_button_label, // Debug debug_bg, - debug_button, - debug_button_label, fps_counter, + // Game Version version, - // Bag and Inventory - bag, - bag_contents, - bag_close, - bag_map_open, - inv_alignment, - inv_grid_1, - inv_grid_2, - inv_scrollbar, - inv_slot_0, - inv_slot[], - - // Buttons - settings_button, - social_button, - map_button, - spellbook_button, - character_button, - qlog_button, - social_button_bg, - spellbook_button_bg, - character_button_bg, - qlog_button_bg, - bag_text, - mmap_button, - //help + // Help help, help_bg, - //ESC-Menu - esc_bg, - fireplace, - menu_button_1, - menu_button_2, - menu_button_3, - menu_button_4, - menu_button_5, - //Mini-Map - mmap_frame, - mmap_frame_bg, - mmap_location, - //Action-Bar - xp_bar, - l_click, - r_click, - health_bar, - mana_bar, - sb_grid_l, - sb_grid_r, - sb_grid_bg_l, - sb_grid_bg_r, - xp_bar_progress, - health_bar_color, - mana_bar_color, - // Level Display - level_text, - next_level_text, - //Window Frames + + // Window Frames window_frame_0, window_frame_1, window_frame_2, window_frame_3, window_frame_4, window_frame_5, - //0 Settings-Window - settings_bg, - settings_content, - settings_icon, - settings_button_mo, - settings_close, - settings_title, - settings_r, - settings_l, - settings_scrollbar, - controls_text, - controls_controls, - //Contents - button_help, + + // Contents button_help2, - show_help_label, - interface, - video, - sound, - gameplay, - controls, - rectangle, - //1 Social - social_frame, - social_bg, - social_icon, - social_close, - social_title, - //2 Map - map_frame, - map_bg, - map_icon, - map_close, - map_title, - map_frame_l, - map_frame_r, - map_frame_bl, - map_frame_br, - //3 Spellbook - spellbook_frame, - spellbook_bg, - spellbook_icon, - spellbook_close, - spellbook_title, - //4 Charwindow - charwindow_frame, - charwindow, - charwindow_bg, - charwindow_icon, - charwindow_close, - charwindow_title, - charwindow_tab_bg, - charwindow_tab1, - charwindow_tab1_title, - charwindow_tab1_level, - charwindow_tab1_exp, - charwindow_tab1_stats, - charwindow_tab1_statnames, - charwindow_tab1_stats_numbers, - charwindow_tab1_expbar, - charwindow_rectangle, - charwindow_exp_rectangle, - charwindow_exp_progress_rectangle, - //5 Quest-Log - questlog_frame, - questlog_bg, - questlog_icon, - questlog_close, - questlog_title, + + // External + chat, + map, + character_window, + minimap, + bag, + skillbar, + buttons, + esc_menu, + small_window, + settings_window, } } -// TODO: make macro to mimic widget_ids! for images ids or find another solution to simplify addition of new images. -pub(self) struct Imgs { - // Bag - bag: ImgId, - bag_hover: ImgId, - bag_press: ImgId, - bag_open: ImgId, - bag_open_hover: ImgId, - bag_open_press: ImgId, - bag_contents: ImgId, - inv_grid: ImgId, - inv_slot: ImgId, - - // Buttons - mmap_closed: ImgId, - mmap_closed_hover: ImgId, - mmap_closed_press: ImgId, - mmap_open: ImgId, - mmap_open_hover: ImgId, - mmap_open_press: ImgId, - - settings: ImgId, - settings_hover: ImgId, - settings_press: ImgId, - - social_button: ImgId, - social_hover: ImgId, - social_press: ImgId, - - map_button: ImgId, - map_hover: ImgId, - map_press: ImgId, - - spellbook_button: ImgId, - spellbook_hover: ImgId, - spellbook_press: ImgId, - - character_button: ImgId, - character_hover: ImgId, - character_press: ImgId, - - qlog_button: ImgId, - qlog_hover: ImgId, - qlog_press: ImgId, - - // Close button - close_button: ImgId, - close_button_hover: ImgId, - close_button_press: ImgId, - - // Menu - esc_bg: ImgId, - fireplace: ImgId, - button: ImgId, - button_hover: ImgId, - button_press: ImgId, - - // MiniMap - mmap_frame: ImgId, - mmap_frame_closed: ImgId, - - // SkillBar Module - sb_grid: ImgId, - sb_grid_bg: ImgId, - l_click: ImgId, - r_click: ImgId, - mana_bar: ImgId, - health_bar: ImgId, - xp_bar: ImgId, - - //Buff Frame(s) - //buff_frame: ImgId, - //buff_frame_bg: ImgId, - //buff_frame_red: ImgId, - //buff_frame_green: ImgId, - - //Missing: Buff Frame Animation - window_frame: ImgId, - window_frame_2: ImgId, - //Settings-Window - settings_frame_r: ImgId, - settings_frame_l: ImgId, - settings_button: ImgId, - settings_button_pressed: ImgId, - settings_button_hover: ImgId, - settings_button_press: ImgId, - settings_bg: ImgId, - settings_icon: ImgId, - settings_button_mo: ImgId, - check: ImgId, - check_mo: ImgId, - check_press: ImgId, - check_checked: ImgId, - check_checked_mo: ImgId, - slider: ImgId, - slider_indicator: ImgId, - button_blank: ImgId, - button_blue_mo: ImgId, - button_blue_press: ImgId, - window_bg: ImgId, - // Social-Window - social_bg: ImgId, - social_icon: ImgId, - // Map-Window - map_bg: ImgId, - map_icon: ImgId, - map_frame_l: ImgId, - map_frame_r: ImgId, - map_frame_bl: ImgId, - map_frame_br: ImgId, - // Spell Book Window - spellbook_bg: ImgId, - spellbook_icon: ImgId, - // Char Window - charwindow: ImgId, - charwindow_icon: ImgId, - charwindow_tab_bg: ImgId, - charwindow_tab: ImgId, - charwindow_expbar: ImgId, - progress_frame: ImgId, - progress: ImgId, - - // Buttons - grid_button: ImgId, - grid_button_hover: ImgId, - grid_button_press: ImgId, - grid_button_open: ImgId, - - // Quest-Log Window - questlog_bg: ImgId, - questlog_icon: ImgId, - //help - // Chat-Arrow - chat_arrow: ImgId, - chat_arrow_mo: ImgId, - chat_arrow_press: ImgId, -} -impl Imgs { - fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs { - let mut load_img = |filename, ui: &mut Ui| { - let fullpath: String = ["/voxygen/", filename].concat(); - let image = image::load_from_memory( - assets::load(fullpath.as_str()) - .expect("Error loading Main UI Image") - .as_slice(), - ) - .unwrap(); - ui.new_graphic(ui::Graphic::Image(image)) - }; - let mut load_vox = |filename, ui: &mut Ui| { - let fullpath: String = ["/voxygen/", filename].concat(); - let dot_vox = dot_vox::load_bytes( - assets::load(fullpath.as_str()) - .expect("Error loading Main UI .vox") - .as_slice(), - ) - .unwrap(); - ui.new_graphic(ui::Graphic::Voxel(Segment::from(dot_vox))) - }; - Imgs { - // Bag - bag: load_img("element/buttons/bag/closed.png", ui), - bag_hover: load_img("element/buttons/bag/closed_hover.png", ui), - bag_press: load_img("element/buttons/bag/closed_press.png", ui), - bag_open: load_img("element/buttons/bag/open.png", ui), - bag_open_hover: load_img("element/buttons/bag/open_hover.png", ui), - bag_open_press: load_img("element/buttons/bag/open_press.png", ui), - bag_contents: load_vox("element/frames/bag.vox", ui), - inv_grid: load_vox("element/frames/inv_grid.vox", ui), - inv_slot: load_vox("element/buttons/inv_slot.vox", ui), - - // Buttons - mmap_closed: load_vox("element/buttons/button_mmap_closed.vox", ui), - mmap_closed_hover: load_vox("element/buttons/button_mmap_closed_hover.vox", ui), - mmap_closed_press: load_vox("element/buttons/button_mmap_closed_press.vox", ui), - mmap_open: load_vox("element/buttons/button_mmap_open.vox", ui), - mmap_open_hover: load_vox("element/buttons/button_mmap_open_hover.vox", ui), - mmap_open_press: load_vox("element/buttons/button_mmap_open_press.vox", ui), - - settings: load_vox("element/buttons/settings.vox", ui), - settings_hover: load_vox("element/buttons/settings_hover.vox", ui), - settings_press: load_vox("element/buttons/settings_press.vox", ui), - - social_button: load_vox("element/buttons/social.vox", ui), - social_hover: load_vox("element/buttons/social_hover.vox", ui), - social_press: load_vox("element/buttons/social_press.vox", ui), - - map_button: load_vox("element/buttons/map.vox", ui), - map_hover: load_vox("element/buttons/map_hover.vox", ui), - map_press: load_vox("element/buttons/map_press.vox", ui), - - spellbook_button: load_vox("element/buttons/spellbook.vox", ui), - spellbook_hover: load_vox("element/buttons/spellbook_hover.vox", ui), - spellbook_press: load_vox("element/buttons/spellbook_press.vox", ui), - - character_button: load_vox("element/buttons/character.vox", ui), - character_hover: load_vox("element/buttons/character_hover.vox", ui), - character_press: load_vox("element/buttons/character_press.vox", ui), - - qlog_button: load_vox("element/buttons/qlog.vox", ui), - qlog_hover: load_vox("element/buttons/qlog_hover.vox", ui), - qlog_press: load_vox("element/buttons/qlog_press.vox", ui), - - grid_button: load_img("element/buttons/border.png", ui), - grid_button_hover: load_img("element/buttons/border_mo.png", ui), - grid_button_press: load_img("element/buttons/border_press.png", ui), - grid_button_open: load_img("element/buttons/border_pressed.png", ui), - - // Close button - close_button: load_vox("element/buttons/x.vox", ui), - close_button_hover: load_vox("element/buttons/x_hover.vox", ui), - close_button_press: load_vox("element/buttons/x_press.vox", ui), - - // Esc-Menu - esc_bg: load_img("element/frames/menu.png", ui), - fireplace: load_vox("element/misc_bg/fireplace.vox", ui), - button: load_vox("element/buttons/button.vox", ui), - button_hover: load_vox("element/buttons/button_hover.vox", ui), - button_press: load_vox("element/buttons/button_press.vox", ui), - - // MiniMap - mmap_frame: load_vox("element/frames/mmap.vox", ui), - mmap_frame_closed: load_vox("element/frames/mmap_closed.vox", ui), - - // Skillbar Module - sb_grid: load_img("element/skill_bar/sbar_grid.png", ui), - sb_grid_bg: load_img("element/skill_bar/sbar_grid_bg.png", ui), - l_click: load_img("element/skill_bar/l.png", ui), - r_click: load_img("element/skill_bar/r.png", ui), - mana_bar: load_img("element/skill_bar/mana_bar.png", ui), - health_bar: load_img("element/skill_bar/health_bar.png", ui), - xp_bar: load_img("element/skill_bar/xp_bar.png", ui), - - // Missing: Buff Frame Animation (.gif ?!) (we could do animation in ui.maintain(), or in shader?) - window_frame: load_vox("element/frames/window2.vox", ui), - window_frame_2: load_img("element/frames/window_2.png", ui), - - // Settings Window - settings_frame_r: load_vox("element/frames/settings_r.vox", ui), - settings_frame_l: load_vox("element/frames/settings_l.vox", ui), - settings_button: load_vox("element/buttons/settings_button.vox", ui), - settings_button_pressed: load_vox("element/buttons/settings_button_pressed.vox", ui), - settings_button_hover: load_vox("element/buttons/settings_button_hover.vox", ui), - settings_button_press: load_vox("element/buttons/settings_button_press.vox", ui), - settings_bg: load_img("element/frames/settings.png", ui), - settings_icon: load_img("element/icons/settings.png", ui), - settings_button_mo: load_img("element/buttons/blue_mo.png", ui), - check: load_vox("element/buttons/check/no.vox", ui), - check_mo: load_vox("element/buttons/check/no_mo.vox", ui), - check_press: load_vox("element/buttons/check/press.vox", ui), - check_checked: load_vox("element/buttons/check/yes.vox", ui), - check_checked_mo: load_vox("element/buttons/check/yes_mo.vox", ui), - slider: load_vox("element/slider/track.vox", ui), - slider_indicator: load_vox("element/slider/indicator.vox", ui), - button_blank: ui.new_graphic(ui::Graphic::Blank), - button_blue_mo: load_img("element/buttons/blue_mo.png", ui), - button_blue_press: load_img("element/buttons/blue_press.png", ui), - - // Window BG - window_bg: load_img("element/misc_bg/window_bg.png", ui), - - // Social Window - social_bg: load_img("element/misc_bg/small_bg.png", ui), - social_icon: load_img("element/icons/social.png", ui), - - // Map Window - map_bg: load_img("element/misc_bg/small_bg.png", ui), - map_icon: load_img("element/icons/map.png", ui), - map_frame_l: load_vox("element/frames/map_l.vox", ui), - map_frame_r: load_vox("element/frames/map_r.vox", ui), - map_frame_bl: load_vox("element/frames/map_bl.vox", ui), - map_frame_br: load_vox("element/frames/map_br.vox", ui), - - // Spell Book Window - spellbook_bg: load_img("element/misc_bg/small_bg.png", ui), - spellbook_icon: load_img("element/icons/spellbook.png", ui), - - // Char Window - charwindow: load_img("element/misc_bg/charwindow.png", ui), - charwindow_icon: load_img("element/icons/charwindow.png", ui), - charwindow_tab_bg: load_img("element/frames/tab.png", ui), - charwindow_tab: load_img("element/buttons/tab.png", ui), - charwindow_expbar: load_img("element/misc_bg/small_bg.png", ui), - progress_frame: load_img("element/frames/progress_bar.png", ui), - progress: load_img("element/misc_bg/progress.png", ui), - - // Quest-Log Window - questlog_bg: load_img("element/misc_bg/small_bg.png", ui), - questlog_icon: load_img("element/icons/questlog.png", ui), - - // Chat-Arrows - chat_arrow: load_vox("element/buttons/arrow_down.vox", ui), - chat_arrow_mo: load_vox("element/buttons/arrow_down_hover.vox", ui), - chat_arrow_press: load_vox("element/buttons/arrow_down_press.vox", ui), - } - } -} - -enum SettingsTab { - Interface, - Video, - Sound, - Gameplay, - Controls, -} - pub enum Event { SendMessage(String), Logout, @@ -481,1402 +92,51 @@ pub enum Event { // TODO: are these the possible layouts we want? // TODO: maybe replace this with bitflags // map not here because it currently is displayed over the top of other open windows -enum Windows { - Settings, // display settings window - CharacterAnd(Option), // show character window + optionally another - Small(Small), +#[derive(PartialEq)] +pub enum Windows { + Settings, // display settings window + CharacterAnd(Option), // show character window + optionally another + Small(SmallWindowType), None, } -#[derive(Clone, Copy)] -enum Small { - Spellbook, - Social, - Questlog, -} -pub struct Hud { - ui: Ui, - ids: Ids, - imgs: Imgs, - chat: chat::Chat, - font_metamorph: FontId, - font_opensans: FontId, - show_help: bool, - show_debug: bool, - bag_open: bool, - menu_open: bool, +pub struct Show { + ui: bool, + help: bool, + debug: bool, + bag: bool, + esc_menu: bool, open_windows: Windows, - map_open: bool, - mmap_open: bool, - show_ui: bool, - inventory_space: u32, - xp_percentage: f64, - hp_percentage: f64, - mana_percentage: f64, - inventorytest_button: bool, - settings_tab: SettingsTab, - settings: Settings, + map: bool, + inventory_test_button: bool, + mini_map: bool, } - -//#[inline] -//pub fn rgba_bytes(r: u8, g: u8, b: u8, a: f32) -> Color { -//Color::Rgba(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a) -//} - -impl Hud { - pub fn new(window: &mut Window, settings: Settings) -> Self { - let mut ui = Ui::new(window).unwrap(); - // TODO: adjust/remove this, right now it is used to demonstrate window scaling functionality - ui.scaling_mode(ScaleMode::RelativeToWindow([1920.0, 1080.0].into())); - // Generate ids - let ids = Ids::new(ui.id_generator()); - // Load images - let imgs = Imgs::new(&mut ui, window.renderer_mut()); - // Load fonts - let load_font = |filename, ui: &mut Ui| { - let fullpath: String = ["/voxygen/font", filename].concat(); - ui.new_font( - conrod_core::text::Font::from_bytes( - assets::load(fullpath.as_str()).expect("Error loading file"), - ) - .unwrap(), - ) - }; - let font_opensans = load_font("/OpenSans-Regular.ttf", &mut ui); - let font_metamorph = load_font("/Metamorphous-Regular.ttf", &mut ui); - // Chat box - let chat = chat::Chat::new(&mut ui); - - Self { - ui, - imgs, - ids, - chat, - settings_tab: SettingsTab::Interface, - show_help: false, - show_debug: true, - bag_open: false, - menu_open: false, - map_open: false, - mmap_open: false, - show_ui: true, - inventorytest_button: false, - inventory_space: 0, - open_windows: Windows::None, - font_metamorph, - font_opensans, - xp_percentage: 0.4, - hp_percentage: 1.0, - mana_percentage: 1.0, - settings: settings, - } - } - - fn update_layout(&mut self, tps: f64) -> Vec { - let mut events = Vec::new(); - let ref mut ui_widgets = self.ui.set_widgets(); - let version = env!("CARGO_PKG_VERSION"); - - const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); - const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0); - const MANA_COLOR: Color = Color::Rgba(0.42, 0.41, 0.66, 1.0); - const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0); - - // Don't show anything if the ui is toggled off - if !self.show_ui { - return events; - } - - // Display debug window - if self.show_debug { - // Alpha Version - Text::new(version) - .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) - .font_size(14) - .font_id(self.font_opensans) - .color(TEXT_COLOR) - .set(self.ids.version, ui_widgets); - Text::new(&format!("FPS: {:.1}", tps)) - .color(TEXT_COLOR) - .down_from(self.ids.version, 5.0) - .font_id(self.font_opensans) - .font_size(14) - .set(self.ids.fps_counter, ui_widgets); - } - - // Add Bag-Space Button - if self.inventorytest_button { - if Button::image(self.imgs.grid_button) - .w_h(100.0, 100.0) - .middle_of(ui_widgets.window) - .label("1 Up!") - .label_font_size(20) - .hover_image(self.imgs.grid_button_hover) - .press_image(self.imgs.grid_button_press) - .set(self.ids.bag_space_add, ui_widgets) - .was_clicked() - { - self.inventory_space = self.inventory_space + 1; - }; - } - - // Chat box - if let Some(msg) = self - .chat - .update_layout(ui_widgets, self.font_opensans, &self.imgs) - { - events.push(Event::SendMessage(msg)); - } - - // Help Text - if self.show_help { - Image::new(self.imgs.window_frame_2) - .top_left_with_margins_on(ui_widgets.window, 3.0, 3.0) - .w_h(300.0, 190.0) - .set(self.ids.help_bg, ui_widgets); - Text::new(get_help_text(&self.settings.controls).as_str()) - .color(TEXT_COLOR) - .top_left_with_margins_on(self.ids.help_bg, 20.0, 20.0) - .font_id(self.font_opensans) - .font_size(18) - .set(self.ids.help, ui_widgets); - // X-button - if Button::image(self.imgs.close_button) - .w_h(100.0 * 0.2, 100.0 * 0.2) - .hover_image(self.imgs.close_button_hover) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.help_bg, 8.0, 3.0) - .set(self.ids.button_help2, ui_widgets) - .was_clicked() - { - self.show_help = false; - }; - } - - // Minimap - - if self.mmap_open { - Image::new(self.imgs.mmap_frame) - .w_h(100.0 * 2.0, 100.0 * 2.0) - .top_right_with_margins_on(ui_widgets.window, 5.0, 5.0) - .set(self.ids.mmap_frame, ui_widgets); - - Rectangle::fill_with([92.0 * 2.0, 82.0 * 2.0], color::TRANSPARENT) - .mid_top_with_margin_on(self.ids.mmap_frame, 13.0 * 2.0 + 2.0) - .set(self.ids.mmap_frame_bg, ui_widgets); - } else { - Image::new(self.imgs.mmap_frame_closed) - .w_h(100.0 * 2.0, 11.0 * 2.0) - .top_right_with_margins_on(ui_widgets.window, 5.0, 5.0) - .set(self.ids.mmap_frame, ui_widgets); - }; - - if Button::image(if self.mmap_open { - self.imgs.mmap_open - } else { - self.imgs.mmap_closed - }) - .w_h(100.0 * 0.2, 100.0 * 0.2) - .hover_image(if self.mmap_open { - self.imgs.mmap_open_hover - } else { - self.imgs.mmap_closed_hover - }) - .press_image(if self.mmap_open { - self.imgs.mmap_open_press - } else { - self.imgs.mmap_closed_press - }) - .top_right_with_margins_on(self.ids.mmap_frame, 0.0, 0.0) - .set(self.ids.mmap_button, ui_widgets) - .was_clicked() - { - self.mmap_open = !self.mmap_open; - }; - - // Title - // Make it display the actual location - Text::new("Uncanny Valley") - .mid_top_with_margin_on(self.ids.mmap_frame, 3.0) - .font_size(14) - .color(TEXT_COLOR) - .set(self.ids.mmap_location, ui_widgets); - - // Buttons at Bag - - // 0 Settings - if Button::image(self.imgs.settings) - .w_h(29.0, 25.0) - .bottom_right_with_margins_on(ui_widgets.window, 5.0, 57.0) - .hover_image(self.imgs.settings_hover) - .press_image(self.imgs.settings_press) - .label(&format!("{:?}", self.settings.controls.settings)) - .label_font_size(10) - .label_font_id(self.font_metamorph) - .color(TEXT_COLOR) - .label_color(TEXT_COLOR) - .label_y(conrod_core::position::Relative::Scalar(-7.0)) - .label_x(conrod_core::position::Relative::Scalar(10.0)) - .set(self.ids.settings_button, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::Settings => Windows::None, - _ => Windows::Settings, - }; - self.bag_open = false; - }; - - // 2 Map - if Button::image(self.imgs.map_button) - .w_h(22.0, 25.0) - .left_from(self.ids.social_button, 10.0) - .hover_image(self.imgs.map_hover) - .press_image(self.imgs.map_press) - .label(&format!("{:?}", self.settings.controls.map)) - .label_font_size(10) - .label_font_id(self.font_metamorph) - .label_color(TEXT_COLOR) - .label_y(conrod_core::position::Relative::Scalar(-7.0)) - .label_x(conrod_core::position::Relative::Scalar(10.0)) - .set(self.ids.map_button, ui_widgets) - .was_clicked() - { - self.map_open = !self.map_open; - self.bag_open = false; - }; - - // Other Windows can only be accessed, when Settings are closed. - // Opening Settings will close all other Windows including the Bag. - // Opening the Map won't close the windows displayed before. - Image::new(self.imgs.social_button) - .w_h(25.0, 25.0) - .left_from(self.ids.settings_button, 10.0) - .set(self.ids.social_button_bg, ui_widgets); - Image::new(self.imgs.spellbook_button) - .w_h(28.0, 25.0) - .left_from(self.ids.map_button, 10.0) - .set(self.ids.spellbook_button_bg, ui_widgets); - Image::new(self.imgs.character_button) - .w_h(27.0, 25.0) - .left_from(self.ids.spellbook_button, 10.0) - .set(self.ids.character_button_bg, ui_widgets); - Image::new(self.imgs.qlog_button) - .w_h(23.0, 25.0) - .left_from(self.ids.character_button, 10.0) - .set(self.ids.qlog_button_bg, ui_widgets); - - if match self.open_windows { - Windows::Settings => false, - _ => true, - } && self.map_open == false - { - // 1 Social - if Button::image(self.imgs.social_button) - .w_h(25.0, 25.0) - .left_from(self.ids.settings_button, 10.0) - .hover_image(self.imgs.social_hover) - .press_image(self.imgs.social_press) - .label(&format!("{:?}", self.settings.controls.social)) - .label_font_size(10) - .label_font_id(self.font_metamorph) - .label_color(TEXT_COLOR) - .label_y(conrod_core::position::Relative::Scalar(-7.0)) - .label_x(conrod_core::position::Relative::Scalar(10.0)) - .set(self.ids.social_button, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::Small(Small::Social) => Windows::None, - Windows::None | Windows::Small(_) => Windows::Small(Small::Social), - Windows::CharacterAnd(small) => match small { - Some(Small::Social) => Windows::CharacterAnd(None), - _ => Windows::CharacterAnd(Some(Small::Social)), - }, - Windows::Settings => Windows::Settings, - }; - } - - // 3 Spellbook - if Button::image(self.imgs.spellbook_button) - .w_h(28.0, 25.0) - .left_from(self.ids.map_button, 10.0) - .hover_image(self.imgs.spellbook_hover) - .press_image(self.imgs.spellbook_press) - .label(&format!("{:?}", self.settings.controls.spellbook)) - .label_font_size(10) - .label_font_id(self.font_metamorph) - .label_color(TEXT_COLOR) - .label_y(conrod_core::position::Relative::Scalar(-7.0)) - .label_x(conrod_core::position::Relative::Scalar(10.0)) - .set(self.ids.spellbook_button, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::Small(Small::Spellbook) => Windows::None, - Windows::None | Windows::Small(_) => Windows::Small(Small::Spellbook), - Windows::CharacterAnd(small) => match small { - Some(Small::Spellbook) => Windows::CharacterAnd(None), - _ => Windows::CharacterAnd(Some(Small::Spellbook)), - }, - Windows::Settings => Windows::Settings, - }; - } - - // 4 Char-Window - if Button::image(self.imgs.character_button) - .w_h(27.0, 25.0) - .left_from(self.ids.spellbook_button, 10.0) - .hover_image(self.imgs.character_hover) - .press_image(self.imgs.character_press) - .label(&format!("{:?}", self.settings.controls.character_window)) - .label_font_size(10) - .label_font_id(self.font_metamorph) - .label_color(TEXT_COLOR) - .label_y(conrod_core::position::Relative::Scalar(-7.0)) - .label_x(conrod_core::position::Relative::Scalar(10.0)) - .set(self.ids.character_button, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::CharacterAnd(small) => match small { - Some(small) => Windows::Small(small), - None => Windows::None, - }, - Windows::Small(small) => Windows::CharacterAnd(Some(small)), - Windows::None => Windows::CharacterAnd(None), - Windows::Settings => Windows::Settings, - } - } - - // 5 Quest-Log - if Button::image(self.imgs.qlog_button) - .w_h(23.0, 25.0) - .left_from(self.ids.character_button, 10.0) - .hover_image(self.imgs.qlog_hover) - .press_image(self.imgs.qlog_press) - .label(&format!("{:?}", self.settings.controls.quest_log)) - .label_font_size(10) - .label_font_id(self.font_metamorph) - .label_color(TEXT_COLOR) - .label_y(conrod_core::position::Relative::Scalar(-7.0)) - .label_x(conrod_core::position::Relative::Scalar(10.0)) - .set(self.ids.qlog_button, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::Small(Small::Questlog) => Windows::None, - Windows::None | Windows::Small(_) => Windows::Small(Small::Questlog), - Windows::CharacterAnd(small) => match small { - Some(Small::Questlog) => Windows::CharacterAnd(None), - _ => Windows::CharacterAnd(Some(Small::Questlog)), - }, - Windows::Settings => Windows::Settings, - }; - } - } - - // Skillbar Module - - // Experience-Bar - Image::new(self.imgs.xp_bar) - .w_h(2688.0 / 6.0, 116.0 / 6.0) - .mid_bottom_of(ui_widgets.window) - .set(self.ids.xp_bar, ui_widgets); - - Rectangle::fill_with([406.0 * (self.xp_percentage), 5.0], XP_COLOR) // "W=406*[Exp. %]" - .top_left_with_margins_on(self.ids.xp_bar, 5.0, 21.0) - .set(self.ids.xp_bar_progress, ui_widgets); - - // Left Grid - Image::new(self.imgs.sb_grid) - .w_h(2240.0 / 12.0, 448.0 / 12.0) - .up_from(self.ids.xp_bar, 0.0) - .align_left_of(self.ids.xp_bar) - .set(self.ids.sb_grid_l, ui_widgets); - - Image::new(self.imgs.sb_grid_bg) - .w_h(2240.0 / 12.0, 448.0 / 12.0) - .middle_of(self.ids.sb_grid_l) - .set(self.ids.sb_grid_bg_l, ui_widgets); - - // Right Grid - Image::new(self.imgs.sb_grid) - .w_h(2240.0 / 12.0, 448.0 / 12.0) - .up_from(self.ids.xp_bar, 0.0) - .align_right_of(self.ids.xp_bar) - .set(self.ids.sb_grid_r, ui_widgets); - - Image::new(self.imgs.sb_grid_bg) - .w_h(2240.0 / 12.0, 448.0 / 12.0) - .middle_of(self.ids.sb_grid_r) - .set(self.ids.sb_grid_bg_r, ui_widgets); - - // Right and Left Click - Image::new(self.imgs.l_click) - .w_h(224.0 / 6.0, 320.0 / 6.0) - .right_from(self.ids.sb_grid_bg_l, 0.0) - .align_bottom_of(self.ids.sb_grid_bg_l) - .set(self.ids.l_click, ui_widgets); - - Image::new(self.imgs.r_click) - .w_h(224.0 / 6.0, 320.0 / 6.0) - .left_from(self.ids.sb_grid_bg_r, 0.0) - .align_bottom_of(self.ids.sb_grid_bg_r) - .set(self.ids.r_click, ui_widgets); - - // Health Bar - Image::new(self.imgs.health_bar) - .w_h(1120.0 / 6.0, 96.0 / 6.0) - .left_from(self.ids.l_click, 0.0) - .align_top_of(self.ids.l_click) - .set(self.ids.health_bar, ui_widgets); - - // Filling - Rectangle::fill_with([182.0 * (self.hp_percentage), 6.0], HP_COLOR) // "W=182.0 * [Health. %]" - .top_right_with_margins_on(self.ids.health_bar, 5.0, 0.0) - .set(self.ids.health_bar_color, ui_widgets); - - // Mana Bar - Image::new(self.imgs.mana_bar) - .w_h(1120.0 / 6.0, 96.0 / 6.0) - .right_from(self.ids.r_click, 0.0) - .align_top_of(self.ids.r_click) - .set(self.ids.mana_bar, ui_widgets); - - // Filling - Rectangle::fill_with([182.0 * (self.mana_percentage), 6.0], MANA_COLOR) // "W=182.0 * [Mana. %]" - .top_left_with_margins_on(self.ids.mana_bar, 5.0, 0.0) - .set(self.ids.mana_bar_color, ui_widgets); - - // Buffs/Debuffs - - // Buffs - - // Debuffs - - // Level Display - - // Insert actual Level here - Text::new("1") - .left_from(self.ids.xp_bar, -15.0) - .font_size(10) - .color(TEXT_COLOR) - .set(self.ids.level_text, ui_widgets); - - // Insert next Level here - Text::new("2") - .right_from(self.ids.xp_bar, -15.0) - .font_size(10) - .color(TEXT_COLOR) - .set(self.ids.next_level_text, ui_widgets); - - // Bag contents - if self.bag_open { - // Contents - Image::new(self.imgs.bag_contents) - .w_h(68.0 * 4.0, 123.0 * 4.0) - .bottom_right_with_margins_on(ui_widgets.window, 60.0, 5.0) - .set(self.ids.bag_contents, ui_widgets); - - // Alignment for Grid - Rectangle::fill_with([58.0 * 4.0 - 5.0, 100.0 * 4.0], color::TRANSPARENT) - .top_left_with_margins_on(self.ids.bag_contents, 11.0 * 4.0, 5.0 * 4.0) - .scroll_kids() - .scroll_kids_vertically() - .set(self.ids.inv_alignment, ui_widgets); - // Grid - Image::new(self.imgs.inv_grid) - .w_h(58.0 * 4.0, 111.0 * 4.0) - .mid_top_with_margin_on(self.ids.inv_alignment, 0.0) - .set(self.ids.inv_grid_1, ui_widgets); - Image::new(self.imgs.inv_grid) - .w_h(58.0 * 4.0, 111.0 * 4.0) - .mid_top_with_margin_on(self.ids.inv_alignment, 110.0 * 4.0) - .set(self.ids.inv_grid_2, ui_widgets); - Scrollbar::y_axis(self.ids.inv_alignment) - .thickness(5.0) - .rgba(0.33, 0.33, 0.33, 1.0) - .set(self.ids.inv_scrollbar, ui_widgets); - - // X-button - if Button::image(self.imgs.close_button) - .w_h(28.0, 28.0) - .hover_image(self.imgs.close_button_hover) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.bag_contents, 0.0, 0.0) - .set(self.ids.bag_close, ui_widgets) - .was_clicked() - { - self.bag_open = false; - } - - if self.inventory_space > 0 { - // First Slot - Button::image(self.imgs.inv_slot) - .top_left_with_margins_on(self.ids.inv_grid_1, 4.0, 4.0) - .w_h(10.0 * 4.0, 10.0 * 4.0) - .set(self.ids.inv_slot_0, ui_widgets); - } - } - - // Bag - if !self.map_open && self.show_ui { - self.bag_open = ToggleButton::new(self.bag_open, self.imgs.bag, self.imgs.bag_open) - .bottom_right_with_margins_on(ui_widgets.window, 5.0, 5.0) - .hover_images(self.imgs.bag_hover, self.imgs.bag_open_hover) - .press_images(self.imgs.bag_press, self.imgs.bag_open_press) - .w_h(420.0 / 10.0, 480.0 / 10.0) - .set(self.ids.bag, ui_widgets); - Text::new(&format!("{:?}", self.settings.controls.bag)) - .bottom_right_with_margins_on(self.ids.bag, 0.0, 0.0) - .font_size(10) - .font_id(self.font_metamorph) - .color(TEXT_COLOR) - .set(self.ids.bag_text, ui_widgets); - } else { - Image::new(self.imgs.bag) - .bottom_right_with_margins_on(ui_widgets.window, 5.0, 5.0) - .w_h(420.0 / 10.0, 480.0 / 10.0) - .set(self.ids.bag_map_open, ui_widgets); - Text::new(&format!("{:?}", self.settings.controls.bag)) - .bottom_right_with_margins_on(self.ids.bag, 0.0, 0.0) - .font_size(10) - .font_id(self.font_metamorph) - .set(self.ids.bag_text, ui_widgets); - } - - //Windows - - //Char Window will always appear at the left side. Other Windows either appear at the left side, - //or when the Char Window is opened they will appear right from it. - - // 0 Settings - - if let Windows::Settings = self.open_windows { - // Frame Alignment - Rectangle::fill_with([824.0, 488.0], color::TRANSPARENT) - .middle_of(ui_widgets.window) - .set(self.ids.settings_bg, ui_widgets); - // Frame - Image::new(self.imgs.settings_frame_l) - .top_left_with_margins_on(self.ids.settings_bg, 0.0, 0.0) - .w_h(412.0, 488.0) - .set(self.ids.settings_l, ui_widgets); - Image::new(self.imgs.settings_frame_r) - .right_from(self.ids.settings_l, 0.0) - .parent(self.ids.settings_bg) - .w_h(412.0, 488.0) - .set(self.ids.settings_r, ui_widgets); - // Content Alignment - Rectangle::fill_with([198.0 * 4.0, 97.0 * 4.0], color::TRANSPARENT) - .top_right_with_margins_on(self.ids.settings_r, 21.0 * 4.0, 4.0 * 4.0) - .scroll_kids() - .scroll_kids_vertically() - .set(self.ids.settings_content, ui_widgets); - Scrollbar::y_axis(self.ids.settings_content) - .thickness(5.0) - .rgba(0.33, 0.33, 0.33, 1.0) - .set(self.ids.settings_scrollbar, ui_widgets); - // X-Button - if Button::image(self.imgs.close_button) - .w_h(28.0, 28.0) - .hover_image(self.imgs.close_button_hover) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.settings_r, 0.0, 0.0) - .set(self.ids.settings_close, ui_widgets) - .was_clicked() - { - self.open_windows = Windows::None; - self.settings_tab = SettingsTab::Interface; - } - - // Title - Text::new("Settings") - .mid_top_with_margin_on(self.ids.settings_bg, 5.0) - .font_size(14) - .color(TEXT_COLOR) - .set(self.ids.settings_title, ui_widgets); - // Icon - //Image::new(self.imgs.settings_icon) - //.w_h(224.0 / 3.0, 224.0 / 3.0) - //.top_left_with_margins_on(self.ids.settings_bg, -10.0, -10.0) - //.set(self.ids.settings_icon, ui_widgets); - // TODO: Find out if we can remove this - - // 1 Interface//////////////////////////// - if Button::image(if let SettingsTab::Interface = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button - }) - .w_h(31.0 * 4.0, 12.0 * 4.0) - .hover_image(if let SettingsTab::Interface = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_hover - }) - .press_image(if let SettingsTab::Interface = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_press - }) - .top_left_with_margins_on(self.ids.settings_l, 8.0 * 4.0, 2.0 * 4.0) - .label("Interface") - .label_font_size(14) - .label_color(TEXT_COLOR) - .set(self.ids.interface, ui_widgets) - .was_clicked() - { - self.settings_tab = SettingsTab::Interface; - } - // Toggle Help - if let SettingsTab::Interface = self.settings_tab { - self.show_help = - ToggleButton::new(self.show_help, self.imgs.check, self.imgs.check_checked) - .w_h(288.0 / 24.0, 288.0 / 24.0) - .top_left_with_margins_on(self.ids.settings_content, 5.0, 5.0) - .hover_images(self.imgs.check_checked_mo, self.imgs.check_mo) - .press_images(self.imgs.check_press, self.imgs.check_press) - .set(self.ids.button_help, ui_widgets); - Text::new("Show Help") - .right_from(self.ids.button_help, 10.0) - .font_size(14) - .font_id(self.font_opensans) - .graphics_for(self.ids.button_help) - .color(TEXT_COLOR) - .set(self.ids.show_help_label, ui_widgets); - - self.inventorytest_button = ToggleButton::new( - self.inventorytest_button, - self.imgs.check, - self.imgs.check_checked, - ) - .w_h(288.0 / 24.0, 288.0 / 24.0) - .down_from(self.ids.button_help, 7.0) - .hover_images(self.imgs.check_checked_mo, self.imgs.check_mo) - .press_images(self.imgs.check_press, self.imgs.check_press) - .set(self.ids.inventorytest_button, ui_widgets); - - Text::new("Show Inventory Test Button") - .right_from(self.ids.inventorytest_button, 10.0) - .font_size(14) - .font_id(self.font_opensans) - .graphics_for(self.ids.inventorytest_button) - .color(TEXT_COLOR) - .set(self.ids.inventorytest_button_label, ui_widgets); - - self.show_debug = - ToggleButton::new(self.show_debug, self.imgs.check, self.imgs.check_checked) - .w_h(288.0 / 24.0, 288.0 / 24.0) - .down_from(self.ids.inventorytest_button, 7.0) - .hover_images(self.imgs.check_checked_mo, self.imgs.check_mo) - .press_images(self.imgs.check_press, self.imgs.check_press) - .set(self.ids.debug_button, ui_widgets); - - Text::new("Show Debug Window") - .right_from(self.ids.debug_button, 10.0) - .font_size(14) - .font_id(self.font_opensans) - .graphics_for(self.ids.debug_button) - .color(TEXT_COLOR) - .set(self.ids.debug_button_label, ui_widgets); - } - - // 2 Gameplay//////////////// - if Button::image(if let SettingsTab::Gameplay = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button - }) - .w_h(31.0 * 4.0, 12.0 * 4.0) - .hover_image(if let SettingsTab::Gameplay = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_hover - }) - .press_image(if let SettingsTab::Gameplay = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_press - }) - .right_from(self.ids.interface, 0.0) - .label("Gameplay") - .label_font_size(14) - .label_color(TEXT_COLOR) - .set(self.ids.gameplay, ui_widgets) - .was_clicked() - { - self.settings_tab = SettingsTab::Gameplay; - } - - // 3 Controls///////////////////// - if Button::image(if let SettingsTab::Controls = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button - }) - .w_h(31.0 * 4.0, 12.0 * 4.0) - .hover_image(if let SettingsTab::Controls = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_hover - }) - .press_image(if let SettingsTab::Controls = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_press - }) - .right_from(self.ids.gameplay, 0.0) - .label("Controls") - .label_font_size(14) - .label_color(TEXT_COLOR) - .set(self.ids.controls, ui_widgets) - .was_clicked() - { - self.settings_tab = SettingsTab::Controls; - } - if let SettingsTab::Controls = self.settings_tab { - Text::new( - "Free Cursor\n\ - Toggle Help Window\n\ - Toggle Interface\n\ - Toggle FPS and Debug Info\n\ - \n\ - \n\ - Move Forward\n\ - Move Left\n\ - Move Right\n\ - Move Backwards\n\ - \n\ - Jump\n\ - \n\ - Dodge\n\ - \n\ - Auto Walk\n\ - \n\ - Sheathe/Draw Weapons\n\ - \n\ - Put on/Remove Helmet\n\ - \n\ - \n\ - Basic Attack\n\ - Secondary Attack/Block/Aim\n\ - \n\ - \n\ - Skillbar Slot 1\n\ - Skillbar Slot 2\n\ - Skillbar Slot 3\n\ - Skillbar Slot 4\n\ - Skillbar Slot 5\n\ - Skillbar Slot 6\n\ - Skillbar Slot 7\n\ - Skillbar Slot 8\n\ - Skillbar Slot 9\n\ - Skillbar Slot 10\n\ - \n\ - \n\ - Pause Menu\n\ - Settings\n\ - Social\n\ - Map\n\ - Spellbook\n\ - Character\n\ - Questlog\n\ - Bag\n\ - \n\ - \n\ - \n\ - Send Chat Message\n\ - Scroll Chat\n\ - \n\ - \n\ - Chat commands: \n\ - \n\ - /alias [Name] - Change your Chat Name \n\ - /tp [Name] - Teleports you to another player - ", - ) - .color(TEXT_COLOR) - .top_left_with_margins_on(self.ids.settings_content, 5.0, 5.0) - .font_id(self.font_opensans) - .font_size(18) - .set(self.ids.controls_text, ui_widgets); - // TODO: Replace with buttons that show the actual keybind and allow the user to change it. - Text::new( - "TAB\n\ - F1\n\ - F2\n\ - F3\n\ - \n\ - \n\ - W\n\ - A\n\ - S\n\ - D\n\ - \n\ - SPACE\n\ - \n\ - ??\n\ - \n\ - ??\n\ - \n\ - ??\n\ - \n\ - ??\n\ - \n\ - \n\ - L-Click\n\ - R-Click\n\ - \n\ - \n\ - 1\n\ - 2\n\ - 3\n\ - 4\n\ - 5\n\ - 6\n\ - 7\n\ - 8\n\ - 9\n\ - 0\n\ - \n\ - \n\ - ESC\n\ - N\n\ - O\n\ - M\n\ - P\n\ - C\n\ - L\n\ - B\n\ - \n\ - \n\ - \n\ - ENTER\n\ - Mousewheel\n\ - \n\ - \n\ - \n\ - \n\ - \n\ - \n\ - ", - ) - .color(TEXT_COLOR) - .right_from(self.ids.controls_text, 0.0) - .font_id(self.font_opensans) - .font_size(18) - .set(self.ids.controls_controls, ui_widgets); - } - // 4 Video//////////////////////////////// - if Button::image(if let SettingsTab::Video = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button - }) - .w_h(31.0 * 4.0, 12.0 * 4.0) - .hover_image(if let SettingsTab::Video = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_hover - }) - .press_image(if let SettingsTab::Video = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_press - }) - .right_from(self.ids.controls, 0.0) - .label("Video") - .parent(self.ids.settings_r) - .label_font_size(14) - .label_color(TEXT_COLOR) - .set(self.ids.video, ui_widgets) - .was_clicked() - { - self.settings_tab = SettingsTab::Video; - } - - // 5 Sound/////////////////////////////// - if Button::image(if let SettingsTab::Sound = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button - }) - .w_h(31.0 * 4.0, 12.0 * 4.0) - .hover_image(if let SettingsTab::Sound = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_hover - }) - .press_image(if let SettingsTab::Sound = self.settings_tab { - self.imgs.settings_button_pressed - } else { - self.imgs.settings_button_press - }) - .right_from(self.ids.video, 0.0) - .parent(self.ids.settings_r) - .label("Sound") - .label_font_size(14) - .label_color(TEXT_COLOR) - .set(self.ids.sound, ui_widgets) - .was_clicked() - { - self.settings_tab = SettingsTab::Sound; - } - } - - if let Some((small, char_window_open)) = match self.open_windows { - Windows::Small(small) => Some((small, false)), - Windows::CharacterAnd(Some(small)) => Some((small, true)), - _ => None, - } { - // TODO: there is common code in each match arm, might be able to combine this - match small { - Small::Social => { - //Frame - if char_window_open { - Image::new(self.imgs.window_frame) - .right_from(self.ids.charwindow_frame, 20.0) - .w_h(107.0 * 4.0, 125.0 * 4.0) - .set(self.ids.social_frame, ui_widgets); - } else { - Image::new(self.imgs.window_frame) - .top_left_with_margins_on(ui_widgets.window, 200.0, 10.0) - .w_h(107.0 * 4.0, 125.0 * 4.0) - .set(self.ids.social_frame, ui_widgets); - } - - // Icon - Image::new(self.imgs.social_icon) - .w_h(40.0, 40.0) - .top_left_with_margins_on(self.ids.social_frame, 4.0, 4.0) - .set(self.ids.social_icon, ui_widgets); - - // Content alignment - Rectangle::fill_with([362.0, 418.0], color::TRANSPARENT) - .bottom_right_with_margins_on(self.ids.social_frame, 17.0, 17.0) - .scroll_kids() - .scroll_kids_vertically() - .set(self.ids.social_bg, ui_widgets); - - // X-Button - if Button::image(self.imgs.close_button) - .w_h(28.0, 28.0) - .hover_image(self.imgs.close_button_hover) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.social_frame, 12.0, 0.0) - .set(self.ids.social_close, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::Small(_) => Windows::None, - Windows::CharacterAnd(_) => Windows::CharacterAnd(None), - _ => Windows::Settings, - } - } - // Title - Text::new("Social") - .mid_top_with_margin_on(self.ids.social_frame, 17.0) - .font_id(self.font_metamorph) - .font_size(14) - .color(TEXT_COLOR) - .set(self.ids.social_title, ui_widgets); - } - Small::Spellbook => { - // Frame - if char_window_open { - Image::new(self.imgs.window_frame) - .right_from(self.ids.charwindow_frame, 20.0) - .w_h(107.0 * 4.0, 125.0 * 4.0) - .set(self.ids.spellbook_frame, ui_widgets); - } else { - Image::new(self.imgs.window_frame) - .top_left_with_margins_on(ui_widgets.window, 200.0, 10.0) - .w_h(107.0 * 4.0, 125.0 * 4.0) - .set(self.ids.spellbook_frame, ui_widgets); - } - - // Icon - Image::new(self.imgs.spellbook_icon) - .w_h(40.0, 40.0) - .top_left_with_margins_on(self.ids.spellbook_frame, 4.0, 4.0) - .set(self.ids.spellbook_icon, ui_widgets); - - // Content alignment - Rectangle::fill_with([362.0, 418.0], color::TRANSPARENT) - .bottom_right_with_margins_on(self.ids.spellbook_frame, 17.0, 17.0) - .scroll_kids() - .scroll_kids_vertically() - .set(self.ids.spellbook_bg, ui_widgets); - - // X-Button - if Button::image(self.imgs.close_button) - .w_h(14.0, 14.0) - .hover_image(self.imgs.close_button_hover) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.spellbook_frame, 12.0, 0.0) - .set(self.ids.spellbook_close, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::Small(_) => Windows::None, - Windows::CharacterAnd(_) => Windows::CharacterAnd(None), - _ => Windows::Settings, - } - } - // Title - Text::new("Spellbook") - .mid_top_with_margin_on(self.ids.spellbook_frame, 17.0) - .font_size(14) - .color(TEXT_COLOR) - .set(self.ids.spellbook_title, ui_widgets); - } - Small::Questlog => { - // Frame - if char_window_open { - Image::new(self.imgs.window_frame) - .right_from(self.ids.charwindow_frame, 20.0) - .w_h(107.0 * 4.0, 125.0 * 4.0) - .set(self.ids.questlog_frame, ui_widgets); - } else { - Image::new(self.imgs.window_frame) - .top_left_with_margins_on(ui_widgets.window, 200.0, 10.0) - .w_h(107.0 * 4.0, 125.0 * 4.0) - .set(self.ids.questlog_frame, ui_widgets); - } - - // Icon - Image::new(self.imgs.questlog_icon) - .w_h(40.0, 40.0) - .top_left_with_margins_on(self.ids.questlog_frame, 4.0, 4.0) - .set(self.ids.questlog_icon, ui_widgets); - - // Content alignment - Rectangle::fill_with([362.0, 418.0], color::TRANSPARENT) - .bottom_right_with_margins_on(self.ids.questlog_frame, 17.0, 17.0) - .scroll_kids() - .scroll_kids_vertically() - .set(self.ids.questlog_bg, ui_widgets); - - // X-Button - if Button::image(self.imgs.close_button) - .w_h(20.0, 20.0) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.questlog_frame, 17.0, 5.0) - .set(self.ids.questlog_close, ui_widgets) - .was_clicked() - { - self.open_windows = match self.open_windows { - Windows::Small(_) => Windows::None, - Windows::CharacterAnd(_) => Windows::CharacterAnd(None), - _ => Windows::Settings, - } - } - // Title - Text::new("Quest-Log") - .mid_top_with_margin_on(self.ids.questlog_frame, 17.0) - .color(TEXT_COLOR) - .font_size(14) - .set(self.ids.questlog_title, ui_widgets); - } - } - } - - // 4 Char-Window - if let Windows::CharacterAnd(small) = self.open_windows { - // Frame - Image::new(self.imgs.window_frame) - .top_left_with_margins_on(ui_widgets.window, 200.0, 215.0) - .w_h(1648.0 / 4.0, 1952.0 / 4.0) - .set(self.ids.charwindow_frame, ui_widgets); - - // BG - //Image::new(self.imgs.window_bg) - //.w_h(348.0, 404.0) - //.mid_top_with_margin_on(self.ids.charwindow_frame, 48.0) - //.set(self.ids.charwindow_bg, ui_widgets); - - // Overlay - //Image::new(self.imgs.charwindow) - //.middle_of(self.ids.charwindow_bg) - //.set(self.ids.charwindow, ui_widgets); - - // Icon - //Image::new(self.imgs.charwindow_icon) - //.w_h(224.0 / 3.0, 224.0 / 3.0) - //.top_left_with_margins_on(self.ids.charwindow_frame, -10.0, -10.0) - //.set(self.ids.charwindow_icon, ui_widgets); - - // X-Button - if Button::image(self.imgs.close_button) - .w_h(244.0 * 0.22 / 4.0, 244.0 * 0.22 / 4.0) - .hover_image(self.imgs.close_button_hover) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.charwindow_frame, 4.0, 4.0) - .set(self.ids.charwindow_close, ui_widgets) - .was_clicked() - { - self.open_windows = match small { - Some(small) => Windows::Small(small), - None => Windows::None, - } - } - - // Title - Text::new("Character Name") //Add in actual Character Name - .mid_top_with_margin_on(self.ids.charwindow_frame, 7.0) - .color(TEXT_COLOR) - .set(self.ids.charwindow_title, ui_widgets); - // Tab BG - Image::new(self.imgs.charwindow_tab_bg) - .w_h(205.0, 412.0) - .mid_left_with_margin_on(self.ids.charwindow_frame, -205.0) - .set(self.ids.charwindow_tab_bg, ui_widgets); - // Tab Rectangle - Rectangle::fill_with([192.0, 371.0], color::rgba(0.0, 0.0, 0.0, 0.8)) - .top_right_with_margins_on(self.ids.charwindow_tab_bg, 20.0, 0.0) - .set(self.ids.charwindow_rectangle, ui_widgets); - // Tab Button - Button::image(self.imgs.charwindow_tab) - .w_h(65.0, 23.0) - .top_left_with_margins_on(self.ids.charwindow_tab_bg, -18.0, 2.0) - .label("Stats") - .label_color(TEXT_COLOR) - .label_font_id(self.font_opensans) - .label_font_size(14) - .set(self.ids.charwindow_tab1, ui_widgets); - Text::new("1") //Add in actual Character Level - .mid_top_with_margin_on(self.ids.charwindow_rectangle, 10.0) - .font_id(self.font_opensans) - .font_size(30) - .color(TEXT_COLOR) - .set(self.ids.charwindow_tab1_level, ui_widgets); - // Exp-Bar Background - Rectangle::fill_with([170.0, 10.0], color::BLACK) - .mid_top_with_margin_on(self.ids.charwindow_rectangle, 50.0) - .set(self.ids.charwindow_exp_rectangle, ui_widgets); - // Exp-Bar Progress - Rectangle::fill_with([170.0 * (self.xp_percentage), 6.0], XP_COLOR) // 0.8 = Experience percantage - .mid_left_with_margin_on(self.ids.charwindow_tab1_expbar, 1.0) - .set(self.ids.charwindow_exp_progress_rectangle, ui_widgets); - // Exp-Bar Foreground Frame - Image::new(self.imgs.progress_frame) - .w_h(170.0, 10.0) - .middle_of(self.ids.charwindow_exp_rectangle) - .set(self.ids.charwindow_tab1_expbar, ui_widgets); - // Exp-Text - Text::new("120/170") // Shows the Exp / Exp to reach the next level - .mid_top_with_margin_on(self.ids.charwindow_tab1_expbar, 10.0) - .font_id(self.font_opensans) - .font_size(15) - .color(TEXT_COLOR) - .set(self.ids.charwindow_tab1_exp, ui_widgets); - - // Stats - Text::new( - "Stamina\n\ - \n\ - Strength\n\ - \n\ - Dexterity\n\ - \n\ - Intelligence", - ) - .top_left_with_margins_on(self.ids.charwindow_rectangle, 100.0, 20.0) - .font_id(self.font_opensans) - .font_size(16) - .color(TEXT_COLOR) - .set(self.ids.charwindow_tab1_statnames, ui_widgets); - - Text::new( - "1234\n\ - \n\ - 12312\n\ - \n\ - 12414\n\ - \n\ - 124124", - ) - .right_from(self.ids.charwindow_tab1_statnames, 10.0) - .font_id(self.font_opensans) - .font_size(16) - .color(TEXT_COLOR) - .set(self.ids.charwindow_tab1_stats, ui_widgets); - } - - // 2 Map - if self.map_open { - // BG - Rectangle::fill_with([824.0, 976.0], color::TRANSPARENT) - .mid_top_with_margin_on(ui_widgets.window, 15.0) - .scroll_kids() - .scroll_kids_vertically() - .set(self.ids.map_bg, ui_widgets); - // Frame - Image::new(self.imgs.map_frame_l) - .top_left_with_margins_on(self.ids.map_bg, 0.0, 0.0) - .w_h(412.0, 488.0) - .set(self.ids.map_frame_l, ui_widgets); - Image::new(self.imgs.map_frame_r) - .right_from(self.ids.map_frame_l, 0.0) - .w_h(412.0, 488.0) - .set(self.ids.map_frame_r, ui_widgets); - Image::new(self.imgs.map_frame_br) - .down_from(self.ids.map_frame_r, 0.0) - .w_h(412.0, 488.0) - .set(self.ids.map_frame_br, ui_widgets); - Image::new(self.imgs.map_frame_bl) - .down_from(self.ids.map_frame_l, 0.0) - .w_h(412.0, 488.0) - .set(self.ids.map_frame_bl, ui_widgets); - - // Icon - Image::new(self.imgs.map_icon) - .w_h(224.0 / 3.0, 224.0 / 3.0) - .top_left_with_margins_on(self.ids.map_frame, -10.0, -10.0) - .set(self.ids.map_icon, ui_widgets); - - // X-Button - if Button::image(self.imgs.close_button) - .w_h(28.0, 28.0) - .hover_image(self.imgs.close_button_hover) - .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.map_frame_r, 0.0, 0.0) - .set(self.ids.map_close, ui_widgets) - .was_clicked() - { - self.map_open = false; - } - // Title - //Text::new("Map") - //.mid_top_with_margin_on(self.ids.map_bg, -7.0) - //.font_size(14) - //.color(TEXT_COLOR) - //.set(self.ids.map_title, ui_widgets); - } - - // ESC-MENU - // Background - if self.menu_open { - Image::new(self.imgs.esc_bg) - .w_h(228.0, 450.0) - .middle_of(ui_widgets.window) - .set(self.ids.esc_bg, ui_widgets); - - Image::new(self.imgs.fireplace) - .w_h(180.0, 60.0) - .mid_top_with_margin_on(self.ids.esc_bg, 50.0) - .set(self.ids.fireplace, ui_widgets); - - // Settings - if Button::image(self.imgs.button) - .mid_top_with_margin_on(self.ids.esc_bg, 115.0) - .w_h(170.0, 50.0) - .hover_image(self.imgs.button_hover) - .press_image(self.imgs.button_press) - .label("Settings") - .label_y(conrod_core::position::Relative::Scalar(2.0)) - .label_color(TEXT_COLOR) - .label_font_size(17) - .set(self.ids.menu_button_1, ui_widgets) - .was_clicked() - { - self.menu_open = false; - self.open_windows = Windows::Settings; - }; - // Controls - if Button::image(self.imgs.button) - .mid_top_with_margin_on(self.ids.esc_bg, 175.0) - .w_h(170.0, 50.0) - .hover_image(self.imgs.button_hover) - .press_image(self.imgs.button_press) - .label("Controls") - .label_y(conrod_core::position::Relative::Scalar(2.0)) - .label_color(TEXT_COLOR) - .label_font_size(17) - .set(self.ids.menu_button_2, ui_widgets) - .was_clicked() - { - self.menu_open = false; - self.settings_tab = SettingsTab::Controls; - self.open_windows = Windows::Settings; - }; - // Servers - if Button::image(self.imgs.button) - .mid_top_with_margin_on(self.ids.esc_bg, 235.0) - .w_h(170.0, 50.0) - .hover_image(self.imgs.button_hover) - .press_image(self.imgs.button_press) - .label("Servers") - .label_y(conrod_core::position::Relative::Scalar(2.0)) - .label_color(TEXT_COLOR) - .label_font_size(17) - .set(self.ids.menu_button_3, ui_widgets) - .was_clicked() - { - //self.menu_open = false; - }; - // Logout - if Button::image(self.imgs.button) - .mid_top_with_margin_on(self.ids.esc_bg, 295.0) - .w_h(170.0, 50.0) - .hover_image(self.imgs.button_hover) - .press_image(self.imgs.button_press) - .label("Logout") - .label_y(conrod_core::position::Relative::Scalar(2.0)) - .label_color(TEXT_COLOR) - .label_font_size(17) - .set(self.ids.menu_button_4, ui_widgets) - .was_clicked() - { - events.push(Event::Logout); - }; - // Quit - if Button::image(self.imgs.button) - .mid_top_with_margin_on(self.ids.esc_bg, 355.0) - .w_h(170.0, 50.0) - .hover_image(self.imgs.button_hover) - .press_image(self.imgs.button_press) - .label("Quit") - .label_y(conrod_core::position::Relative::Scalar(2.0)) - .label_color(TEXT_COLOR) - .label_font_size(17) - .set(self.ids.menu_button_5, ui_widgets) - .was_clicked() - { - events.push(Event::Quit); - }; - } - - events - } - - pub fn new_message(&mut self, msg: String) { - self.chat.new_message(msg); - } - - fn toggle_menu(&mut self) { - self.menu_open = !self.menu_open; - } +impl Show { fn toggle_bag(&mut self) { - self.bag_open = !self.bag_open + self.bag = !self.bag } + fn toggle_map(&mut self) { - self.map_open = !self.map_open; - self.bag_open = false; + self.map = !self.map; + self.bag = false; } - fn toggle_questlog(&mut self) { + + fn toggle_mini_map(&mut self) { + self.mini_map = !self.mini_map; + } + + fn toggle_small(&mut self, target: SmallWindowType) { self.open_windows = match self.open_windows { - Windows::Small(Small::Questlog) => Windows::None, - Windows::None | Windows::Small(_) => Windows::Small(Small::Questlog), + Windows::Small(small) if small == target => Windows::None, + Windows::None | Windows::Small(_) => Windows::Small(target), Windows::CharacterAnd(small) => match small { - Some(Small::Questlog) => Windows::CharacterAnd(None), - _ => Windows::CharacterAnd(Some(Small::Questlog)), + Some(small) if small == target => Windows::CharacterAnd(None), + _ => Windows::CharacterAnd(Some(target)), }, Windows::Settings => Windows::Settings, }; } + fn toggle_charwindow(&mut self) { self.open_windows = match self.open_windows { Windows::CharacterAnd(small) => match small { @@ -1888,66 +148,302 @@ impl Hud { Windows::Settings => Windows::Settings, } } - fn toggle_social(&mut self) { - self.open_windows = match self.open_windows { - Windows::Small(Small::Social) => Windows::None, - Windows::None | Windows::Small(_) => Windows::Small(Small::Social), - Windows::CharacterAnd(small) => match small { - Some(Small::Social) => Windows::CharacterAnd(None), - _ => Windows::CharacterAnd(Some(Small::Social)), - }, - Windows::Settings => Windows::Settings, - }; - } - fn toggle_spellbook(&mut self) { - self.open_windows = match self.open_windows { - Windows::Small(Small::Spellbook) => Windows::None, - Windows::None | Windows::Small(_) => Windows::Small(Small::Spellbook), - Windows::CharacterAnd(small) => match small { - Some(Small::Spellbook) => Windows::CharacterAnd(None), - _ => Windows::CharacterAnd(Some(Small::Spellbook)), - }, - Windows::Settings => Windows::Settings, - }; - } + fn toggle_settings(&mut self) { self.open_windows = match self.open_windows { Windows::Settings => Windows::None, _ => Windows::Settings, }; - self.bag_open = false; + self.bag = false; } + fn toggle_help(&mut self) { - self.show_help = !self.show_help + self.help = !self.help } + fn toggle_ui(&mut self) { - self.show_ui = !self.show_ui; + self.ui = !self.ui; } fn toggle_windows(&mut self, global_state: &mut GlobalState) { - if self.bag_open - || self.menu_open - || self.map_open + if self.bag + || self.esc_menu + || self.map || match self.open_windows { Windows::None => false, _ => true, } { - self.bag_open = false; - self.menu_open = false; - self.map_open = false; + self.bag = false; + self.esc_menu = false; + self.map = false; self.open_windows = Windows::None; global_state.window.grab_cursor(true); } else { - self.menu_open = true; + self.esc_menu = true; global_state.window.grab_cursor(false); } } +} +pub struct Hud { + ui: Ui, + ids: Ids, + imgs: Imgs, + fonts: Fonts, + new_messages: VecDeque, + inventory_space: u32, + show: Show, + to_focus: Option>, + settings: Settings, +} + +impl Hud { + pub fn new(window: &mut Window, settings: Settings) -> Self { + let mut ui = Ui::new(window).unwrap(); + // TODO: adjust/remove this, right now it is used to demonstrate window scaling functionality + ui.scaling_mode(ScaleMode::RelativeToWindow([1920.0, 1080.0].into())); + // Generate ids + let ids = Ids::new(ui.id_generator()); + // Load images + let imgs = Imgs::load(&mut ui).expect("Failed to load images"); + // Load fonts + let fonts = Fonts::load(&mut ui).expect("Failed to load fonts"); + + Self { + ui, + imgs, + fonts, + ids, + new_messages: VecDeque::new(), + inventory_space: 0, + show: Show { + help: true, + debug: false, + bag: false, + esc_menu: false, + open_windows: Windows::None, + map: false, + ui: true, + inventory_test_button: false, + mini_map: false, + }, + to_focus: None, + settings, + } + } + + fn update_layout(&mut self, tps: f64) -> Vec { + let mut events = Vec::new(); + let ref mut ui_widgets = self.ui.set_widgets(); + let version = env!("CARGO_PKG_VERSION"); + + // Don't show anything if the UI is toggled off + if !self.show.ui { + return events; + } + + // Display debug window + if self.show.debug { + // Alpha Version + Text::new(version) + .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(self.ids.version, ui_widgets); + Text::new(&format!("FPS: {:.1}", tps)) + .color(TEXT_COLOR) + .down_from(self.ids.version, 5.0) + .font_id(self.fonts.opensans) + .font_size(14) + .set(self.ids.fps_counter, ui_widgets); + } + + // Add Bag-Space Button + if self.show.inventory_test_button { + if Button::image(self.imgs.grid_button) + .w_h(100.0, 100.0) + .middle_of(ui_widgets.window) + .label("1 Up!") + .label_font_size(20) + .hover_image(self.imgs.grid_button_hover) + .press_image(self.imgs.grid_button_press) + .set(self.ids.bag_space_add, ui_widgets) + .was_clicked() + { + self.inventory_space += 1; + }; + } + + // Help Text + if self.show.help { + Image::new(self.imgs.window_frame_2) + .top_left_with_margins_on(ui_widgets.window, 3.0, 3.0) + .w_h(300.0, 190.0) + .set(self.ids.help_bg, ui_widgets); + Text::new(get_help_text(&self.settings.controls).as_str()) + .color(TEXT_COLOR) + .top_left_with_margins_on(self.ids.help_bg, 20.0, 20.0) + .font_id(self.fonts.opensans) + .font_size(18) + .set(self.ids.help, ui_widgets); + // X-button + if Button::image(self.imgs.close_button) + .w_h(100.0 * 0.2, 100.0 * 0.2) + .hover_image(self.imgs.close_button_hover) + .press_image(self.imgs.close_button_press) + .top_right_with_margins_on(self.ids.help_bg, 8.0, 3.0) + .set(self.ids.button_help2, ui_widgets) + .was_clicked() + { + self.show.help = false; + }; + } + + // Bag button and icons near it + match Buttons::new( + &self.show.open_windows, + self.show.map, + self.show.bag, + &self.imgs, + &self.fonts, + ) + .set(self.ids.buttons, ui_widgets) + { + Some(buttons::Event::ToggleBag) => self.show.toggle_bag(), + Some(buttons::Event::ToggleSettings) => self.show.toggle_settings(), + Some(buttons::Event::ToggleCharacter) => self.show.toggle_charwindow(), + Some(buttons::Event::ToggleSmall(small)) => self.show.toggle_small(small), + Some(buttons::Event::ToggleMap) => self.show.toggle_map(), + None => {} + } + + // MiniMap + match MiniMap::new(&self.show, &self.imgs, &self.fonts).set(self.ids.minimap, ui_widgets) { + Some(minimap::Event::Toggle) => self.show.toggle_mini_map(), + None => {} + } + + // Bag contents + if self.show.bag { + match Bag::new(self.inventory_space, &self.imgs, &self.fonts) + .set(self.ids.bag, ui_widgets) + { + Some(bag::Event::Close) => self.show.bag = false, + None => {} + } + } + + // Skillbar + Skillbar::new(&self.imgs, &self.fonts).set(self.ids.skillbar, ui_widgets); + + // Chat box + match Chat::new(&mut self.new_messages, &self.imgs, &self.fonts) + .set(self.ids.chat, ui_widgets) + { + Some(chat::Event::SendMessage(message)) => { + events.push(Event::SendMessage(message)); + } + Some(chat::Event::Focus(focus_id)) => { + self.to_focus = Some(Some(focus_id)); + } + None => {} + } + self.new_messages = VecDeque::new(); + + //Windows + + //Char Window will always appear at the left side. Other Windows either appear at the left side, + //or when the Char Window is opened they will appear right from it. + + // Settings + if let Windows::Settings = self.show.open_windows { + match SettingsWindow::new(&self.show, &self.imgs, &self.fonts) + .set(self.ids.settings_window, ui_widgets) + { + Some(settings_window::Event::ToggleHelp) => self.show.toggle_help(), + Some(settings_window::Event::ToggleInventoryTestButton) => { + self.show.inventory_test_button = !self.show.inventory_test_button + } + Some(settings_window::Event::ToggleDebug) => self.show.debug = !self.show.debug, + Some(settings_window::Event::Close) => { + self.show.open_windows = Windows::None; + } + None => {} + } + } + + // Small Window + if let Windows::Small(small) | Windows::CharacterAnd(Some(small)) = self.show.open_windows { + match SmallWindow::new(small, &self.show, &self.imgs, &self.fonts) + .set(self.ids.small_window, ui_widgets) + { + Some(small_window::Event::Close) => { + self.show.open_windows = match self.show.open_windows { + Windows::Small(_) => Windows::None, + Windows::CharacterAnd(_) => Windows::CharacterAnd(None), + _ => Windows::Settings, + } + } + None => {} + } + } + + // Character Window + if let Windows::CharacterAnd(small) = self.show.open_windows { + match CharacterWindow::new(&self.imgs, &self.fonts) + .set(self.ids.character_window, ui_widgets) + { + Some(character_window::Event::Close) => { + self.show.open_windows = match small { + Some(small) => Windows::Small(small), + None => Windows::None, + } + } + None => {} + } + } + + // Map + if self.show.map { + match Map::new(&self.imgs, &self.fonts).set(self.ids.map, ui_widgets) { + Some(map::Event::Close) => self.show.map = false, + None => {} + } + } + + // Esc-menu + if self.show.esc_menu { + match EscMenu::new(&self.imgs, &self.fonts).set(self.ids.esc_menu, ui_widgets) { + Some(esc_menu::Event::OpenSettings) => { + self.show.esc_menu = false; + self.show.open_windows = Windows::Settings; + } + Some(esc_menu::Event::Close) => self.show.esc_menu = false, + Some(esc_menu::Event::Logout) => events.push(Event::Logout), + Some(esc_menu::Event::Quit) => events.push(Event::Quit), + None => {} + } + } + + events + } + + pub fn new_message(&mut self, msg: String) { + self.new_messages.push_back(msg); + } + + // Checks if a TextEdit widget has the keyboard captured fn typing(&self) -> bool { - match self.ui.widget_capturing_keyboard() { - Some(id) if id == self.chat.input_box_id() => true, - _ => false, + if let Some(id) = self.ui.widget_capturing_keyboard() { + self.ui + .widget_graph() + .widget(id) + .and_then(graph::Container::unique_widget_state::) + .is_some() + } else { + false } } @@ -1955,7 +451,7 @@ impl Hud { let cursor_grabbed = global_state.window.is_cursor_grabbed(); match event { WinEvent::Ui(event) => { - if (self.typing() && event.is_keyboard() && self.show_ui) + if (self.typing() && event.is_keyboard() && self.show.ui) || !(cursor_grabbed && event.is_keyboard_or_mouse()) { self.ui.handle_event(event); @@ -1963,16 +459,16 @@ impl Hud { true } WinEvent::KeyDown(Key::ToggleInterface) => { - self.toggle_ui(); + self.show.toggle_ui(); true } - _ if !self.show_ui => false, + _ if !self.show.ui => false, WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(), WinEvent::KeyDown(Key::Enter) => { self.ui.focus_widget(if self.typing() { None } else { - Some(self.chat.input_box_id()) + Some(self.ids.chat) }); true } @@ -1981,41 +477,41 @@ impl Hud { self.ui.focus_widget(None); } else { // Close windows on esc - self.toggle_windows(global_state); + self.show.toggle_windows(global_state); } true } WinEvent::KeyDown(key) if !self.typing() => match key { Key::Map => { - self.toggle_map(); + self.show.toggle_map(); true } Key::Bag => { - self.toggle_bag(); + self.show.toggle_bag(); true } Key::QuestLog => { - self.toggle_questlog(); + self.show.toggle_small(SmallWindowType::QuestLog); true } Key::CharacterWindow => { - self.toggle_charwindow(); + self.show.toggle_charwindow(); true } Key::Social => { - self.toggle_social(); + self.show.toggle_small(SmallWindowType::Social); true } Key::Spellbook => { - self.toggle_spellbook(); + self.show.toggle_small(SmallWindowType::Spellbook); true } Key::Settings => { - self.toggle_settings(); + self.show.toggle_settings(); true } Key::Help => { - self.toggle_help(); + self.show.toggle_help(); true } _ => false, @@ -2034,6 +530,9 @@ impl Hud { } pub fn maintain(&mut self, renderer: &mut Renderer, tps: f64) -> Vec { + if let Some(maybe_id) = self.to_focus.take() { + self.ui.focus_widget(maybe_id); + } let events = self.update_layout(tps); self.ui.maintain(renderer); events diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs new file mode 100644 index 0000000000..1a57cce990 --- /dev/null +++ b/voxygen/src/hud/settings_window.rs @@ -0,0 +1,484 @@ +use super::{font_ids::Fonts, img_ids::Imgs, TEXT_COLOR}; +use crate::{hud::Show, ui::ToggleButton}; +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle, Scrollbar, Text}, + widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +widget_ids! { + struct Ids { + + settings_content, + settings_icon, + settings_button_mo, + settings_close, + settings_title, + settings_r, + settings_l, + settings_scrollbar, + controls_text, + controls_controls, + button_help, + button_help2, + show_help_label, + gameplay, + controls, + rectangle, + debug_button, + debug_button_label, + interface, + inventory_test_button, + inventory_test_button_label, + settings_bg, + sound, + test, + video, + } +} + +enum SettingsTab { + Interface, + Video, + Sound, + Gameplay, + Controls, +} + +#[derive(WidgetCommon)] +pub struct SettingsWindow<'a> { + show: &'a Show, + + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> SettingsWindow<'a> { + pub fn new(show: &'a Show, imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + show, + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + settings_tab: SettingsTab, + + ids: Ids, +} + +pub enum Event { + ToggleHelp, + ToggleInventoryTestButton, + ToggleDebug, + Close, +} + +impl<'a> Widget for SettingsWindow<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + settings_tab: SettingsTab::Interface, + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + // Frame Alignment + Rectangle::fill_with([824.0, 488.0], color::TRANSPARENT) + .middle_of(ui.window) + .set(state.ids.settings_bg, ui); + // Frame + Image::new(self.imgs.settings_frame_l) + .top_left_with_margins_on(state.ids.settings_bg, 0.0, 0.0) + .w_h(412.0, 488.0) + .set(state.ids.settings_l, ui); + Image::new(self.imgs.settings_frame_r) + .right_from(state.ids.settings_l, 0.0) + .parent(state.ids.settings_bg) + .w_h(412.0, 488.0) + .set(state.ids.settings_r, ui); + // Content Alignment + Rectangle::fill_with([198.0 * 4.0, 97.0 * 4.0], color::TRANSPARENT) + .top_right_with_margins_on(state.ids.settings_r, 21.0 * 4.0, 4.0 * 4.0) + .scroll_kids() + .scroll_kids_vertically() + .set(state.ids.settings_content, ui); + Scrollbar::y_axis(state.ids.settings_content) + .thickness(5.0) + .rgba(0.33, 0.33, 0.33, 1.0) + .set(state.ids.settings_scrollbar, ui); + // X-Button + if Button::image(self.imgs.close_button) + .w_h(28.0, 28.0) + .hover_image(self.imgs.close_button_hover) + .press_image(self.imgs.close_button_press) + .top_right_with_margins_on(state.ids.settings_r, 0.0, 0.0) + .set(state.ids.settings_close, ui) + .was_clicked() + { + return Some(Event::Close); + } + + // Title + Text::new("Settings") + .mid_top_with_margin_on(state.ids.settings_bg, 5.0) + .font_size(14) + .color(TEXT_COLOR) + .set(state.ids.settings_title, ui); + + // Interface + if Button::image(if let SettingsTab::Interface = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button + }) + .w_h(31.0 * 4.0, 12.0 * 4.0) + .hover_image(if let SettingsTab::Interface = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_hover + }) + .press_image(if let SettingsTab::Interface = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_press + }) + .top_left_with_margins_on(state.ids.settings_l, 8.0 * 4.0, 2.0 * 4.0) + .label("Interface") + .label_font_size(14) + .label_color(TEXT_COLOR) + .set(state.ids.interface, ui) + .was_clicked() + { + state.update(|s| s.settings_tab = SettingsTab::Interface); + } + + if let SettingsTab::Interface = state.settings_tab { + // Help + let show_help = + ToggleButton::new(self.show.help, self.imgs.check, self.imgs.check_checked) + .w_h(288.0 / 24.0, 288.0 / 24.0) + .top_left_with_margins_on(state.ids.settings_content, 5.0, 5.0) + .hover_images(self.imgs.check_checked_mo, self.imgs.check_mo) + .press_images(self.imgs.check_press, self.imgs.check_press) + .set(state.ids.button_help, ui); + + if self.show.help != show_help { + return Some(Event::ToggleHelp); + } + + Text::new("Show Help") + .right_from(state.ids.button_help, 10.0) + .font_size(12) + .font_id(self.fonts.opensans) + .graphics_for(state.ids.button_help) + .color(TEXT_COLOR) + .set(state.ids.show_help_label, ui); + + // Inventory test + let inventory_test_button = ToggleButton::new( + self.show.inventory_test_button, + self.imgs.check, + self.imgs.check_checked, + ) + .w_h(288.0 / 24.0, 288.0 / 24.0) + .down_from(state.ids.button_help, 7.0) + .hover_images(self.imgs.check_checked_mo, self.imgs.check_mo) + .press_images(self.imgs.check_press, self.imgs.check_press) + .set(state.ids.inventory_test_button, ui); + + if self.show.inventory_test_button != inventory_test_button { + return Some(Event::ToggleInventoryTestButton); + } + + Text::new("Show Inventory Test Button") + .right_from(state.ids.inventory_test_button, 10.0) + .font_size(14) + .font_id(self.fonts.opensans) + .graphics_for(state.ids.inventory_test_button) + .color(TEXT_COLOR) + .set(state.ids.inventory_test_button_label, ui); + + // Debug + let show_debug = + ToggleButton::new(self.show.debug, self.imgs.check, self.imgs.check_checked) + .w_h(288.0 / 24.0, 288.0 / 24.0) + .down_from(state.ids.inventory_test_button, 7.0) + .hover_images(self.imgs.check_checked_mo, self.imgs.check_mo) + .press_images(self.imgs.check_press, self.imgs.check_press) + .set(state.ids.debug_button, ui); + + if self.show.debug != show_debug { + return Some(Event::ToggleDebug); + } + + Text::new("Show Debug Window") + .right_from(state.ids.debug_button, 10.0) + .font_size(14) + .font_id(self.fonts.opensans) + .graphics_for(state.ids.debug_button) + .color(TEXT_COLOR) + .set(state.ids.debug_button_label, ui); + } + + // 2 Gameplay//////////////// + if Button::image(if let SettingsTab::Gameplay = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button + }) + .w_h(31.0 * 4.0, 12.0 * 4.0) + .hover_image(if let SettingsTab::Gameplay = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_hover + }) + .press_image(if let SettingsTab::Gameplay = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_press + }) + .right_from(state.ids.interface, 0.0) + .label("Gameplay") + .label_font_size(14) + .label_color(TEXT_COLOR) + .set(state.ids.gameplay, ui) + .was_clicked() + { + state.update(|s| s.settings_tab = SettingsTab::Gameplay); + } + + // 3 Controls///////////////////// + if Button::image(if let SettingsTab::Controls = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button + }) + .w_h(31.0 * 4.0, 12.0 * 4.0) + .hover_image(if let SettingsTab::Controls = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_hover + }) + .press_image(if let SettingsTab::Controls = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_press + }) + .right_from(state.ids.gameplay, 0.0) + .label("Controls") + .label_font_size(14) + .label_color(TEXT_COLOR) + .set(state.ids.controls, ui) + .was_clicked() + { + state.update(|s| s.settings_tab = SettingsTab::Controls); + } + if let SettingsTab::Controls = state.settings_tab { + Text::new( + "Free Cursor\n\ + Toggle Help Window\n\ + Toggle Interface\n\ + Toggle FPS and Debug Info\n\ + \n\ + \n\ + Move Forward\n\ + Move Left\n\ + Move Right\n\ + Move Backwards\n\ + \n\ + Jump\n\ + \n\ + Dodge\n\ + \n\ + Auto Walk\n\ + \n\ + Sheathe/Draw Weapons\n\ + \n\ + Put on/Remove Helmet\n\ + \n\ + \n\ + Basic Attack\n\ + Secondary Attack/Block/Aim\n\ + \n\ + \n\ + Skillbar Slot 1\n\ + Skillbar Slot 2\n\ + Skillbar Slot 3\n\ + Skillbar Slot 4\n\ + Skillbar Slot 5\n\ + Skillbar Slot 6\n\ + Skillbar Slot 7\n\ + Skillbar Slot 8\n\ + Skillbar Slot 9\n\ + Skillbar Slot 10\n\ + \n\ + \n\ + Pause Menu\n\ + Settings\n\ + Social\n\ + Map\n\ + Spellbook\n\ + Character\n\ + Questlog\n\ + Bag\n\ + \n\ + \n\ + \n\ + Send Chat Message\n\ + Scroll Chat\n\ + \n\ + \n\ + Chat commands: \n\ + \n\ + /alias [Name] - Change your Chat Name \n\ + /tp [Name] - Teleports you to another player + ", + ) + .color(TEXT_COLOR) + .top_left_with_margins_on(state.ids.settings_content, 5.0, 5.0) + .font_id(self.fonts.opensans) + .font_size(18) + .set(state.ids.controls_text, ui); + // TODO: Replace with buttons that show the actual keybind and allow the user to change it. + Text::new( + "TAB\n\ + F1\n\ + F2\n\ + F3\n\ + \n\ + \n\ + W\n\ + A\n\ + S\n\ + D\n\ + \n\ + SPACE\n\ + \n\ + ??\n\ + \n\ + ??\n\ + \n\ + ??\n\ + \n\ + ??\n\ + \n\ + \n\ + L-Click\n\ + R-Click\n\ + \n\ + \n\ + 1\n\ + 2\n\ + 3\n\ + 4\n\ + 5\n\ + 6\n\ + 7\n\ + 8\n\ + 9\n\ + 0\n\ + \n\ + \n\ + ESC\n\ + N\n\ + O\n\ + M\n\ + P\n\ + C\n\ + L\n\ + B\n\ + \n\ + \n\ + \n\ + ENTER\n\ + Mousewheel\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + ", + ) + .color(TEXT_COLOR) + .right_from(state.ids.controls_text, 0.0) + .font_id(self.fonts.opensans) + .font_size(18) + .set(state.ids.controls_controls, ui); + } + // 4 Video//////////////////////////////// + if Button::image(if let SettingsTab::Video = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button + }) + .w_h(31.0 * 4.0, 12.0 * 4.0) + .hover_image(if let SettingsTab::Video = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_hover + }) + .press_image(if let SettingsTab::Video = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_press + }) + .right_from(state.ids.controls, 0.0) + .label("Video") + .parent(state.ids.settings_r) + .label_font_size(14) + .label_color(TEXT_COLOR) + .set(state.ids.video, ui) + .was_clicked() + { + state.update(|s| s.settings_tab = SettingsTab::Video); + } + + // 5 Sound/////////////////////////////// + if Button::image(if let SettingsTab::Sound = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button + }) + .w_h(31.0 * 4.0, 12.0 * 4.0) + .hover_image(if let SettingsTab::Sound = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_hover + }) + .press_image(if let SettingsTab::Sound = state.settings_tab { + self.imgs.settings_button_pressed + } else { + self.imgs.settings_button_press + }) + .right_from(state.ids.video, 0.0) + .parent(state.ids.settings_r) + .label("Sound") + .label_font_size(14) + .label_color(TEXT_COLOR) + .set(state.ids.sound, ui) + .was_clicked() + { + state.update(|s| s.settings_tab = SettingsTab::Sound); + } + + None + } +} diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs new file mode 100644 index 0000000000..916bbe003a --- /dev/null +++ b/voxygen/src/hud/skillbar.rs @@ -0,0 +1,170 @@ +use super::{font_ids::Fonts, img_ids::Imgs, HP_COLOR, MANA_COLOR, TEXT_COLOR, XP_COLOR}; +use conrod_core::{ + widget::{self, Image, Rectangle, Text}, + widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +widget_ids! { + struct Ids { + health_bar, + health_bar_color, + l_click, + level_text, + mana_bar, + mana_bar_color, + next_level_text, + r_click, + sb_grid_bg_l, + sb_grid_bg_r, + sb_grid_l, + sb_grid_r, + test, + xp_bar, + xp_bar_progress, + } +} + +#[derive(WidgetCommon)] +pub struct Skillbar<'a> { + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> Skillbar<'a> { + pub fn new(imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +pub enum Event {} + +impl<'a> Widget for Skillbar<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + // TODO: Read from parameter / character struct + let xp_percentage = 0.4; + let hp_percentage = 0.4; + let mana_percentage = 0.4; + + // Experience-Bar + Image::new(self.imgs.xp_bar) + .w_h(2688.0 / 6.0, 116.0 / 6.0) + .mid_bottom_of(ui.window) + .set(state.ids.xp_bar, ui); + + Rectangle::fill_with([406.0 * (xp_percentage), 5.0], XP_COLOR) // "W=406*[Exp. %]" + .top_left_with_margins_on(state.ids.xp_bar, 5.0, 21.0) + .set(state.ids.xp_bar_progress, ui); + + // Left Grid + Image::new(self.imgs.sb_grid) + .w_h(2240.0 / 12.0, 448.0 / 12.0) + .up_from(state.ids.xp_bar, 0.0) + .align_left_of(state.ids.xp_bar) + .set(state.ids.sb_grid_l, ui); + + Image::new(self.imgs.sb_grid_bg) + .w_h(2240.0 / 12.0, 448.0 / 12.0) + .middle_of(state.ids.sb_grid_l) + .set(state.ids.sb_grid_bg_l, ui); + + // Right Grid + Image::new(self.imgs.sb_grid) + .w_h(2240.0 / 12.0, 448.0 / 12.0) + .up_from(state.ids.xp_bar, 0.0) + .align_right_of(state.ids.xp_bar) + .set(state.ids.sb_grid_r, ui); + + Image::new(self.imgs.sb_grid_bg) + .w_h(2240.0 / 12.0, 448.0 / 12.0) + .middle_of(state.ids.sb_grid_r) + .set(state.ids.sb_grid_bg_r, ui); + + // Right and Left Click + Image::new(self.imgs.l_click) + .w_h(224.0 / 6.0, 320.0 / 6.0) + .right_from(state.ids.sb_grid_bg_l, 0.0) + .align_bottom_of(state.ids.sb_grid_bg_l) + .set(state.ids.l_click, ui); + + Image::new(self.imgs.r_click) + .w_h(224.0 / 6.0, 320.0 / 6.0) + .left_from(state.ids.sb_grid_bg_r, 0.0) + .align_bottom_of(state.ids.sb_grid_bg_r) + .set(state.ids.r_click, ui); + + // Health Bar + Image::new(self.imgs.health_bar) + .w_h(1120.0 / 6.0, 96.0 / 6.0) + .left_from(state.ids.l_click, 0.0) + .align_top_of(state.ids.l_click) + .set(state.ids.health_bar, ui); + + // Filling + Rectangle::fill_with([182.0 * (hp_percentage), 6.0], HP_COLOR) // "W=182.0 * [Health. %]" + .top_right_with_margins_on(state.ids.health_bar, 5.0, 0.0) + .set(state.ids.health_bar_color, ui); + + // Mana Bar + Image::new(self.imgs.mana_bar) + .w_h(1120.0 / 6.0, 96.0 / 6.0) + .right_from(state.ids.r_click, 0.0) + .align_top_of(state.ids.r_click) + .set(state.ids.mana_bar, ui); + + // Filling + Rectangle::fill_with([182.0 * (mana_percentage), 6.0], MANA_COLOR) // "W=182.0 * [Mana. %]" + .top_left_with_margins_on(state.ids.mana_bar, 5.0, 0.0) + .set(state.ids.mana_bar_color, ui); + + // Buffs/Debuffs + + // Buffs + + // Debuffs + + // Level Display + + // Insert actual Level here + Text::new("1") + .left_from(state.ids.xp_bar, -15.0) + .font_size(10) + .color(TEXT_COLOR) + .set(state.ids.level_text, ui); + + // Insert next Level here + Text::new("2") + .right_from(state.ids.xp_bar, -15.0) + .font_size(10) + .color(TEXT_COLOR) + .set(state.ids.next_level_text, ui); + + None + } +} diff --git a/voxygen/src/hud/small_window.rs b/voxygen/src/hud/small_window.rs new file mode 100644 index 0000000000..f19d75c65e --- /dev/null +++ b/voxygen/src/hud/small_window.rs @@ -0,0 +1,136 @@ +use super::{font_ids::Fonts, img_ids::Imgs, Windows, TEXT_COLOR}; +use crate::hud::Show; +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle, Text}, + widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +widget_ids! { + struct Ids { + frame, + bg, + title, + icon, + close, + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum SmallWindowType { + Spellbook, + Social, + QuestLog, +} + +#[derive(WidgetCommon)] +pub struct SmallWindow<'a> { + content: SmallWindowType, + show: &'a Show, + + imgs: &'a Imgs, + fonts: &'a Fonts, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> SmallWindow<'a> { + pub fn new(content: SmallWindowType, show: &'a Show, imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + Self { + content, + show, + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +pub enum Event { + Close, +} + +impl<'a> Widget for SmallWindow<'a> { + type State = State; + type Style = (); + type Event = Option; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + let (title, icon) = match self.content { + SmallWindowType::Social => ("Social", self.imgs.social_icon), + SmallWindowType::Spellbook => ("Spellbook", self.imgs.spellbook_icon), + SmallWindowType::QuestLog => ("QuestLog", self.imgs.questlog_icon), + }; + + // Frame + // TODO: Relative to Char Window? + if let Windows::CharacterAnd(_) = self.show.open_windows { + Image::new(self.imgs.window_frame) + .top_left_with_margins_on(ui.window, 200.0, 658.0) + .w_h(107.0 * 4.0, 125.0 * 4.0) + .set(state.ids.frame, ui); + } else { + Image::new(self.imgs.window_frame) + .top_left_with_margins_on(ui.window, 200.0, 10.0) + .w_h(107.0 * 4.0, 125.0 * 4.0) + .set(state.ids.frame, ui); + } + + // Icon + Image::new(icon) + .w_h(40.0, 40.0) + .top_left_with_margins_on(state.ids.frame, 4.0, 4.0) + .set(state.ids.icon, ui); + + // Content alignment + Rectangle::fill_with([362.0, 418.0], color::TRANSPARENT) + .bottom_right_with_margins_on(state.ids.frame, 17.0, 17.0) + .scroll_kids() + .scroll_kids_vertically() + .set(state.ids.bg, ui); + + // X-Button + if Button::image(self.imgs.close_button) + .w_h(28.0, 28.0) + .hover_image(self.imgs.close_button_hover) + .press_image(self.imgs.close_button_press) + .top_right_with_margins_on(state.ids.frame, 12.0, 0.0) + .set(state.ids.close, ui) + .was_clicked() + { + return Some(Event::Close); + } + // Title + Text::new(title) + .mid_top_with_margin_on(state.ids.frame, 16.0) + .font_id(self.fonts.metamorph) + .font_size(14) + .color(TEXT_COLOR) + .set(state.ids.title, ui); + + match self.content { + SmallWindowType::Social => {} + SmallWindowType::Spellbook => {} + SmallWindowType::QuestLog => {} + } + + None + } +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index a7a911580f..e48cfc6d1b 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,6 +1,8 @@ #![feature(drain_filter)] #![recursion_limit = "2048"] +#[macro_use] +pub mod ui; pub mod anim; pub mod error; pub mod hud; @@ -12,7 +14,6 @@ pub mod scene; pub mod session; pub mod settings; pub mod singleplayer; -pub mod ui; pub mod window; // Reexports diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 42d2704b4b..815d5fb040 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -1,12 +1,11 @@ use crate::{ render::Renderer, - ui::{self, ScaleMode, Ui}, + ui::{self, Graphic, ScaleMode, Ui}, window::Window, }; use common::{ assets, comp::character::{Belt, Character, Chest, Foot, Gender, Hand, Head, Pants, Race, Weapon}, - figure::Segment, }; use conrod_core::{ color, @@ -16,6 +15,7 @@ use conrod_core::{ widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; +use std::sync::Arc; widget_ids! { struct Ids { @@ -231,26 +231,16 @@ struct Imgs { icon_border_pressed: ImgId, } impl Imgs { - fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs { + fn new(ui: &mut Ui) -> Imgs { let load_img = |filename, ui: &mut Ui| { let fullpath: String = ["/voxygen/", filename].concat(); - let image = image::load_from_memory( - assets::load(fullpath.as_str()) - .expect("Error loading file") - .as_slice(), - ) - .unwrap(); - ui.new_graphic(ui::Graphic::Image(image)) + let image = assets::load::(fullpath.as_str()).unwrap(); + ui.add_graphic(Graphic::Image(image)) }; let load_vox = |filename, ui: &mut Ui| { let fullpath: String = ["/voxygen/", filename].concat(); - let dot_vox = dot_vox::load_bytes( - assets::load(fullpath.as_str()) - .expect("Error loading file") - .as_slice(), - ) - .unwrap(); - ui.new_graphic(ui::Graphic::Voxel(Segment::from(dot_vox))) + let dot_vox = assets::load::(fullpath.as_str()).unwrap(); + ui.add_graphic(Graphic::Voxel(dot_vox)) }; Imgs { v_logo: load_vox("element/v_logo.vox", ui), @@ -367,16 +357,11 @@ impl CharSelectionUi { // Generate ids let ids = Ids::new(ui.id_generator()); // Load images - let imgs = Imgs::new(&mut ui, window.renderer_mut()); + let imgs = Imgs::new(&mut ui); // Load fonts let load_font = |filename, ui: &mut Ui| { let fullpath: String = ["/voxygen/font", filename].concat(); - ui.new_font( - conrod_core::text::Font::from_bytes( - assets::load(fullpath.as_str()).expect("Error loading file"), - ) - .unwrap(), - ) + ui.new_font(assets::load(fullpath.as_str()).expect("Error loading file")) }; let font_opensans = load_font("/OpenSans-Regular.ttf", &mut ui); let font_metamorph = load_font("/Metamorphous-Regular.ttf", &mut ui); diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index f669ec1e42..a644c579b5 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -1,19 +1,19 @@ use crate::{ render::Renderer, - ui::{self, ScaleMode, Ui}, - window::Window, + ui::{self, Graphic, ScaleMode, Ui}, GlobalState, DEFAULT_PUBLIC_SERVER, }; -use common::{assets, figure::Segment}; +use common::assets; use conrod_core::{ color, color::TRANSPARENT, image::Id as ImgId, - position::{Dimension, Relative}, + position::Relative, text::font::Id as FontId, widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; +use std::sync::Arc; widget_ids! { struct Ids { @@ -63,26 +63,16 @@ struct Imgs { button_press: ImgId, } impl Imgs { - fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs { + fn new(ui: &mut Ui) -> Imgs { let load_img = |filename, ui: &mut Ui| { let fullpath: String = ["/voxygen/", filename].concat(); - let image = image::load_from_memory( - assets::load(fullpath.as_str()) - .expect("Error loading file") - .as_slice(), - ) - .unwrap(); - ui.new_graphic(ui::Graphic::Image(image)) + let image = assets::load::(fullpath.as_str()).unwrap(); + ui.add_graphic(Graphic::Image(image)) }; let load_vox = |filename, ui: &mut Ui| { let fullpath: String = ["/voxygen/", filename].concat(); - let dot_vox = dot_vox::load_bytes( - assets::load(fullpath.as_str()) - .expect("Error loading file") - .as_slice(), - ) - .unwrap(); - ui.new_graphic(ui::Graphic::Voxel(Segment::from(dot_vox))) + let dot_vox = assets::load::(fullpath.as_str()).unwrap(); + ui.add_graphic(Graphic::Voxel(dot_vox)) }; Imgs { bg: load_img("background/bg_main.png", ui), @@ -123,7 +113,7 @@ pub struct MainMenuUi { impl MainMenuUi { pub fn new(global_state: &mut GlobalState) -> Self { - let mut window = &mut global_state.window; + let window = &mut global_state.window; let networking = &global_state.settings.networking; let mut ui = Ui::new(window).unwrap(); // TODO: adjust/remove this, right now it is used to demonstrate window scaling functionality @@ -131,16 +121,11 @@ impl MainMenuUi { // Generate ids let ids = Ids::new(ui.id_generator()); // Load images - let imgs = Imgs::new(&mut ui, window.renderer_mut()); + let imgs = Imgs::new(&mut ui); // Load fonts let load_font = |filename, ui: &mut Ui| { let fullpath: String = ["/voxygen/font", filename].concat(); - ui.new_font( - conrod_core::text::Font::from_bytes( - assets::load(fullpath.as_str()).expect("Error loading file"), - ) - .unwrap(), - ) + ui.new_font(assets::load(fullpath.as_str()).expect("Error loading file")) }; let font_opensans = load_font("/OpenSans-Regular.ttf", &mut ui); let font_metamorph = load_font("/Metamorphous-Regular.ttf", &mut ui); diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 58d529537a..af37bb5388 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -19,6 +19,7 @@ use common::{ figure::Segment, msg, }; +use dot_vox::DotVoxData; use specs::{Component, Entity as EcsEntity, Join, VecStorage}; use std::{collections::HashMap, f32}; use vek::*; @@ -98,17 +99,10 @@ impl FigureCache { .retain(|_, (_, last_used)| *last_used + 60 > tick); } - fn load_mesh(filename: &'static str, position: Vec3) -> Mesh { + fn load_mesh(filename: &str, position: Vec3) -> Mesh { let fullpath: String = ["/voxygen/voxel/", filename].concat(); - Segment::from( - dot_vox::load_bytes( - assets::load(fullpath.as_str()) - .expect("Error loading file") - .as_slice(), - ) - .unwrap(), - ) - .generate_mesh(position) + Segment::from(assets::load_expect::(fullpath.as_str()).as_ref()) + .generate_mesh(position) } fn load_head(head: Head) -> Mesh { diff --git a/voxygen/src/ui/font_ids.rs b/voxygen/src/ui/font_ids.rs new file mode 100644 index 0000000000..0ba0a0089b --- /dev/null +++ b/voxygen/src/ui/font_ids.rs @@ -0,0 +1,30 @@ +/// This macro will automatically load all specified assets, get the corresponding FontIds and +/// create a struct with all of them +/// +/// Example usage: +/// ``` +/// image_ids! { +/// pub struct Imgs { +/// font1: "filename1.vox", +/// font2: "filename2.vox", +/// } +/// } +/// ``` +#[macro_export] +macro_rules! font_ids { + ($($v:vis struct $Ids:ident { $( $name:ident: $specifier:expr $(,)? )* })*) => { + $( + $v struct $Ids { + $( $v $name: conrod_core::text::font::Id, )* + } + + impl $Ids { + pub fn load(ui: &mut crate::ui::Ui) -> Result { + Ok(Self { + $( $name: ui.new_font(common::assets::load($specifier)?), )* + }) + } + } + )* + }; +} diff --git a/voxygen/src/ui/graphic/graphic.rs b/voxygen/src/ui/graphic/graphic.rs index b42216b36a..38b80a08e4 100644 --- a/voxygen/src/ui/graphic/graphic.rs +++ b/voxygen/src/ui/graphic/graphic.rs @@ -1,14 +1,16 @@ -use common::figure::Segment; +use dot_vox::DotVoxData; use fnv::FnvHashMap; use guillotiere::{size2, Allocation, AtlasAllocator}; use image::DynamicImage; +use std::sync::Arc; use vek::*; pub enum Graphic { - Image(DynamicImage), - Voxel(Segment), + Image(Arc), + Voxel(Arc), Blank, } + #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct Id(u32); @@ -29,7 +31,7 @@ impl GraphicCache { next_id: 0, } } - pub fn new_graphic(&mut self, graphic: Graphic) -> Id { + pub fn add_graphic(&mut self, graphic: Graphic) -> Id { let id = self.next_id; self.next_id = id.wrapping_add(1); @@ -92,8 +94,8 @@ impl GraphicCache { .pixels() .map(|p| p.data) .collect::>(), - Graphic::Voxel(segment) => { - super::renderer::draw_vox(&segment, aabr.size().into()) + Graphic::Voxel(ref vox) => { + super::renderer::draw_vox(&vox.as_ref().into(), aabr.size().into()) } Graphic::Blank => return None, }; diff --git a/voxygen/src/ui/img_ids.rs b/voxygen/src/ui/img_ids.rs new file mode 100644 index 0000000000..04167078d6 --- /dev/null +++ b/voxygen/src/ui/img_ids.rs @@ -0,0 +1,70 @@ +use super::Graphic; +use common::assets::{load, Error}; +use dot_vox::DotVoxData; +use image::DynamicImage; + +pub struct BlankGraphic; +pub struct ImageGraphic; +pub struct VoxelGraphic; + +pub trait GraphicCreator<'a> { + type Specifier; + fn new_graphic(specifier: Self::Specifier) -> Result; +} +impl<'a> GraphicCreator<'a> for BlankGraphic { + type Specifier = (); + fn new_graphic(_: ()) -> Result { + Ok(Graphic::Blank) + } +} +impl<'a> GraphicCreator<'a> for ImageGraphic { + type Specifier = &'a str; + fn new_graphic(specifier: Self::Specifier) -> Result { + Ok(Graphic::Image(load::(specifier)?)) + } +} +impl<'a> GraphicCreator<'a> for VoxelGraphic { + type Specifier = &'a str; + fn new_graphic(specifier: Self::Specifier) -> Result { + Ok(Graphic::Voxel(load::(specifier)?)) + } +} + +/// This macro will automatically load all specified assets, get the corresponding ImgIds and +/// create a struct with all of them +/// +/// Example usage: +/// ``` +/// image_ids! { +/// pub struct Imgs { +/// +/// button1: "filename1.vox", +/// button2: "filename2.vox", +/// +/// +/// background: "background.png", +/// +/// +/// blank: (), +/// } +/// } +/// ``` +#[macro_export] +macro_rules! image_ids { + ($($v:vis struct $Ids:ident { $( <$T:ty> $( $name:ident: $specifier:expr ),* $(,)? )* })*) => { + $( + $v struct $Ids { + $($( $v $name: conrod_core::image::Id, )*)* + } + + impl $Ids { + pub fn load(ui: &mut crate::ui::Ui) -> Result { + use crate::ui::GraphicCreator; + Ok(Self { + $($( $name: ui.add_graphic(<$T>::new_graphic($specifier)?), )*)* + }) + } + } + )* + }; +} diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 4b008057b8..71c2d2db64 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -1,8 +1,13 @@ mod graphic; mod util; mod widgets; +#[macro_use] +mod img_ids; +#[macro_use] +mod font_ids; pub use graphic::Graphic; +pub use img_ids::{BlankGraphic, GraphicCreator, ImageGraphic, VoxelGraphic}; pub(self) use util::{linear_to_srgb, srgb_to_linear}; pub use widgets::toggle_button::ToggleButton; @@ -14,16 +19,19 @@ use crate::{ window::Window, Error, }; +use common::assets; use conrod_core::{ event::Input, + graph::Graph, image::{Id as ImgId, Map}, input::{touch::Touch, Button, Motion, Widget}, render::Primitive, - text::{font::Id as FontId, Font, GlyphCache}, + text::{self, GlyphCache}, widget::{id::Generator, Id as WidgId}, Ui as CrUi, UiBuilder, UiCell, }; use graphic::{GraphicCache, Id as GraphicId}; +use std::sync::Arc; use vek::*; #[derive(Debug)] @@ -113,8 +121,8 @@ impl Cache { pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture) { (&mut self.graphic_cache, &self.graphic_cache_tex) } - pub fn new_graphic(&mut self, graphic: Graphic) -> GraphicId { - self.graphic_cache.new_graphic(graphic) + pub fn add_graphic(&mut self, graphic: Graphic) -> GraphicId { + self.graphic_cache.add_graphic(graphic) } pub fn clear_graphic_cache(&mut self, renderer: &mut Renderer, new_size: Vec2) { self.graphic_cache.clear_cache(new_size); @@ -212,6 +220,15 @@ impl Scale { } } +pub struct Font(text::Font); +impl assets::Asset for Font { + fn load(specifier: &str) -> Result { + Ok(Font( + text::Font::from_bytes(assets::load_from_path(specifier)?).unwrap(), + )) + } +} + pub struct Ui { ui: CrUi, image_map: Map, @@ -228,6 +245,7 @@ impl Ui { pub fn new(window: &mut Window) -> Result { let scale = Scale::new(window, ScaleMode::Absolute(1.0)); let win_dims = scale.scaled_window_size().into_array(); + Ok(Self { ui: UiBuilder::new(win_dims).build(), image_map: Map::new(), @@ -246,12 +264,12 @@ impl Ui { self.ui.handle_event(Input::Resize(w, h)); } - pub fn new_graphic(&mut self, graphic: Graphic) -> ImgId { - self.image_map.insert(self.cache.new_graphic(graphic)) + pub fn add_graphic(&mut self, graphic: Graphic) -> ImgId { + self.image_map.insert(self.cache.add_graphic(graphic)) } - pub fn new_font(&mut self, font: Font) -> FontId { - self.ui.fonts.insert(font) + pub fn new_font(&mut self, mut font: Arc) -> text::font::Id { + self.ui.fonts.insert(font.as_ref().0.clone()) } pub fn id_generator(&mut self) -> Generator { @@ -285,6 +303,10 @@ impl Ui { .is_none() } + // Get the widget graph + pub fn widget_graph(&self) -> &Graph { + self.ui.widget_graph() + } pub fn handle_event(&mut self, event: Event) { match event.0 { Input::Resize(w, h) => self.window_resized = Some(Vec2::new(w, h)),