Merge branch 'ui' into 'master'

Changes to UI

See merge request veloren/veloren!22

Former-commit-id: e69b799fed563ba7208db7818d0c77e2a6f06dc4
This commit is contained in:
Joshua Barretto 2019-04-14 13:23:08 +00:00
commit bea4a73c2e
9 changed files with 108 additions and 569 deletions

4
.gitignore vendored
View File

@ -20,7 +20,5 @@
**/server_conf.toml
**/keybinds.toml
assets/voxygen
UI3.rar
assets.rar
*.rar
assets/voxygen

@ -1 +1 @@
Subproject commit e3083ec8e8e634af8c9daed00ea82435da195979
Subproject commit 6b64a65356c1fb4c77ad0295908f4006f75a5448

View File

@ -1,74 +0,0 @@
#[derive(Debug)]
pub enum PostError {
InvalidMessage,
InternalError,
Disconnected,
}
#[derive(Debug)]
pub enum PostErrorInternal {
Io(std::io::Error),
Serde(bincode::Error),
ChannelRecv(std::sync::mpsc::TryRecvError),
ChannelSend, // Empty because I couldn't figure out how to handle generic type in mpsc::TrySendError properly
MsgSizeLimitExceeded,
MioError,
}
impl<'a, T: Into<&'a PostErrorInternal>> From<T> for PostError {
fn from(err: T) -> Self {
match err.into() {
// TODO: Are I/O errors always disconnect errors?
PostErrorInternal::Io(_) => PostError::Disconnected,
PostErrorInternal::Serde(_) => PostError::InvalidMessage,
PostErrorInternal::MsgSizeLimitExceeded => PostError::InvalidMessage,
PostErrorInternal::MioError => PostError::InternalError,
PostErrorInternal::ChannelRecv(_) => PostError::InternalError,
PostErrorInternal::ChannelSend => PostError::InternalError,
}
}
}
impl From<PostErrorInternal> for PostError {
fn from(err: PostErrorInternal) -> Self {
(&err).into()
}
}
impl From<std::io::Error> for PostErrorInternal {
fn from(err: std::io::Error) -> Self {
PostErrorInternal::Io(err)
}
}
impl From<bincode::Error> for PostErrorInternal {
fn from(err: bincode::Error) -> Self {
PostErrorInternal::Serde(err)
}
}
impl From<std::sync::mpsc::TryRecvError> for PostErrorInternal {
fn from(err: std::sync::mpsc::TryRecvError) -> Self {
PostErrorInternal::ChannelRecv(err)
}
}
impl From<std::io::Error> for PostError {
fn from(err: std::io::Error) -> Self {
(&PostErrorInternal::from(err)).into()
}
}
impl From<bincode::Error> for PostError {
fn from(err: bincode::Error) -> Self {
(&PostErrorInternal::from(err)).into()
}
}
impl From<std::sync::mpsc::TryRecvError> for PostError {
fn from(err: std::sync::mpsc::TryRecvError) -> Self {
(&PostErrorInternal::from(err)).into()
}
}

View File

