Merge branch 'timo_assets_rebased' into 'master'

New asset system, split hud.rs, macros for img and font id structs (Timo)

See merge request veloren/veloren!111

Former-commit-id: 4765e936dd445d651348b2460707faacd5273d61
This commit is contained in:
Forest Anderson 2019-05-08 02:31:22 +00:00
commit 4d29dbdb18
25 changed files with 2646 additions and 2081 deletions

2
Cargo.lock generated
View File

@ -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)",

View File

@ -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"

View File

@ -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<File> {
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<Arc<dyn Any + 'static + Sync + Send>> for Error {
fn from(_: Arc<dyn Any + 'static + Sync + Send>) -> Self {
Error::InvalidType
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::NotFound(format!("{:?}", err))
}
}
lazy_static! {
static ref ASSETS: RwLock<HashMap<String, Arc<dyn Any + 'static + Sync + Send>>> =
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::<DynamicImage>("core.ui.backgrounds.city").unwrap();
/// ```
pub fn load<A: Asset + 'static>(specifier: &str) -> Result<Arc<A>, 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::<DynamicImage>("core.ui.backgrounds.city");
/// ```
pub fn load_expect<A: Asset + 'static>(specifier: &str) -> Arc<A> {
load(specifier).expect(&format!("Failed loading essential asset: {}", specifier))
}
/// Asset Trait
pub trait Asset: Send + Sync + Sized {
fn load(specifier: &str) -> Result<Self, Error>;
}
impl Asset for DynamicImage {
fn load(specifier: &str) -> Result<Self, Error> {
Ok(image::load_from_memory(load_from_path(specifier)?.as_slice()).unwrap())
}
}
impl Asset for DotVoxData {
fn load(specifier: &str) -> Result<Self, Error> {
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<File> {
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<Vec<u8>, ()> {
return match try_load(name) {
pub fn load_from_path(name: &str) -> Result<Vec<u8>, Error> {
match try_open_with_path(name) {
Some(mut f) => {
let mut content: Vec<u8> = vec![];
f.read_to_end(&mut content);
info!("loaded asset successful: {}", name);
let mut content = Vec::<u8>::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())),
}
}

View File

@ -18,8 +18,8 @@ use self::cell::Cell;
/// Figures are used to represent things like characters, NPCs, mobs, etc.
pub type Segment = Dyna<Cell, ()>;
impl From<DotVoxData> 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

117
voxygen/src/hud/bag.rs Normal file
View File

@ -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<Event>;
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>) -> 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
}
}
}

245
voxygen/src/hud/buttons.rs Normal file
View File

@ -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<Event>;
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>) -> 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
}
}

View File

@ -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<Event>;
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>) -> 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
}
}

View File

@ -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<String>,
input: String,
new_messages: bool,
#[derive(WidgetCommon)]
pub struct Chat<'a> {
new_messages: &'a mut VecDeque<String>,
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<String>, 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<String> {
// Maintain scrolling
if self.new_messages {
self.scroll_new_messages(ui_widgets);
self.new_messages = false;
}
pub struct State {
messages: VecDeque<String>,
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<Event>;
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>) -> 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
}

156
voxygen/src/hud/esc_menu.rs Normal file
View File

@ -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<Event>;
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>) -> 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
}
}

View File

@ -0,0 +1,6 @@
font_ids! {
pub struct Fonts {
opensans: "/voxygen/font/OpenSans-Regular.ttf",
metamorph: "/voxygen/font/Metamorphous-Regular.ttf",
}
}

157
voxygen/src/hud/img_ids.rs Normal file
View File

@ -0,0 +1,157 @@
use crate::ui::{BlankGraphic, ImageGraphic, VoxelGraphic};
image_ids! {
pub struct Imgs {
<VoxelGraphic>
// 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",
<ImageGraphic>
// 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",
<BlankGraphic>
blank: (),
}
}

118
voxygen/src/hud/map.rs Normal file
View File

@ -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<Event>;
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>) -> 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
}
}

115
voxygen/src/hud/minimap.rs Normal file
View File

@ -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<Event>;
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>) -> 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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<Event>;
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>) -> 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
}
}

170
voxygen/src/hud/skillbar.rs Normal file
View File

@ -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<Event>;
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>) -> 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
}
}

View File

@ -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<Event>;
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>) -> 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
}
}

View File

@ -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

View File

@ -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::<image::DynamicImage>(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::<dot_vox::DotVoxData>(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);

View File

@ -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::<image::DynamicImage>(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::<dot_vox::DotVoxData>(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);

View File

@ -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<f32>) -> Mesh<FigurePipeline> {
fn load_mesh(filename: &str, position: Vec3<f32>) -> Mesh<FigurePipeline> {
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::<DotVoxData>(fullpath.as_str()).as_ref())
.generate_mesh(position)
}
fn load_head(head: Head) -> Mesh<FigurePipeline> {

View File

@ -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<Self, common::assets::Error> {
Ok(Self {
$( $name: ui.new_font(common::assets::load($specifier)?), )*
})
}
}
)*
};
}

View File

@ -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<DynamicImage>),
Voxel(Arc<DotVoxData>),
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::<Vec<[u8; 4]>>(),
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,
};

70
voxygen/src/ui/img_ids.rs Normal file
View File

@ -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<Graphic, Error>;
}
impl<'a> GraphicCreator<'a> for BlankGraphic {
type Specifier = ();
fn new_graphic(_: ()) -> Result<Graphic, Error> {
Ok(Graphic::Blank)
}
}
impl<'a> GraphicCreator<'a> for ImageGraphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Image(load::<DynamicImage>(specifier)?))
}
}
impl<'a> GraphicCreator<'a> for VoxelGraphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(load::<DotVoxData>(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 {
/// <VoxelGraphic>
/// button1: "filename1.vox",
/// button2: "filename2.vox",
///
/// <ImageGraphic>
/// background: "background.png",
///
/// <BlankGraphic>
/// 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<Self, common::assets::Error> {
use crate::ui::GraphicCreator;
Ok(Self {
$($( $name: ui.add_graphic(<$T>::new_graphic($specifier)?), )*)*
})
}
}
)*
};
}

View File

@ -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<UiPipeline>) {
(&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<u16>) {
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<Self, assets::Error> {
Ok(Font(
text::Font::from_bytes(assets::load_from_path(specifier)?).unwrap(),
))
}
}
pub struct Ui {
ui: CrUi,
image_map: Map<GraphicId>,
@ -228,6 +245,7 @@ impl Ui {
pub fn new(window: &mut Window) -> Result<Self, Error> {
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<Font>) -> 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)),