@ -1,270 +0,0 @@
// Standard
use std::{
collections::VecDeque,
convert::TryFrom,
io::{
ErrorKind,
Read,
},
net::SocketAddr,
thread,
time::Duration,
sync::mpsc::TryRecvError,
};
// External
use bincode;
use mio::{net::TcpStream, Events, Poll, PollOpt, Ready, Token};
use mio_extras::channel::{channel, Receiver, Sender};
// Crate
use super::{
data::ControlMsg,
error::{
PostError,
PostErrorInternal,
},
PostRecv,
PostSend,
};
// Constants
const CTRL_TOKEN: Token = Token(0); // Token for thread control messages
const DATA_TOKEN: Token = Token(1); // Token for thread data exchange
const CONN_TOKEN: Token = Token(2); // Token for TcpStream for the PostBox child thread
const MESSAGE_SIZE_CAP: u64 = 1 << 20; // Maximum accepted length of a packet
/// A high-level wrapper of [`TcpStream`](mio::net::TcpStream).
/// [`PostBox`] takes care of serializing sent packets and deserializing received packets in the background, providing a simple API for sending and receiving objects over network.
pub struct PostBox<S, R>
where
S: PostSend,
R: PostRecv,
{
handle: Option<thread::JoinHandle<()>>,
ctrl: Sender<ControlMsg>,
recv: Receiver<Result<R, PostErrorInternal>>,
send: Sender<S>,
poll: Poll,
err: Option<PostErrorInternal>,
}
impl<S, R> PostBox<S, R>
where
S: PostSend,
R: PostRecv,
{
/// Creates a new [`PostBox`] connected to specified address, meant to be used by the client
pub fn to_server<A: Into<SocketAddr>>(addr: A) -> Result<PostBox<S, R>, PostError> {
let connection = TcpStream::connect(&addr.into())?;
Self::from_tcpstream(connection)
}
/// Creates a new [`PostBox`] from an existing connection, meant to be used by [`PostOffice`](super::PostOffice) on the server
pub fn from_tcpstream(connection: TcpStream) -> Result<PostBox<S, R>, PostError> {
let (ctrl_tx, ctrl_rx) = channel(); // Control messages
let (send_tx, send_rx) = channel(); // main thread -[data to be serialized and sent]> worker thread
let (recv_tx, recv_rx) = channel(); // main thread <[received and deserialized data]- worker thread
let thread_poll = Poll::new().unwrap();
let postbox_poll = Poll::new().unwrap();
thread_poll
.register(&connection, CONN_TOKEN, Ready::readable(), PollOpt::edge())
.unwrap();
thread_poll
.register(&ctrl_rx, CTRL_TOKEN, Ready::readable(), PollOpt::edge())
.unwrap();
thread_poll
.register(&send_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge())
.unwrap();
postbox_poll
.register(&recv_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge())
.unwrap();
let handle = thread::Builder::new()
.name("postbox_worker".into())
.spawn(move || postbox_thread(connection, ctrl_rx, send_rx, recv_tx, thread_poll))?;
Ok(PostBox {
handle: Some(handle),
ctrl: ctrl_tx,
recv: recv_rx,
send: send_tx,
poll: postbox_poll,
err: None,
})
}
/// Return an `Option<PostError>` indicating the current status of the `PostBox`.
pub fn status(&self) -> Option<PostError> {
self.err.as_ref().map(|err| err.into())
}
/// Non-blocking sender method
pub fn send(&mut self, data: S) -> Result<(), PostError> {
match &mut self.err {
err @ None => if let Err(_) = self.send.send(data) {
*err = Some(PostErrorInternal::MioError);
Err(err.as_ref().unwrap().into())
} else {
Ok(())
},
err => Err(err.as_ref().unwrap().into()),
}
}
/// Non-blocking receiver method returning an iterator over already received and deserialized objects
/// # Errors
/// If the other side disconnects PostBox won't realize that until you try to send something
pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item = R> {
let mut events = Events::with_capacity(4096);
let mut items = VecDeque::new();
// If an error occured, or previously occured, just give up
if let Some(_) = self.err {
return items.into_iter();
} else if let Err(err) = self.poll.poll(&mut events, Some(Duration::new(0, 0))) {
self.err = Some(err.into());
return items.into_iter();
}
for event in events {
match event.token() {
DATA_TOKEN => loop {
match self.recv.try_recv() {
Ok(Ok(item)) => items.push_back(item),
Err(TryRecvError::Empty) => break,
Err(err) => self.err = Some(err.into()),
Ok(Err(err)) => self.err = Some(err.into()),
}
},
_ => (),
}
}
items.into_iter()
}
}
fn postbox_thread<S, R>(
mut connection: TcpStream,
ctrl_rx: Receiver<ControlMsg>,
send_rx: Receiver<S>,
recv_tx: Sender<Result<R, PostErrorInternal>>,
poll: Poll,
) where
S: PostSend,
R: PostRecv,
{
// Receiving related variables
let mut events = Events::with_capacity(64);
let mut recv_buff = Vec::new();
let mut recv_nextlen: u64 = 0;
loop {
let mut disconnected = false;
poll.poll(&mut events, Some(Duration::from_millis(20)))
.expect("Failed to execute poll(), most likely fault of the OS");
println!("FINISHED POLL!");
for event in events.iter() {
println!("EVENT!");
match event.token() {
CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() {
ControlMsg::Shutdown => return,
},
CONN_TOKEN => match connection.read_to_end(&mut recv_buff) {
Ok(_) => {}
// Returned when all the data has been read
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {}
Err(e) => recv_tx.send(Err(e.into())).unwrap(),
},
DATA_TOKEN => {
let msg = send_rx.try_recv().unwrap();
println!("Send: {:?}", msg);
let mut packet = bincode::serialize(&msg).unwrap();
packet.splice(0..0, (packet.len() as u64).to_be_bytes().iter().cloned());
match connection.write_bufs(&[packet.as_slice().into()]) {
Ok(_) => { println!("Sent!"); }
Err(e) => {
println!("Send error!");
recv_tx.send(Err(e.into())).unwrap();
}
};
}
_ => {}
}
}
loop {
if recv_nextlen == 0 && recv_buff.len() >= 8 {
println!("Read nextlen");
recv_nextlen = u64::from_be_bytes(
<[u8; 8]>::try_from(recv_buff.drain(0..8).collect::<Vec<u8>>().as_slice()).unwrap(),
);
if recv_nextlen > MESSAGE_SIZE_CAP {
recv_tx.send(Err(PostErrorInternal::MsgSizeLimitExceeded)).unwrap();
connection.shutdown(std::net::Shutdown::Both).unwrap();
recv_buff.drain(..);
recv_nextlen = 0;
break;
}
}
if recv_buff.len() as u64 >= recv_nextlen && recv_nextlen != 0 {
match bincode::deserialize(recv_buff
.drain(
0..usize::try_from(recv_nextlen)
.expect("Message size was larger than usize (insane message size and 32 bit OS)"),
)
.collect::<Vec<u8>>()
.as_slice()) {
Ok(msg) => {
println!("Recv: {:?}", msg);
recv_tx
.send(Ok(msg))
.unwrap();
recv_nextlen = 0;
}
Err(e) => {
println!("Recv error: {:?}", e);
recv_tx.send(Err(e.into())).unwrap();
recv_nextlen = 0;
}
}
} else {
break;
}
}
match connection.take_error().unwrap() {
Some(e) => {
if e.kind() == ErrorKind::BrokenPipe {
disconnected = true;
}
recv_tx.send(Err(e.into())).unwrap();
}
None => {}
}
if disconnected == true {
break;
}
}
// Loop after disconnected
loop {
poll.poll(&mut events, None)
.expect("Failed to execute poll(), most likely fault of the OS");
for event in events.iter() {
match event.token() {
CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() {
ControlMsg::Shutdown => return,
},
_ => {}
}
}
}
}
impl<S, R> Drop for PostBox<S, R>
where
S: PostSend,
R: PostRecv,
{
fn drop(&mut self) {
self.ctrl.send(ControlMsg::Shutdown).unwrap_or(());
self.handle.take().map(|handle| handle.join());
}
}

View File

@ -1,150 +0,0 @@
// Standard
use core::time::Duration;
use std::{
collections::VecDeque,
net::SocketAddr,
thread,
sync::mpsc::TryRecvError,
};
// External
use mio::{net::TcpListener, Events, Poll, PollOpt, Ready, Token};
use mio_extras::channel::{channel, Receiver, Sender};
// Crate
use super::{
data::ControlMsg,
error::{
PostError,
PostErrorInternal,
},
postbox::PostBox,
PostRecv,
PostSend,
};
// Constants
const CTRL_TOKEN: Token = Token(0); // Token for thread control messages
const DATA_TOKEN: Token = Token(1); // Token for thread data exchange
const CONN_TOKEN: Token = Token(2); // Token for TcpStream for the PostBox child thread
/// A high-level wrapper of [`TcpListener`](mio::net::TcpListener).
/// [`PostOffice`] listens for incoming connections in the background and wraps them into [`PostBox`]es, providing a simple non-blocking API for receiving them.
pub struct PostOffice<S, R>
where
S: PostSend,
R: PostRecv,
{
handle: Option<thread::JoinHandle<()>>,
ctrl: Sender<ControlMsg>,
recv: Receiver<Result<PostBox<S, R>, PostErrorInternal>>,
poll: Poll,
err: Option<PostErrorInternal>,
}
impl<S, R> PostOffice<S, R>
where
S: PostSend,
R: PostRecv,
{
/// Creates a new [`PostOffice`] listening on specified address
pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, PostError> {
let listener = TcpListener::bind(&addr.into())?;
let (ctrl_tx, ctrl_rx) = channel();
let (recv_tx, recv_rx) = channel();
let thread_poll = Poll::new()?;
let postbox_poll = Poll::new()?;
thread_poll.register(&listener, CONN_TOKEN, Ready::readable(), PollOpt::edge())?;
thread_poll.register(&ctrl_rx, CTRL_TOKEN, Ready::readable(), PollOpt::edge())?;
postbox_poll.register(&recv_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge())?;
let handle = thread::Builder::new()
.name("postoffice_worker".into())
.spawn(move || postoffice_thread(listener, ctrl_rx, recv_tx, thread_poll))?;
Ok(PostOffice {
handle: Some(handle),
ctrl: ctrl_tx,
recv: recv_rx,
poll: postbox_poll,
err: None,
})
}
/// Return an `Option<PostError>` indicating the current status of the `PostOffice`.
pub fn status(&self) -> Option<PostError> {
self.err.as_ref().map(|err| err.into())
}
/// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es
pub fn new_connections(
&mut self,
) -> impl ExactSizeIterator<Item = PostBox<S, R>> {
let mut events = Events::with_capacity(256);
let mut conns = VecDeque::new();
// If an error occured, or previously occured, just give up
if let Some(_) = self.err {
return conns.into_iter();
} else if let Err(err) = self.poll.poll(&mut events, Some(Duration::new(0, 0))) {
self.err = Some(err.into());
return conns.into_iter();
}
for event in events {
match event.token() {
DATA_TOKEN => loop {
match self.recv.try_recv() {
Ok(Ok(conn)) => conns.push_back(conn),
Err(TryRecvError::Empty) => break,
Err(err) => self.err = Some(err.into()),
Ok(Err(err)) => self.err = Some(err.into()),
}
},
_ => (),
}
}
conns.into_iter()
}
}
fn postoffice_thread<S, R>(
listener: TcpListener,
ctrl_rx: Receiver<ControlMsg>,
recv_tx: Sender<Result<PostBox<S, R>, PostErrorInternal>>,
poll: Poll,
) where
S: PostSend,
R: PostRecv,
{
let mut events = Events::with_capacity(256);
loop {
poll.poll(&mut events, None).expect("Failed to execute recv_poll.poll() in PostOffce receiver thread, most likely fault of the OS.");
for event in events.iter() {
match event.token() {
CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() {
ControlMsg::Shutdown => return,
},
CONN_TOKEN => {
let (conn, _addr) = listener.accept().unwrap();
recv_tx.send(PostBox::from_tcpstream(conn)
// TODO: Is it okay to count a failure to create a postbox here as an 'internal error'?
.map_err(|_| PostErrorInternal::MioError)).unwrap();
}
_ => (),
}
}
}
}
impl<S, R> Drop for PostOffice<S, R>
where
S: PostSend,
R: PostRecv,
{
fn drop(&mut self) {
self.ctrl.send(ControlMsg::Shutdown).unwrap_or(()); // If this fails the thread is dead already
self.handle.take().map(|handle| handle.join());
}
}

View File

@ -3,7 +3,7 @@ use conrod_core::{
input::Key,
position::Dimension,
text::font::Id as FontId,
widget::{Id, Button, List, Rectangle, Text, TextEdit},
widget::{Button, Id, List, Rectangle, Text, TextEdit},
widget_ids, Color, Colorable, Positionable, Sizeable, UiCell, Widget,
};
use std::collections::VecDeque;
@ -70,7 +70,12 @@ impl Chat {
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> {
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);
@ -81,7 +86,7 @@ impl Chat {
let text_edit = TextEdit::new(&self.input)
.w(470.0)
.restrict_to_height(false)
.font_size(14)
.font_size(15)
.font_id(font)
.bottom_left_with_margins_on(ui_widgets.window, 10.0, 10.0);
let dims = match (
@ -102,20 +107,23 @@ impl Chat {
}
// Message box
Rectangle::fill([470.0, 180.0])
Rectangle::fill([470.0, 167.0])
.rgba(0.0, 0.0, 0.0, 0.4)
.up_from(self.ids.input, 0.0)
.set(self.ids.message_box_bg, ui_widgets);
let (mut items, _scrollbar) = List::flow_down(self.messages.len())
.middle_of(self.ids.message_box_bg)
.scroll_kids_vertically()
let (mut items, scrollbar) = List::flow_down(self.messages.len())
.top_left_with_margins_on(self.ids.message_box_bg, 0.0, 5.0)
.w_h(460.0, 160.0)
.scrollbar_next_to()
.scrollbar_thickness(18.0)
.scrollbar_color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(self.ids.message_box, ui_widgets);
while let Some(item) = items.next(ui_widgets) {
item.set(
Text::new(&self.messages[item.i])
.font_size(14)
.font_size(15)
.font_id(font)
.rgba(0.86 , 0.86, 0.86, 1.0),
.rgba(1.0, 1.0, 1.0, 1.0),
ui_widgets,
)
}

View File

@ -7,11 +7,11 @@ use crate::{
};
use common::assets;
use conrod_core::{
color, Color,
color,
image::Id as ImgId,
text::font::Id as FontId,
widget::{Button, Image, Rectangle, Scrollbar, Text},
widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget,
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
};
widget_ids! {
@ -246,13 +246,13 @@ pub(self) struct Imgs {
impl Imgs {
fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs {
let mut load = |filename| {
let fullpath: String = [
"/voxygen/",
filename,
].concat();
let fullpath: String = ["/voxygen/", filename].concat();
let image = image::load_from_memory(
assets::load(fullpath.as_str()).expect("Error loading file").as_slice()
).unwrap();
assets::load(fullpath.as_str())
.expect("Error loading file")
.as_slice(),
)
.unwrap();
ui.new_image(renderer, &image).unwrap()
};
Imgs {
@ -422,7 +422,7 @@ pub struct Hud {
//#[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)
//Color::Rgba(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a)
//}
impl Hud {
@ -459,7 +459,7 @@ impl Hud {
typing: false,
cursor_grabbed: true,
settings_tab: SettingsTab::Interface,
show_help: false,
show_help: true,
bag_open: false,
menu_open: false,
map_open: false,
@ -479,12 +479,11 @@ impl Hud {
let mut events = Vec::new();
let ref mut ui_widgets = self.ui.set_widgets();
const TEXT_COLOR: Color = Color::Rgba(0.86, 0.86, 0.86, 0.8);
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);
if self.show_ui {
// Add Bag-Space Button
if self.inventorytest_button {
@ -512,7 +511,7 @@ impl Hud {
if self.show_help {
Image::new(self.imgs.window_frame_2)
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
.w_h(300.0, 300.0)
.w_h(300.0, 370.0)
.set(self.ids.help_bg, ui_widgets);
Text::new(
@ -523,6 +522,9 @@ impl Hud {
F1 = Toggle this Window \n\
F2 = Toggle Interface \n\
\n\
Enter = Open Chat \n\
Mouse Wheel = Scroll Chat\n\
\n\
M = Map \n\
B = Bag \n\
L = Quest-Log \n\
@ -753,7 +755,6 @@ impl Hud {
.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)
@ -766,7 +767,6 @@ impl Hud {
.top_left_with_margins_on(self.ids.mana_bar, 5.0, 0.0)
.set(self.ids.mana_bar_color, ui_widgets);
// Buffs/Debuffs
// Buffs
@ -778,14 +778,14 @@ impl Hud {
// Insert actual Level here
Text::new("1")
.left_from(self.ids.xp_bar, -15.0)
.font_size(14)
.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(14)
.font_size(10)
.color(TEXT_COLOR)
.set(self.ids.next_level_text, ui_widgets);

View File

@ -78,6 +78,8 @@ widget_ids! {
//test_chars
test_char_l_button,
test_char_l_big,
help_text_bg,
help_text,
//test_char_m_button,
//test_char_r_button,
@ -181,6 +183,7 @@ struct Imgs {
color_picker_bg: ImgId,
slider_range: ImgId,
slider_indicator: ImgId,
window_frame_2: ImgId,
//test_char_m_button: ImgId,
//test_char_r_button: ImgId,
@ -225,13 +228,13 @@ struct Imgs {
impl Imgs {
fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs {
let mut load = |filename| {
let fullpath: String = [
"/voxygen/",
filename,
].concat();
let fullpath: String = ["/voxygen/", filename].concat();
let image = image::load_from_memory(
assets::load(fullpath.as_str()).expect("Error loading file").as_slice()
).unwrap();
assets::load(fullpath.as_str())
.expect("Error loading file")
.as_slice(),
)
.unwrap();
ui.new_image(renderer, &image).unwrap()
};
Imgs {
@ -262,6 +265,7 @@ impl Imgs {
color_picker_bg: load("element/misc_backgrounds/color_picker_blank.png"),
slider_range: load("element/slider/track.png"),
slider_indicator: load("element/slider/indicator.png"),
window_frame_2: load("element/frames/window_2.png"),
// Weapon Icons
daggers: load("element/icons/daggers.png"),
@ -345,7 +349,8 @@ pub enum Event {
Play,
}
const TEXT_COLOR: Color = Color::Rgba(0.86, 0.86, 0.86, 0.8);
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
const TEXT_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
pub struct CharSelectionUi {
ui: Ui,
@ -476,6 +481,16 @@ impl CharSelectionUi {
.label_y(conrod_core::position::Relative::Scalar(-40.0))
.label_x(conrod_core::position::Relative::Scalar(-100.0))
.set(self.ids.v_logo, ui_widgets);
// Click Character to Login <-- Temporary!
Image::new(self.imgs.window_frame_2)
.mid_top_with_margin_on(self.ids.bg_selection, 60.0)
.w_h(700.0, 70.0)
.set(self.ids.help_text_bg, ui_widgets);
Text::new("Click character to select it")
.middle_of(self.ids.help_text_bg)
.font_size(40)
.color(TEXT_COLOR)
.set(self.ids.help_text, ui_widgets);
if let Some(no) = self.selected_char_no {
// Selection_Window
@ -495,7 +510,6 @@ impl CharSelectionUi {
.color(TEXT_COLOR)
.set(self.ids.char_level, ui_widgets);
// Selected Character
if no == 1 {
Image::new(self.imgs.test_char_l_big)
@ -869,14 +883,23 @@ impl CharSelectionUi {
const HUMAN_DESC: &str =
"The former nomads were only recently able to gain a foothold in the world of Veloren. \n\
\n\
Their greatest strengths are their adaptability and intelligence, which makes them allrounders in many fields.";
Their greatest strengths are their adaptability and intelligence, which makes them allrounders in many fields.\n\
\n\
Humans are extremely diverse. \n\
Some become wicked witches, slimy scoundrels, and members of the underworld, while others become witch-hunters, sages, and noble knights. \n\
This diversity however creates constant conflict and antagonism between humans themselves, rather than with the other races of Veloren.";
const ORC_DESC: &str =
"They are considered brutal, rude and combative. \n\
But once you gained their trust they will be loyal friends \n\
that follow a strict code of honor in all of their actions. \n\
\n\
Their warriors are masters of melee combat, but their true power \
comes from the magical rituals of their powerful shamans.";
comes from the magical rituals of their powerful shamans. \n\
\n\
They are divided into three clans. \n\
Two of them are led by the conflicting descendants of the recently deceased High-Warlord. \n\
The third clan was formed by a group of Shamans to prevent the bloodshed caused by the rivaling groups and to secure their source of magic: \n\
A powerful nature crystal, stolen from the Brushwood Elves...";
const DWARF_DESC: &str =
"Smoking chimneys, the sound of countless hammers and hoes. \
Infinite tunnel systems to track down even the last chunk of metal in the ground. \n\
@ -901,6 +924,8 @@ impl CharSelectionUi {
\n\
Gold Elves that hunger for political power in their massive city states. \n\
\n\
Dark Elves, seeking war to brutalize their enemies, with honor.\n\
\n\
And many more!";
const DANARI_DESC: &str =
"The white domes and towers of their underwater kingdom are often mistaken for coral reefs from above the water. \n\

View File

@ -10,8 +10,7 @@ use conrod_core::{
position::Dimension,
text::font::Id as FontId,
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox},
widget_ids, Borderable, Color,
Colorable, Labelable, Positionable, Sizeable, Widget,
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
};
widget_ids! {
@ -58,13 +57,13 @@ impl Imgs {
fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs {
// TODO: update paths
let mut load = |filename| {
let fullpath: String = [
"/voxygen/",
filename,
].concat();
let fullpath: String = ["/voxygen/", filename].concat();
let image = image::load_from_memory(
assets::load(fullpath.as_str()).expect("Error loading file").as_slice()
).unwrap();
assets::load(fullpath.as_str())
.expect("Error loading file")
.as_slice(),
)
.unwrap();
ui.new_image(renderer, &image).unwrap()
};
Imgs {
@ -170,7 +169,7 @@ impl MainMenuUi {
});
};
}
const TEXT_COLOR: Color = Color::Rgba(0.94, 0.94, 0.94, 0.8);
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
// Username
// TODO: get a lower resolution and cleaner input_bg.png
Image::new(self.imgs.input_bg)
@ -193,7 +192,9 @@ impl MainMenuUi {
// Note: TextBox limits the input string length to what fits in it
self.username = username.to_string();
}
TextBoxEvent::Enter => { login!(); }
TextBoxEvent::Enter => {
login!();
}
}
}
// Login error
@ -211,8 +212,7 @@ impl MainMenuUi {
.parent(ui_widgets.window)
.up_from(self.ids.username_bg, 35.0)
.set(self.ids.login_error_bg, ui_widgets);
text
.middle_of(self.ids.login_error_bg)
text.middle_of(self.ids.login_error_bg)
.set(self.ids.login_error, ui_widgets);
}
// Server address
@ -235,7 +235,9 @@ impl MainMenuUi {
TextBoxEvent::Update(server_address) => {
self.server_address = server_address.to_string();
}
TextBoxEvent::Enter => { login!(); }
TextBoxEvent::Enter => {
login!();
}
}
}
// Login button
@ -247,7 +249,7 @@ impl MainMenuUi {
.align_middle_x_of(self.ids.address_bg)
.label("Login")
.label_color(TEXT_COLOR)
.label_font_size(28)
.label_font_size(24)
.label_y(conrod_core::position::Relative::Scalar(5.0))
.set(self.ids.login_button, ui_widgets)
.was_clicked()