mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'ui' into 'master'
Ui See merge request veloren/fresh!14 Former-commit-id: 0222d01456af717d4956c7aff373fcefe1527042
This commit is contained in:
commit
b0f483184c
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,3 +19,4 @@
|
||||
# Veloren
|
||||
**/server_conf.toml
|
||||
**/keybinds.toml
|
||||
assets/voxygen
|
||||
|
12
README.md
12
README.md
@ -1,13 +1,3 @@
|
||||
# Fresh
|
||||
|
||||
An experiment
|
||||
|
||||
## Compile
|
||||
|
||||
```
|
||||
git clone https://gitlab.com/veloren/fresh.git
|
||||
cd fresh
|
||||
git submodule update --init --recursive
|
||||
rustup default nightly
|
||||
cargo build
|
||||
```
|
||||
An experiment
|
1
assets/voxygen
Submodule
1
assets/voxygen
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e25c963fe1726e7bdfd81633ed9b7004bc36dcf8
|
1
assets/voxygen/.gitignore
vendored
1
assets/voxygen/.gitignore
vendored
@ -1 +0,0 @@
|
||||
tmp/
|
3
chat-cli/.gitignore
vendored
3
chat-cli/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
/assets
|
||||
**/*.rs.bk.rar
|
||||
Cargo.lock
|
||||
|
@ -138,11 +138,10 @@ impl Client {
|
||||
// Handle new messages from the server
|
||||
frontend_events.append(&mut self.handle_new_messages()?);
|
||||
|
||||
// Step 3
|
||||
// Step 1
|
||||
if let Some(ecs_entity) = self.player {
|
||||
// TODO: remove this
|
||||
const PLAYER_VELOCITY: f32 = 100.0;
|
||||
|
||||
// TODO: Set acceleration instead
|
||||
self.state.write_component(ecs_entity, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY)));
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ gfx = "0.17"
|
||||
gfx_device_gl = { version = "0.15", optional = true }
|
||||
gfx_window_glutin = "0.28"
|
||||
glutin = "0.19"
|
||||
conrod_core = "0.63"
|
||||
conrod_winit = "0.63"
|
||||
|
||||
# ECS
|
||||
specs = "0.14"
|
||||
|
@ -1,17 +1,22 @@
|
||||
#version 330 core
|
||||
|
||||
in vec3 f_pos;
|
||||
in vec2 f_uv;
|
||||
|
||||
layout (std140)
|
||||
uniform u_locals {
|
||||
vec4 bounds;
|
||||
};
|
||||
in vec4 f_color;
|
||||
flat in uint f_mode;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec4 tgt_color;
|
||||
|
||||
void main() {
|
||||
tgt_color = texture(u_tex, f_uv);
|
||||
// Text
|
||||
if (f_mode == uint(0)) {
|
||||
tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a);
|
||||
// Image
|
||||
} else if (f_mode == uint(1)) {
|
||||
tgt_color = texture(u_tex, f_uv);
|
||||
// 2D Geometry
|
||||
} else if (f_mode == uint(2)) {
|
||||
tgt_color = f_color;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,19 @@
|
||||
#version 330 core
|
||||
|
||||
in vec3 v_pos;
|
||||
in vec2 v_pos;
|
||||
in vec2 v_uv;
|
||||
|
||||
layout (std140)
|
||||
uniform u_locals {
|
||||
vec4 bounds;
|
||||
};
|
||||
in vec4 v_color;
|
||||
in uint v_mode;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec3 f_pos;
|
||||
out vec2 f_uv;
|
||||
flat out uint f_mode;
|
||||
out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
f_uv = v_uv;
|
||||
f_pos = vec3(vec2(bounds.x, bounds.y) + v_pos.xy * vec2(bounds.z, bounds.w), 0);
|
||||
f_pos.xy = vec2(f_pos.x * 2.0 - 1.0, f_pos.y * -2.0 + 1.0);
|
||||
|
||||
gl_Position = vec4(f_pos, 1);
|
||||
f_color = v_color;
|
||||
gl_Position = vec4(v_pos, 0.0, 1.0);
|
||||
f_mode = v_mode;
|
||||
}
|
||||
|
165
voxygen/src/hud/chat.rs
Normal file
165
voxygen/src/hud/chat.rs
Normal file
@ -0,0 +1,165 @@
|
||||
use crate::ui::Ui;
|
||||
use conrod_core::{
|
||||
input::Key,
|
||||
position::Dimension,
|
||||
text::font::Id as FontId,
|
||||
widget::{Id, Button, List, Rectangle, Text, TextEdit},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, UiCell, Widget,
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
message_box,
|
||||
message_box_bg,
|
||||
input,
|
||||
input_bg,
|
||||
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,
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
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 let Some(scroll) = ui_widgets
|
||||
.widget_graph()
|
||||
.widget(self.ids.message_box)
|
||||
.and_then(|widget| widget.maybe_y_scroll_state)
|
||||
{
|
||||
// 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 {
|
||||
// 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
|
||||
.widget_graph()
|
||||
.widget(self.ids.message_box)
|
||||
.and_then(|widget| widget.maybe_y_scroll_state)
|
||||
{
|
||||
scroll.offset >= scroll.offset_bounds.start
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn scroll_to_bottom(&self, ui_widgets: &mut UiCell) {
|
||||
ui_widgets.scroll_widget(self.ids.message_box, [0.0, std::f64::MAX]);
|
||||
}
|
||||
pub 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;
|
||||
}
|
||||
|
||||
// Chat input with rectangle as background
|
||||
let text_edit = TextEdit::new(&self.input)
|
||||
.w(470.0)
|
||||
.restrict_to_height(false)
|
||||
.font_size(14)
|
||||
.font_id(font)
|
||||
.bottom_left_with_margins_on(ui_widgets.window, 10.0, 10.0);
|
||||
let dims = match (
|
||||
text_edit.get_x_dimension(ui_widgets),
|
||||
text_edit.get_y_dimension(ui_widgets),
|
||||
) {
|
||||
(Dimension::Absolute(x), Dimension::Absolute(y)) => [x, y],
|
||||
_ => [0.0, 0.0],
|
||||
};
|
||||
Rectangle::fill(dims)
|
||||
.rgba(0.0, 0.0, 0.0, 0.8)
|
||||
.x_position(text_edit.get_x_position(ui_widgets))
|
||||
.y_position(text_edit.get_y_position(ui_widgets))
|
||||
.set(self.ids.input_bg, ui_widgets);
|
||||
if let Some(str) = text_edit.set(self.ids.input, ui_widgets) {
|
||||
self.input = str.to_string();
|
||||
self.input.retain(|c| c != '\n');
|
||||
}
|
||||
|
||||
// Message box
|
||||
Rectangle::fill([470.0, 180.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)
|
||||
.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_id(font)
|
||||
.rgba(220.0, 220.0, 220.0, 1.0),
|
||||
ui_widgets,
|
||||
)
|
||||
}
|
||||
//if let Some(s) = scrollbar {
|
||||
// s.set(ui_widgets)
|
||||
//}
|
||||
|
||||
// Chat Arrow
|
||||
if !self.scrolled_to_bottom(ui_widgets) {
|
||||
if Button::image(imgs.chat_arrow)
|
||||
.w_h(22.0, 22.0)
|
||||
.hover_image(imgs.chat_arrow_mo)
|
||||
.press_image(imgs.chat_arrow_press)
|
||||
.bottom_right_with_margins_on(self.ids.message_box_bg, 2.0, 2.0)
|
||||
.set(self.ids.chat_arrow, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.scroll_to_bottom(ui_widgets);
|
||||
}
|
||||
}
|
||||
|
||||
// If enter is pressed send the current message
|
||||
if ui_widgets
|
||||
.widget_input(self.ids.input)
|
||||
.presses()
|
||||
.key()
|
||||
.any(|key_press| match key_press.key {
|
||||
Key::Return => true,
|
||||
_ => false,
|
||||
})
|
||||
{
|
||||
let new_message = self.input.clone();
|
||||
self.input.clear();
|
||||
Some(new_message)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
1225
voxygen/src/hud/mod.rs
Normal file
1225
voxygen/src/hud/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,9 @@
|
||||
#![feature(drain_filter)]
|
||||
#![recursion_limit="2048"]
|
||||
|
||||
pub mod anim;
|
||||
pub mod error;
|
||||
pub mod hud;
|
||||
pub mod key_state;
|
||||
pub mod menu;
|
||||
pub mod mesh;
|
||||
@ -23,7 +25,7 @@ use pretty_env_logger;
|
||||
|
||||
// Crate
|
||||
use crate::{
|
||||
menu::title::TitleState,
|
||||
menu::main::MainMenuState,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
@ -37,6 +39,7 @@ impl GlobalState {
|
||||
/// effects a state may have made).
|
||||
pub fn on_play_state_changed(&mut self) {
|
||||
self.window.grab_cursor(false);
|
||||
self.window.needs_refresh_resize();
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,8 +78,8 @@ fn main() {
|
||||
};
|
||||
|
||||
// Set up the initial play state
|
||||
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(TitleState::new(
|
||||
&mut global_state.window.renderer_mut(),
|
||||
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(
|
||||
&mut global_state.window,
|
||||
))];
|
||||
states.last().map(|current_state| {
|
||||
log::info!("Started game with state '{}'", current_state.name())
|
||||
|
85
voxygen/src/menu/char_selection/mod.rs
Normal file
85
voxygen/src/menu/char_selection/mod.rs
Normal file
@ -0,0 +1,85 @@
|
||||
mod ui;
|
||||
|
||||
use crate::{
|
||||
window::{Event, Window},
|
||||
session::SessionState,
|
||||
GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
use common::clock::Clock;
|
||||
use std::time::Duration;
|
||||
use ui::CharSelectionUi;
|
||||
use vek::*;
|
||||
|
||||
const FPS: u64 = 60;
|
||||
|
||||
pub struct CharSelectionState {
|
||||
char_selection_ui: CharSelectionUi,
|
||||
}
|
||||
|
||||
impl CharSelectionState {
|
||||
/// Create a new `CharSelectionState`
|
||||
pub fn new(window: &mut Window) -> Self {
|
||||
Self {
|
||||
char_selection_ui: CharSelectionUi::new(window),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The background colour
|
||||
const BG_COLOR: Rgba<f32> = Rgba {
|
||||
r: 0.0,
|
||||
g: 0.3,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
impl PlayState for CharSelectionState {
|
||||
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::new();
|
||||
|
||||
loop {
|
||||
// Handle window events
|
||||
for event in global_state.window.fetch_events() {
|
||||
match event {
|
||||
Event::Close => return PlayStateResult::Shutdown,
|
||||
// Pass events to ui
|
||||
Event::Ui(event) => {
|
||||
self.char_selection_ui.handle_event(event);
|
||||
}
|
||||
// Ignore all other events
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
global_state.window.renderer_mut().clear(BG_COLOR);
|
||||
|
||||
// Maintain the UI
|
||||
for event in self.char_selection_ui.maintain(global_state.window.renderer_mut()) {
|
||||
match event {
|
||||
ui::Event::Logout => return PlayStateResult::Pop,
|
||||
ui::Event::Play => return PlayStateResult::Push(
|
||||
Box::new(SessionState::new(&mut global_state.window).unwrap()) // TODO: Handle this error
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the UI to the screen
|
||||
self.char_selection_ui.render(global_state.window.renderer_mut());
|
||||
|
||||
// Finish the frame
|
||||
global_state.window.renderer_mut().flush();
|
||||
global_state
|
||||
.window
|
||||
.swap_buffers()
|
||||
.expect("Failed to swap window buffers");
|
||||
|
||||
// Wait for the next tick
|
||||
clock.tick(Duration::from_millis(1000 / FPS));
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Title"
|
||||
}
|
||||
}
|
1869
voxygen/src/menu/char_selection/ui.rs
Normal file
1869
voxygen/src/menu/char_selection/ui.rs
Normal file
File diff suppressed because it is too large
Load Diff
87
voxygen/src/menu/main/mod.rs
Normal file
87
voxygen/src/menu/main/mod.rs
Normal file
@ -0,0 +1,87 @@
|
||||
mod ui;
|
||||
|
||||
use super::char_selection::CharSelectionState;
|
||||
use crate::{
|
||||
window::{Event, Window},
|
||||
GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
use common::clock::Clock;
|
||||
use std::time::Duration;
|
||||
use ui::{Event as MainMenuEvent, MainMenuUi};
|
||||
use vek::*;
|
||||
|
||||
const FPS: u64 = 60;
|
||||
|
||||
pub struct MainMenuState {
|
||||
main_menu_ui: MainMenuUi,
|
||||
}
|
||||
|
||||
impl MainMenuState {
|
||||
/// Create a new `MainMenuState`
|
||||
pub fn new(window: &mut Window) -> Self {
|
||||
Self {
|
||||
main_menu_ui: MainMenuUi::new(window),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Background colour
|
||||
const BG_COLOR: Rgba<f32> = Rgba {
|
||||
r: 0.0,
|
||||
g: 0.3,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
impl PlayState for MainMenuState {
|
||||
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::new();
|
||||
|
||||
loop {
|
||||
// Handle window events
|
||||
for event in global_state.window.fetch_events() {
|
||||
match event {
|
||||
Event::Close => return PlayStateResult::Shutdown,
|
||||
// Pass events to ui
|
||||
Event::Ui(event) => {
|
||||
self.main_menu_ui.handle_event(event);
|
||||
}
|
||||
// Ignore all other events
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
global_state.window.renderer_mut().clear(BG_COLOR);
|
||||
|
||||
// Maintain the UI
|
||||
for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) {
|
||||
match event {
|
||||
MainMenuEvent::LoginAttempt{ username, server_address } =>
|
||||
// For now just start a new session
|
||||
return PlayStateResult::Push(
|
||||
Box::new(CharSelectionState::new(&mut global_state.window))
|
||||
),
|
||||
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the UI to the screen
|
||||
self.main_menu_ui.render(global_state.window.renderer_mut());
|
||||
|
||||
// Finish the frame
|
||||
global_state.window.renderer_mut().flush();
|
||||
global_state
|
||||
.window
|
||||
.swap_buffers()
|
||||
.expect("Failed to swap window buffers");
|
||||
|
||||
// Wait for the next tick
|
||||
clock.tick(Duration::from_millis(1000 / FPS));
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Title"
|
||||
}
|
||||
}
|
307
voxygen/src/menu/main/ui.rs
Normal file
307
voxygen/src/menu/main/ui.rs
Normal file
@ -0,0 +1,307 @@
|
||||
use crate::{
|
||||
render::Renderer,
|
||||
ui::{self, ScaleMode, Ui},
|
||||
window::Window,
|
||||
};
|
||||
use conrod_core::{
|
||||
color::TRANSPARENT,
|
||||
image::Id as ImgId,
|
||||
text::font::Id as FontId,
|
||||
widget::{text_box::Event as TextBoxEvent, Button, Image, TextBox},
|
||||
widget_ids, Borderable, Color,
|
||||
Colorable, Labelable, Positionable, Sizeable, Widget,
|
||||
};
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
// Background and logo
|
||||
bg,
|
||||
v_logo,
|
||||
alpha_version,
|
||||
// Login, Singleplayer
|
||||
login_button,
|
||||
login_text,
|
||||
address_text,
|
||||
address_bg,
|
||||
address_field,
|
||||
username_text,
|
||||
username_bg,
|
||||
username_field,
|
||||
singleplayer_button,
|
||||
singleplayer_text,
|
||||
// Buttons
|
||||
servers_button,
|
||||
settings_button,
|
||||
quit_button,
|
||||
}
|
||||
}
|
||||
|
||||
struct Imgs {
|
||||
bg: ImgId,
|
||||
v_logo: ImgId,
|
||||
|
||||
input_bg: ImgId,
|
||||
|
||||
login_button: ImgId,
|
||||
login_button_hover: ImgId,
|
||||
login_button_press: ImgId,
|
||||
|
||||
button: ImgId,
|
||||
button_hover: ImgId,
|
||||
button_press: ImgId,
|
||||
}
|
||||
impl Imgs {
|
||||
fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs {
|
||||
// TODO: update paths
|
||||
let mut load = |filename| {
|
||||
let image = image::open(
|
||||
&[
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../assets/voxygen/",
|
||||
filename,
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
.unwrap();
|
||||
ui.new_image(renderer, &image).unwrap()
|
||||
};
|
||||
Imgs {
|
||||
bg: load("background/bg_main.png"),
|
||||
v_logo: load("element/v_logo.png"),
|
||||
|
||||
// Input fields
|
||||
input_bg: load("element/misc_backgrounds/textbox.png"),
|
||||
|
||||
// Login button
|
||||
login_button: load("element/buttons/button_login.png"),
|
||||
login_button_hover: load("element/buttons/button_login_hover.png"),
|
||||
login_button_press: load("element/buttons/button_login_press.png"),
|
||||
|
||||
// Servers, settings, and quit buttons
|
||||
button: load("element/buttons/button.png"),
|
||||
button_hover: load("element/buttons/button_hover.png"),
|
||||
button_press: load("element/buttons/button_press.png"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
LoginAttempt {
|
||||
username: String,
|
||||
server_address: String,
|
||||
},
|
||||
Quit,
|
||||
}
|
||||
|
||||
pub struct MainMenuUi {
|
||||
ui: Ui,
|
||||
ids: Ids,
|
||||
imgs: Imgs,
|
||||
font_metamorph: FontId,
|
||||
font_opensans: FontId,
|
||||
username: String,
|
||||
server_address: String,
|
||||
}
|
||||
|
||||
impl MainMenuUi {
|
||||
pub fn new(window: &mut Window) -> Self {
|
||||
let mut ui = Ui::new(window).unwrap();
|
||||
// TODO: adjust/remove this, right now it is used to demonstrate window scaling functionality
|
||||
ui.scaling_mode(ScaleMode::RelativeToWindow([1920.0, 1080.0].into()));
|
||||
// Generate ids
|
||||
let ids = Ids::new(ui.id_generator());
|
||||
// Load images
|
||||
let imgs = Imgs::new(&mut ui, window.renderer_mut());
|
||||
// Load fonts
|
||||
let font_opensans = ui.new_font(
|
||||
conrod_core::text::font::from_file(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../assets/voxygen/font/OpenSans-Regular.ttf"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
let font_metamorph = ui.new_font(
|
||||
conrod_core::text::font::from_file(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../assets/voxygen/font/Metamorphous-Regular.ttf"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
Self {
|
||||
ui,
|
||||
imgs,
|
||||
ids,
|
||||
font_metamorph,
|
||||
font_opensans,
|
||||
username: "Username".to_string(),
|
||||
server_address: "Server Address".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_layout(&mut self) -> Vec<Event> {
|
||||
let mut events = Vec::new();
|
||||
let ref mut ui_widgets = self.ui.set_widgets();
|
||||
// Background image, Veloren logo, Alpha-Version Label
|
||||
Image::new(self.imgs.bg)
|
||||
.middle_of(ui_widgets.window)
|
||||
.set(self.ids.bg, ui_widgets);
|
||||
Button::image(self.imgs.v_logo)
|
||||
.w_h(346.0, 111.0)
|
||||
.top_left_with_margins(30.0, 40.0)
|
||||
.label("Alpha 0.1")
|
||||
.label_rgba(255.0, 255.0, 255.0, 1.0)
|
||||
.label_font_size(10)
|
||||
.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);
|
||||
|
||||
// Input fields
|
||||
// Used when the login button is pressed, or enter is pressed within input field
|
||||
macro_rules! login {
|
||||
() => {
|
||||
events.push(Event::LoginAttempt {
|
||||
username: self.username.clone(),
|
||||
server_address: self.server_address.clone(),
|
||||
});
|
||||
};
|
||||
}
|
||||
// Username
|
||||
// TODO: get a lower resolution and cleaner input_bg.png
|
||||
Image::new(self.imgs.input_bg)
|
||||
.w_h(337.0, 67.0)
|
||||
.middle_of(ui_widgets.window)
|
||||
.set(self.ids.username_bg, ui_widgets);
|
||||
for event in TextBox::new(&self.username)
|
||||
.w_h(580.0 / 2.0, 60.0 / 2.0)
|
||||
.mid_bottom_with_margin_on(self.ids.username_bg, 44.0 / 2.0)
|
||||
.font_size(20)
|
||||
.font_id(self.font_opensans)
|
||||
.text_color(Color::Rgba(220.0, 220.0, 220.0, 0.8))
|
||||
// transparent background
|
||||
.color(TRANSPARENT)
|
||||
.border_color(TRANSPARENT)
|
||||
.set(self.ids.username_field, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
TextBoxEvent::Update(username) => {
|
||||
// Note: TextBox limits the input string length to what fits in it
|
||||
self.username = username.to_string();
|
||||
}
|
||||
TextBoxEvent::Enter => login!(),
|
||||
}
|
||||
}
|
||||
// Server address
|
||||
Image::new(self.imgs.input_bg)
|
||||
.w_h(337.0, 67.0)
|
||||
.down_from(self.ids.username_bg, 10.0)
|
||||
.set(self.ids.address_bg, ui_widgets);
|
||||
for event in TextBox::new(&self.server_address)
|
||||
.w_h(580.0 / 2.0, 60.0 / 2.0)
|
||||
.mid_bottom_with_margin_on(self.ids.address_bg, 44.0 / 2.0)
|
||||
.font_size(20)
|
||||
.font_id(self.font_opensans)
|
||||
.text_color(Color::Rgba(220.0, 220.0, 220.0, 0.8))
|
||||
// transparent background
|
||||
.color(TRANSPARENT)
|
||||
.border_color(TRANSPARENT)
|
||||
.set(self.ids.address_field, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
TextBoxEvent::Update(server_address) => {
|
||||
self.server_address = server_address.to_string();
|
||||
}
|
||||
TextBoxEvent::Enter => login!(),
|
||||
}
|
||||
}
|
||||
// Login button
|
||||
if Button::image(self.imgs.login_button)
|
||||
.hover_image(self.imgs.login_button_hover)
|
||||
.press_image(self.imgs.login_button_press)
|
||||
.w_h(258.0, 68.0)
|
||||
.down_from(self.ids.address_bg, 20.0)
|
||||
.align_middle_x_of(self.ids.address_bg)
|
||||
.label("Login")
|
||||
.label_rgba(220.0, 220.0, 220.0, 0.8)
|
||||
.label_font_size(28)
|
||||
.label_y(conrod_core::position::Relative::Scalar(5.0))
|
||||
.set(self.ids.login_button, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
login!();
|
||||
}
|
||||
//Singleplayer button
|
||||
if Button::image(self.imgs.login_button)
|
||||
.hover_image(self.imgs.login_button_hover)
|
||||
.press_image(self.imgs.login_button_press)
|
||||
.w_h(258.0, 68.0)
|
||||
.down_from(self.ids.login_button, 20.0)
|
||||
.align_middle_x_of(self.ids.address_bg)
|
||||
.label("Singleplayer")
|
||||
.label_rgba(220.0, 220.0, 220.0, 0.8)
|
||||
.label_font_size(26)
|
||||
.label_y(conrod_core::position::Relative::Scalar(5.0))
|
||||
.label_x(conrod_core::position::Relative::Scalar(2.0))
|
||||
.set(self.ids.singleplayer_button, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
login!();
|
||||
}
|
||||
// Quit
|
||||
if Button::image(self.imgs.button)
|
||||
.w_h(203.0, 53.0)
|
||||
.bottom_left_with_margins_on(ui_widgets.window, 60.0, 30.0)
|
||||
.hover_image(self.imgs.button_hover)
|
||||
.press_image(self.imgs.button_press)
|
||||
.label("Quit")
|
||||
.label_rgba(220.0, 220.0, 220.0, 0.8)
|
||||
.label_font_size(20)
|
||||
.label_y(conrod_core::position::Relative::Scalar(3.0))
|
||||
.set(self.ids.quit_button, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::Quit);
|
||||
};
|
||||
// Settings
|
||||
if Button::image(self.imgs.button)
|
||||
.w_h(203.0, 53.0)
|
||||
.up_from(self.ids.quit_button, 8.0)
|
||||
.hover_image(self.imgs.button_hover)
|
||||
.press_image(self.imgs.button_press)
|
||||
.label("Settings")
|
||||
.label_rgba(220.0, 220.0, 220.0, 0.8)
|
||||
.label_font_size(20)
|
||||
.label_y(conrod_core::position::Relative::Scalar(3.0))
|
||||
.set(self.ids.settings_button, ui_widgets)
|
||||
.was_clicked()
|
||||
{};
|
||||
// Servers
|
||||
if Button::image(self.imgs.button)
|
||||
.w_h(203.0, 53.0)
|
||||
.up_from(self.ids.settings_button, 8.0)
|
||||
.hover_image(self.imgs.button_hover)
|
||||
.press_image(self.imgs.button_press)
|
||||
.label("Servers")
|
||||
.label_rgba(220.0, 220.0, 220.0, 0.8)
|
||||
.label_font_size(20)
|
||||
.label_y(conrod_core::position::Relative::Scalar(3.0))
|
||||
.set(self.ids.servers_button, ui_widgets)
|
||||
.was_clicked()
|
||||
{};
|
||||
|
||||
events
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: ui::Event) {
|
||||
self.ui.handle_event(event);
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer) -> Vec<Event> {
|
||||
let events = self.update_layout();
|
||||
self.ui.maintain(renderer);
|
||||
events
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer) {
|
||||
self.ui.render(renderer);
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod title;
|
||||
pub mod char_selection;
|
||||
pub mod main;
|
||||
|
@ -1,74 +0,0 @@
|
||||
// Library
|
||||
use vek::*;
|
||||
use image;
|
||||
|
||||
// Crate
|
||||
use crate::{
|
||||
PlayState,
|
||||
PlayStateResult,
|
||||
GlobalState,
|
||||
window::Event,
|
||||
session::SessionState,
|
||||
render::Renderer,
|
||||
ui::{
|
||||
Ui,
|
||||
element::{
|
||||
Widget,
|
||||
image::Image,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct TitleState {
|
||||
ui: Ui,
|
||||
}
|
||||
|
||||
impl TitleState {
|
||||
/// Create a new `TitleState`
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
let img = Image::new(renderer, &image::open(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/test.png")).unwrap()).unwrap();
|
||||
let widget = Widget::new(renderer, img).unwrap();
|
||||
Self {
|
||||
ui: Ui::new(renderer, widget).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The background colour
|
||||
const BG_COLOR: Rgba<f32> = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 };
|
||||
|
||||
impl PlayState for TitleState {
|
||||
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
|
||||
loop {
|
||||
// Handle window events
|
||||
for event in global_state.window.fetch_events() {
|
||||
match event {
|
||||
Event::Close => return PlayStateResult::Shutdown,
|
||||
// When space is pressed, start a session
|
||||
Event::Char(' ') => return PlayStateResult::Push(
|
||||
Box::new(SessionState::new(global_state.window.renderer_mut()).unwrap()), // TODO: Handle this error
|
||||
),
|
||||
// Ignore all other events
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the screen
|
||||
global_state.window.renderer_mut().clear(BG_COLOR);
|
||||
|
||||
// Maintain the UI
|
||||
self.ui.maintain(global_state.window.renderer_mut());
|
||||
|
||||
// Draw the UI to the screen
|
||||
self.ui.render(global_state.window.renderer_mut());
|
||||
|
||||
// Finish the frame
|
||||
global_state.window.renderer_mut().flush();
|
||||
global_state.window
|
||||
.swap_buffers()
|
||||
.expect("Failed to swap window buffers");
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str { "Title" }
|
||||
}
|
@ -13,6 +13,11 @@ impl<P: Pipeline> Mesh<P> {
|
||||
Self { verts: vec![] }
|
||||
}
|
||||
|
||||
/// Clear vertices, allows reusing allocated memory of the underlying Vec
|
||||
pub fn clear(&mut self) {
|
||||
self.verts.clear();
|
||||
}
|
||||
|
||||
/// Get a slice referencing the vertices of this mesh.
|
||||
pub fn vertices(&self) -> &[P::Vertex] {
|
||||
&self.verts
|
||||
|
@ -30,9 +30,10 @@ pub use self::{
|
||||
Locals as TerrainLocals,
|
||||
},
|
||||
ui::{
|
||||
create_quad_mesh as create_ui_quad_mesh,
|
||||
create_quad as create_ui_quad,
|
||||
create_tri as create_ui_tri,
|
||||
Mode as UiMode,
|
||||
UiPipeline,
|
||||
Locals as UiLocals,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -48,6 +49,7 @@ use gfx;
|
||||
pub enum RenderError {
|
||||
PipelineError(gfx::PipelineStateError<String>),
|
||||
UpdateError(gfx::UpdateError<usize>),
|
||||
TexUpdateError(gfx::UpdateError<[u16; 3]>),
|
||||
CombinedError(gfx::CombinedError),
|
||||
}
|
||||
|
||||
|
@ -1,73 +1,109 @@
|
||||
// Library
|
||||
use gfx::{
|
||||
self,
|
||||
// Macros
|
||||
gfx_defines,
|
||||
gfx_vertex_struct_meta,
|
||||
gfx_constant_struct_meta,
|
||||
gfx_impl_struct_meta,
|
||||
gfx_pipeline,
|
||||
gfx_pipeline_inner,
|
||||
};
|
||||
|
||||
// Local
|
||||
use vek::*;
|
||||
use super::super::{
|
||||
Pipeline,
|
||||
TgtColorFmt,
|
||||
TgtDepthFmt,
|
||||
Mesh,
|
||||
Quad,
|
||||
Pipeline,
|
||||
TgtColorFmt,
|
||||
TgtDepthFmt,
|
||||
Mesh,
|
||||
Quad,
|
||||
Tri,
|
||||
};
|
||||
|
||||
gfx_defines! {
|
||||
vertex Vertex {
|
||||
pos: [f32; 3] = "v_pos",
|
||||
pos: [f32; 2] = "v_pos",
|
||||
uv: [f32; 2] = "v_uv",
|
||||
}
|
||||
|
||||
constant Locals {
|
||||
bounds: [f32; 4] = "bounds",
|
||||
color: [f32; 4] = "v_color",
|
||||
mode: u32 = "v_mode",
|
||||
}
|
||||
|
||||
pipeline pipe {
|
||||
vbuf: gfx::VertexBuffer<Vertex> = (),
|
||||
|
||||
locals: gfx::ConstantBuffer<Locals> = "u_locals",
|
||||
tex: gfx::TextureSampler<[f32; 4]> = "u_tex",
|
||||
|
||||
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
|
||||
scissor: gfx::Scissor = (),
|
||||
|
||||
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA),
|
||||
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::PASS_TEST,
|
||||
}
|
||||
}
|
||||
|
||||
impl Locals {
|
||||
pub fn default() -> Self {
|
||||
Self { bounds: [0.0, 0.0, 1.0, 1.0] }
|
||||
}
|
||||
|
||||
pub fn new(bounds: [f32; 4]) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiPipeline;
|
||||
|
||||
impl Pipeline for UiPipeline {
|
||||
type Vertex = Vertex;
|
||||
}
|
||||
|
||||
pub fn create_quad_mesh() -> Mesh<UiPipeline> {
|
||||
let mut mesh = Mesh::new();
|
||||
/// Draw text from the text cache texture `tex` in the fragment shader.
|
||||
pub const MODE_TEXT: u32 = 0;
|
||||
/// Draw an image from the texture at `tex` in the fragment shader.
|
||||
pub const MODE_IMAGE: u32 = 1;
|
||||
/// Ignore `tex` and draw simple, colored 2D geometry.
|
||||
pub const MODE_GEOMETRY: u32 = 2;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mesh.push_quad(Quad::new(
|
||||
Vertex { pos: [0.0, 0.0, 0.0], uv: [0.0, 0.0] },
|
||||
Vertex { pos: [0.0, 1.0, 0.0], uv: [0.0, 1.0] },
|
||||
Vertex { pos: [1.0, 1.0, 0.0], uv: [1.0, 1.0] },
|
||||
Vertex { pos: [1.0, 0.0, 0.0], uv: [1.0, 0.0] },
|
||||
));
|
||||
|
||||
mesh
|
||||
pub enum Mode {
|
||||
Text,
|
||||
Image,
|
||||
Geometry,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
fn value(self) -> u32 {
|
||||
match self {
|
||||
Mode::Text => MODE_TEXT,
|
||||
Mode::Image => MODE_IMAGE,
|
||||
Mode::Geometry => MODE_GEOMETRY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_quad(rect: Aabr<f32>, uv_rect: Aabr<f32>, color: [f32; 4], mode: Mode) -> Quad<UiPipeline> {
|
||||
let mode_val = mode.value();
|
||||
let v = |pos, uv| {
|
||||
Vertex {
|
||||
pos,
|
||||
uv,
|
||||
color,
|
||||
mode: mode_val,
|
||||
}
|
||||
};
|
||||
let aabr_to_lbrt = |aabr: Aabr<f32>| (
|
||||
aabr.min.x, aabr.min.y,
|
||||
aabr.max.x, aabr.max.y,
|
||||
);
|
||||
|
||||
let (l, b, r, t) = aabr_to_lbrt(rect);
|
||||
let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect);
|
||||
Quad::new(
|
||||
v([r, t], [uv_r, uv_t]),
|
||||
v([l, t], [uv_l, uv_t]),
|
||||
v([l, b], [uv_l, uv_b]),
|
||||
v([r, b], [uv_r, uv_b]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_tri(tri: [[f32; 2]; 3], uv_tri: [[f32; 2]; 3], color: [f32; 4], mode: Mode) -> Tri<UiPipeline> {
|
||||
let mode_val = mode.value();
|
||||
let v = |pos, uv| {
|
||||
Vertex {
|
||||
pos,
|
||||
uv,
|
||||
color,
|
||||
mode: mode_val,
|
||||
}
|
||||
};
|
||||
Tri::new(
|
||||
v([tri[0][0], tri[0][1]], [uv_tri[0][0], uv_tri[0][1]]),
|
||||
v([tri[1][0], tri[1][1]], [uv_tri[1][0], uv_tri[1][1]]),
|
||||
v([tri[2][0], tri[2][1]], [uv_tri[2][0], uv_tri[2][1]]),
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
// Library
|
||||
use vek::*;
|
||||
use gfx::{
|
||||
self,
|
||||
traits::{Device, FactoryExt},
|
||||
};
|
||||
use image;
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
consts::Consts,
|
||||
mesh::Mesh,
|
||||
@ -173,6 +170,34 @@ impl Renderer {
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the specified dimensions
|
||||
pub fn create_dynamic_texture<P: Pipeline>(
|
||||
&mut self,
|
||||
dims: Vec2<u16>
|
||||
) -> Result<Texture<P>, RenderError> {
|
||||
Texture::new_dynamic(
|
||||
&mut self.factory,
|
||||
dims.x,
|
||||
dims.y,
|
||||
)
|
||||
}
|
||||
|
||||
/// Update a texture with the provided offset, size, and data
|
||||
pub fn update_texture<P: Pipeline>(
|
||||
&mut self,
|
||||
texture: &Texture<P>,
|
||||
offset: [u16; 2],
|
||||
size: [u16; 2],
|
||||
data: &[[u8; 4]]
|
||||
) -> Result<(), RenderError> {
|
||||
texture.update(
|
||||
&mut self.encoder,
|
||||
offset,
|
||||
size,
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
/// Queue the rendering of the provided skybox model in the upcoming frame.
|
||||
pub fn render_skybox(
|
||||
&mut self,
|
||||
@ -239,15 +264,16 @@ impl Renderer {
|
||||
pub fn render_ui_element(
|
||||
&mut self,
|
||||
model: &Model<ui::UiPipeline>,
|
||||
locals: &Consts<ui::Locals>,
|
||||
tex: &Texture<ui::UiPipeline>,
|
||||
scissor: Aabr<u16>,
|
||||
) {
|
||||
let Aabr { min, max } = scissor;
|
||||
self.encoder.draw(
|
||||
&model.slice,
|
||||
&self.ui_pipeline.pso,
|
||||
&ui::pipe::Data {
|
||||
vbuf: model.vbuf.clone(),
|
||||
locals: locals.buf.clone(),
|
||||
scissor: gfx::Rect { x: min.x, y: min.y, w: max.x - min.x, h: max.y - min.y },
|
||||
tex: (tex.srv.clone(), tex.sampler.clone()),
|
||||
tgt_color: self.tgt_color_view.clone(),
|
||||
tgt_depth: self.tgt_depth_view.clone(),
|
||||
|
@ -10,6 +10,7 @@ use image::{
|
||||
DynamicImage,
|
||||
GenericImageView,
|
||||
};
|
||||
use vek::Vec2;
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
@ -54,4 +55,64 @@ impl<P: Pipeline> Texture<P> {
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
pub fn new_dynamic(
|
||||
factory: &mut gfx_backend::Factory,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> Result<Self, RenderError> {
|
||||
let tex = factory.create_texture(
|
||||
gfx::texture::Kind::D2(
|
||||
width,
|
||||
height,
|
||||
gfx::texture::AaMode::Single,
|
||||
),
|
||||
1 as gfx::texture::Level,
|
||||
gfx::memory::Bind::SHADER_RESOURCE,
|
||||
gfx::memory::Usage::Dynamic,
|
||||
Some(<<ShaderFormat as gfx::format::Formatted>::Channel as gfx::format::ChannelTyped>::get_channel_type()),
|
||||
)
|
||||
.map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?;
|
||||
|
||||
let srv =
|
||||
factory.view_texture_as_shader_resource::<ShaderFormat>(&tex, (0, 0), gfx::format::Swizzle::new())
|
||||
.map_err(|err| RenderError::CombinedError(gfx::CombinedError::Resource(err)))?;
|
||||
|
||||
Ok(Self {
|
||||
tex,
|
||||
srv,
|
||||
sampler: factory.create_sampler(gfx::texture::SamplerInfo::new(
|
||||
gfx::texture::FilterMethod::Bilinear,
|
||||
gfx::texture::WrapMode::Clamp,
|
||||
)),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
// Updates a texture with the given data (used for updating the glyph cache texture)
|
||||
pub fn update(
|
||||
&self,
|
||||
encoder: &mut gfx::Encoder<gfx_backend::Resources, gfx_backend::CommandBuffer>,
|
||||
offset: [u16; 2],
|
||||
size: [u16; 2],
|
||||
data: &[[u8; 4]],
|
||||
) -> Result<(), RenderError> {
|
||||
let info = gfx::texture::ImageInfoCommon {
|
||||
xoffset: offset[0],
|
||||
yoffset: offset[1],
|
||||
zoffset: 0,
|
||||
width: size[0],
|
||||
height: size[1],
|
||||
depth: 0,
|
||||
format: (),
|
||||
mipmap: 0,
|
||||
};
|
||||
encoder
|
||||
.update_texture::<<ShaderFormat as gfx::format::Formatted>::Surface, ShaderFormat>(&self.tex, None, info, data)
|
||||
.map_err(|err| RenderError::TexUpdateError(err))
|
||||
}
|
||||
/// Get dimensions of the represented image
|
||||
pub fn get_dimensions(&self) -> Vec2<u16> {
|
||||
let (w, h, ..) = self.tex.get_info().kind.get_dimensions();
|
||||
Vec2::new(w, h)
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ pub struct Scene {
|
||||
|
||||
// TODO: Make a proper asset loading system
|
||||
fn load_segment(filename: &'static str) -> Segment {
|
||||
Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/").to_string() + filename)).unwrap())
|
||||
Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/voxygen/voxel/").to_string() + filename)).unwrap())
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
|
@ -18,9 +18,10 @@ use crate::{
|
||||
PlayStateResult,
|
||||
GlobalState,
|
||||
key_state::KeyState,
|
||||
window::{Event, Key},
|
||||
window::{Event, Key, Window},
|
||||
render::Renderer,
|
||||
scene::Scene,
|
||||
hud::{Hud, Event as HudEvent},
|
||||
};
|
||||
|
||||
const FPS: u64 = 60;
|
||||
@ -29,18 +30,20 @@ pub struct SessionState {
|
||||
scene: Scene,
|
||||
client: Client,
|
||||
key_state: KeyState,
|
||||
hud: Hud,
|
||||
}
|
||||
|
||||
/// Represents an active game session (i.e: one that is being played)
|
||||
impl SessionState {
|
||||
/// Create a new `SessionState`
|
||||
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
|
||||
pub fn new(window: &mut Window) -> Result<Self, Error> {
|
||||
let client = Client::new(([127, 0, 0, 1], 59003))?.with_test_state(); // <--- TODO: Remove this
|
||||
Ok(Self {
|
||||
// Create a scene for this session. The scene handles visible elements of the game world
|
||||
scene: Scene::new(renderer, &client),
|
||||
scene: Scene::new(window.renderer_mut(), &client),
|
||||
client,
|
||||
key_state: KeyState::new(),
|
||||
hud: Hud::new(window),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -60,7 +63,14 @@ impl SessionState {
|
||||
let dir_vec = self.key_state.dir_vec();
|
||||
let move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
|
||||
|
||||
self.client.tick(client::Input { move_dir }, dt)?;
|
||||
for event in self.client.tick(client::Input { move_dir }, dt)? {
|
||||
match event {
|
||||
client::Event::Chat(msg) => {
|
||||
self.hud.new_message(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -78,6 +88,8 @@ impl SessionState {
|
||||
|
||||
// Render the screen using the global renderer
|
||||
self.scene.render_to(renderer);
|
||||
// Draw the UI to the screen
|
||||
self.hud.render(renderer);
|
||||
|
||||
// Finish the frame
|
||||
renderer.flush();
|
||||
@ -105,13 +117,23 @@ impl PlayState for SessionState {
|
||||
loop {
|
||||
// Handle window events
|
||||
for event in global_state.window.fetch_events() {
|
||||
|
||||
// Pass all events to the ui first
|
||||
if self.hud.handle_event(event.clone()) {
|
||||
continue;
|
||||
}
|
||||
let _handled = match event {
|
||||
Event::Close => return PlayStateResult::Shutdown,
|
||||
// When 'q' is pressed, exit the session
|
||||
Event::Char('q') => return PlayStateResult::Pop,
|
||||
// When 'm' is pressed, open/close the in-game test menu
|
||||
Event::Char('m') => self.hud.toggle_menu(),
|
||||
// Close windows on esc
|
||||
Event::KeyDown(Key::Escape) => self.hud.toggle_windows(),
|
||||
// Toggle cursor grabbing
|
||||
Event::KeyDown(Key::ToggleCursor) => {
|
||||
global_state.window.grab_cursor(!global_state.window.is_cursor_grabbed());
|
||||
self.hud.update_grab(global_state.window.is_cursor_grabbed());
|
||||
},
|
||||
// Movement Key Pressed
|
||||
Event::KeyDown(Key::MoveForward) => self.key_state.up = true,
|
||||
@ -135,6 +157,17 @@ impl PlayState for SessionState {
|
||||
|
||||
// Maintain the scene
|
||||
self.scene.maintain(global_state.window.renderer_mut(), &self.client);
|
||||
// Maintain the UI
|
||||
for event in self.hud.maintain(global_state.window.renderer_mut()) {
|
||||
match event {
|
||||
HudEvent::SendMessage(msg) => {
|
||||
// TODO: Handle result
|
||||
self.client.send_chat(msg);
|
||||
},
|
||||
HudEvent::Logout => return PlayStateResult::Pop,
|
||||
HudEvent::Quit => return PlayStateResult::Shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
// Render the session
|
||||
self.render(global_state.window.renderer_mut());
|
||||
|
@ -1,77 +0,0 @@
|
||||
// Standard
|
||||
use std::rc::Rc;
|
||||
|
||||
// Library
|
||||
use image::DynamicImage;
|
||||
use vek::*;
|
||||
|
||||
// Crate
|
||||
use crate::render::{
|
||||
Consts,
|
||||
UiLocals,
|
||||
Renderer,
|
||||
Texture,
|
||||
UiPipeline,
|
||||
};
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
super::{
|
||||
UiError,
|
||||
Cache,
|
||||
},
|
||||
Element,
|
||||
Bounds,
|
||||
SizeRequest,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Image {
|
||||
texture: Rc<Texture<UiPipeline>>,
|
||||
locals: Consts<UiLocals>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn new(renderer: &mut Renderer, image: &DynamicImage) -> Result<Self, UiError> {
|
||||
Ok(Self {
|
||||
texture: Rc::new(
|
||||
renderer.create_texture(image)
|
||||
.map_err(|err| UiError::RenderError(err))?
|
||||
),
|
||||
locals: renderer.create_consts(&[UiLocals::default()])
|
||||
.map_err(|err| UiError::RenderError(err))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Image {
|
||||
fn get_hsize_request(&self) -> SizeRequest { SizeRequest::indifferent() }
|
||||
fn get_vsize_request(&self) -> SizeRequest { SizeRequest::indifferent() }
|
||||
|
||||
fn maintain(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.update_consts(&mut self.locals, &[UiLocals::new(
|
||||
[bounds.x, bounds.y, bounds.w, bounds.h],
|
||||
)])
|
||||
.expect("Could not update UI image consts");
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.render_ui_element(
|
||||
cache.model(),
|
||||
&self.locals,
|
||||
&self.texture,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
pub mod image;
|
||||
|
||||
// Standard
|
||||
use std::rc::Rc;
|
||||
|
||||
// Library
|
||||
use vek::*;
|
||||
|
||||
// Crate
|
||||
use crate::render::{
|
||||
Renderer,
|
||||
Texture,
|
||||
Consts,
|
||||
UiLocals,
|
||||
UiPipeline,
|
||||
};
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
UiError,
|
||||
Cache,
|
||||
Span,
|
||||
SizeRequest,
|
||||
};
|
||||
|
||||
// Bounds
|
||||
|
||||
pub type Bounds<T> = Rect<T, T>;
|
||||
|
||||
pub trait BoundsExt {
|
||||
fn relative_to(self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
impl BoundsExt for Bounds<f32> {
|
||||
fn relative_to(self, other: Self) -> Self {
|
||||
Self::new(
|
||||
other.x + self.x * other.w,
|
||||
other.y + self.y * other.h,
|
||||
self.w * other.w,
|
||||
self.h * other.h,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BoundsSpan {
|
||||
fn in_resolution(self, resolution: Vec2<f32>) -> Bounds<f32>;
|
||||
}
|
||||
|
||||
impl BoundsSpan for Bounds<Span> {
|
||||
fn in_resolution(self, resolution: Vec2<f32>) -> Bounds<f32> {
|
||||
Bounds::new(
|
||||
self.x.to_rel(resolution.x).rel,
|
||||
self.y.to_rel(resolution.y).rel,
|
||||
self.w.to_rel(resolution.x).rel,
|
||||
self.h.to_rel(resolution.y).rel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Element
|
||||
|
||||
pub trait Element: 'static {
|
||||
//fn deep_clone(&self) -> Rc<dyn Element>;
|
||||
|
||||
fn get_hsize_request(&self) -> SizeRequest;
|
||||
fn get_vsize_request(&self) -> SizeRequest;
|
||||
|
||||
fn maintain(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
);
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
);
|
||||
}
|
||||
|
||||
// Surface
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Surface {
|
||||
Transparent,
|
||||
Color(Rgba<f32>),
|
||||
Texture(Rc<Texture<UiPipeline>>),
|
||||
Bevel,
|
||||
}
|
||||
|
||||
// Widget
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Widget<E: Element> {
|
||||
inner: Box<E>,
|
||||
background: Surface,
|
||||
margin_top: Span,
|
||||
margin_bottom: Span,
|
||||
margin_left: Span,
|
||||
margin_right: Span,
|
||||
locals: Consts<UiLocals>,
|
||||
}
|
||||
|
||||
impl<E: Element> Widget<E> {
|
||||
pub fn new(renderer: &mut Renderer, inner: E) -> Result<Box<Self>, UiError> {
|
||||
Ok(Box::new(Self {
|
||||
inner: Box::new(inner),
|
||||
background: Surface::Transparent,
|
||||
margin_top: Span::rel(0.2),
|
||||
margin_bottom: Span::rel(0.2),
|
||||
margin_left: Span::rel(0.2),
|
||||
margin_right: Span::rel(0.2),
|
||||
locals: renderer.create_consts(&[UiLocals::default()])
|
||||
.map_err(|err| UiError::RenderError(err))?,
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_inner_bounds(&self) -> Bounds<Span> {
|
||||
Bounds::new(
|
||||
self.margin_left,
|
||||
self.margin_top,
|
||||
Span::full() - self.margin_left - self.margin_right,
|
||||
Span::full() - self.margin_top - self.margin_bottom,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> Element for Widget<E> {
|
||||
fn get_hsize_request(&self) -> SizeRequest {
|
||||
self.inner.get_hsize_request() + self.margin_left + self.margin_right
|
||||
}
|
||||
|
||||
fn get_vsize_request(&self) -> SizeRequest {
|
||||
self.inner.get_vsize_request() + self.margin_top + self.margin_bottom
|
||||
}
|
||||
|
||||
fn maintain(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.update_consts(&mut self.locals, &[UiLocals::new(
|
||||
[bounds.x, bounds.y, bounds.w, bounds.h],
|
||||
)])
|
||||
.expect("Could not update UI image consts");
|
||||
|
||||
let inner_bounds = self
|
||||
.get_inner_bounds()
|
||||
.in_resolution(resolution)
|
||||
.relative_to(bounds);
|
||||
|
||||
self.inner.maintain(renderer, cache, inner_bounds, resolution);
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.render_ui_element(
|
||||
cache.model(),
|
||||
&self.locals,
|
||||
&cache.blank_texture(),
|
||||
);
|
||||
|
||||
let inner_bounds = self
|
||||
.get_inner_bounds()
|
||||
.in_resolution(resolution)
|
||||
.relative_to(bounds);
|
||||
|
||||
self.inner.render(renderer, cache, inner_bounds, resolution);
|
||||
}
|
||||
}
|
@ -1,85 +1,579 @@
|
||||
pub mod element;
|
||||
pub mod size_request;
|
||||
pub mod span;
|
||||
mod widgets;
|
||||
|
||||
// Reexports
|
||||
pub use self::{
|
||||
span::Span,
|
||||
size_request::SizeRequest,
|
||||
};
|
||||
pub use widgets::toggle_button::ToggleButton;
|
||||
|
||||
// Library
|
||||
use image::DynamicImage;
|
||||
|
||||
// Crate
|
||||
use conrod_core::{
|
||||
Ui as CrUi,
|
||||
UiBuilder,
|
||||
UiCell,
|
||||
text::{
|
||||
Font,
|
||||
GlyphCache,
|
||||
font::Id as FontId,
|
||||
},
|
||||
image::{Map, Id as ImgId},
|
||||
widget::{Id as WidgId, id::Generator},
|
||||
render::Primitive,
|
||||
event::Input,
|
||||
input::{touch::Touch, Widget, Motion, Button, MouseButton},
|
||||
};
|
||||
use vek::*;
|
||||
use crate::{
|
||||
Error,
|
||||
render::{
|
||||
RenderError,
|
||||
Renderer,
|
||||
Model,
|
||||
Mesh,
|
||||
Texture,
|
||||
UiPipeline,
|
||||
create_ui_quad_mesh,
|
||||
UiMode,
|
||||
create_ui_quad,
|
||||
create_ui_tri,
|
||||
},
|
||||
};
|
||||
|
||||
// Local
|
||||
use self::element::{
|
||||
Element,
|
||||
Bounds,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UiError {
|
||||
RenderError(RenderError),
|
||||
}
|
||||
|
||||
pub struct Cache {
|
||||
model: Model<UiPipeline>,
|
||||
blank_texture: Texture<UiPipeline>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
model: renderer.create_model(&create_ui_quad_mesh())?,
|
||||
blank_texture: renderer.create_texture(&DynamicImage::new_rgba8(1, 1))?,
|
||||
#[derive(Clone)]
|
||||
pub struct Event(Input);
|
||||
impl Event {
|
||||
pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option<Self> {
|
||||
conrod_winit::convert_event(event, window.window()).map(|input| {
|
||||
Self(input)
|
||||
})
|
||||
}
|
||||
pub fn is_keyboard_or_mouse(&self) -> bool {
|
||||
match self.0 {
|
||||
Input::Press(_) | Input::Release(_) | Input::Motion(_) | Input::Touch(_) | Input::Text(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_keyboard(&self) -> bool {
|
||||
match self.0 {
|
||||
Input::Press(Button::Keyboard(_)) | Input::Release(Button::Keyboard(_)) | Input::Text(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn new_resize(dims: Vec2<f64>) -> Self {
|
||||
Self(Input::Resize(dims.x, dims.y))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn model(&self) -> &Model<UiPipeline> { &self.model }
|
||||
pub struct Cache {
|
||||
blank_texture: Texture<UiPipeline>,
|
||||
glyph_cache: GlyphCache<'static>,
|
||||
glyph_cache_tex: Texture<UiPipeline>,
|
||||
}
|
||||
|
||||
// TODO: Should functions be returning UiError instead of Error?
|
||||
impl Cache {
|
||||
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
|
||||
let (w, h) = renderer.get_resolution().into_tuple();
|
||||
const SCALE_TOLERANCE: f32 = 0.1;
|
||||
const POSITION_TOLERANCE: f32 = 0.1;
|
||||
|
||||
Ok(Self {
|
||||
blank_texture: renderer.create_texture(&DynamicImage::new_rgba8(1, 1))?,
|
||||
glyph_cache: GlyphCache::builder()
|
||||
.dimensions(w as u32, h as u32)
|
||||
.scale_tolerance(SCALE_TOLERANCE)
|
||||
.position_tolerance(POSITION_TOLERANCE)
|
||||
.build(),
|
||||
glyph_cache_tex: renderer.create_dynamic_texture((w, h).into())?,
|
||||
})
|
||||
}
|
||||
pub fn blank_texture(&self) -> &Texture<UiPipeline> { &self.blank_texture }
|
||||
pub fn glyph_cache_tex(&self) -> &Texture<UiPipeline> { &self.glyph_cache_tex }
|
||||
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture<UiPipeline>) { (&mut self.glyph_cache, &self.glyph_cache_tex) }
|
||||
}
|
||||
|
||||
enum DrawKind {
|
||||
Image(ImgId),
|
||||
// Text and non-textured geometry
|
||||
Plain,
|
||||
}
|
||||
enum DrawCommand {
|
||||
Draw {
|
||||
kind: DrawKind,
|
||||
model: Model<UiPipeline>,
|
||||
},
|
||||
Scissor(Aabr<u16>),
|
||||
}
|
||||
impl DrawCommand {
|
||||
fn image(model: Model<UiPipeline>, img_id: ImgId) -> DrawCommand {
|
||||
DrawCommand::Draw {
|
||||
kind: DrawKind::Image(img_id),
|
||||
model,
|
||||
}
|
||||
}
|
||||
fn plain(model: Model<UiPipeline>) -> DrawCommand {
|
||||
DrawCommand::Draw {
|
||||
kind: DrawKind::Plain,
|
||||
model,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// How to scale the ui
|
||||
pub enum ScaleMode {
|
||||
// Scale against physical size
|
||||
Absolute(f64),
|
||||
// Use the dpi factor provided by the windowing system (i.e. use logical size)
|
||||
DpiFactor,
|
||||
// Scale based on the window's physical size, but maintain aspect ratio of widgets
|
||||
// Contains width and height of the "default" window size (ie where there should be no scaling)
|
||||
RelativeToWindow(Vec2<f64>),
|
||||
}
|
||||
|
||||
struct Scale {
|
||||
// Type of scaling to use
|
||||
mode: ScaleMode,
|
||||
// Current dpi factor
|
||||
dpi_factor: f64,
|
||||
// Current logical window size
|
||||
window_dims: Vec2<f64>,
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
fn new(window: &Window, mode: ScaleMode) -> Self {
|
||||
let window_dims = window.logical_size();
|
||||
let dpi_factor = window.renderer().get_resolution().x as f64 / window_dims.x;
|
||||
Scale {
|
||||
mode,
|
||||
dpi_factor,
|
||||
window_dims,
|
||||
}
|
||||
}
|
||||
// Change the scaling mode
|
||||
pub fn scaling_mode(&mut self, mode: ScaleMode) {
|
||||
self.mode = mode;
|
||||
}
|
||||
// Calculate factor to transform between logical coordinates and our scaled coordinates
|
||||
fn scale_factor_logical(&self) -> f64 {
|
||||
match self.mode {
|
||||
ScaleMode::Absolute(scale) => scale / self.dpi_factor,
|
||||
ScaleMode::DpiFactor => 1.0,
|
||||
ScaleMode::RelativeToWindow(dims) => (self.window_dims.x / dims.x).min(self.window_dims.y / dims.y),
|
||||
}
|
||||
}
|
||||
// Calculate factor to transform between physical coordinates and our scaled coordinates
|
||||
fn scale_factor_physical(&self) -> f64 {
|
||||
self.scale_factor_logical() * self.dpi_factor
|
||||
}
|
||||
// Updates internal window size (and/or dpi_factor)
|
||||
fn window_resized(&mut self, new_dims: Vec2<f64>, renderer: &Renderer) {
|
||||
self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x;
|
||||
self.window_dims = new_dims;
|
||||
}
|
||||
// Get scaled window size
|
||||
fn scaled_window_size(&self) -> Vec2<f64> {
|
||||
self.window_dims / self.scale_factor_logical()
|
||||
}
|
||||
// Transform point from logical to scaled coordinates
|
||||
fn scale_point(&self, point: Vec2<f64>) -> Vec2<f64> {
|
||||
point / self.scale_factor_logical()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
base: Box<dyn Element>,
|
||||
ui: CrUi,
|
||||
image_map: Map<Texture<UiPipeline>>,
|
||||
cache: Cache,
|
||||
// Draw commands for the next render
|
||||
draw_commands: Vec<DrawCommand>,
|
||||
// Stores new window size for updating scaling
|
||||
window_resized: Option<Vec2<f64>>,
|
||||
// Scaling of the ui
|
||||
scale: Scale,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn new<E: Element>(renderer: &mut Renderer, base: Box<E>) -> Result<Self, Error> {
|
||||
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 {
|
||||
base,
|
||||
cache: Cache::new(renderer)?,
|
||||
ui: UiBuilder::new(win_dims).build(),
|
||||
image_map: Map::new(),
|
||||
cache: Cache::new(window.renderer_mut())?,
|
||||
window_resized: None,
|
||||
draw_commands: vec![],
|
||||
scale,
|
||||
})
|
||||
}
|
||||
|
||||
// Set the scaling mode of the ui
|
||||
pub fn scaling_mode(&mut self, mode: ScaleMode) {
|
||||
self.scale.scaling_mode(mode);
|
||||
// Give conrod the new size
|
||||
let (w, h) = self.scale.scaled_window_size().into_tuple();
|
||||
self.ui.handle_event(Input::Resize(w, h));
|
||||
}
|
||||
|
||||
pub fn new_image(&mut self, renderer: &mut Renderer, image: &DynamicImage) -> Result<ImgId, Error> {
|
||||
Ok(self.image_map.insert(renderer.create_texture(image)?))
|
||||
}
|
||||
|
||||
pub fn new_font(&mut self, font: Font) -> FontId {
|
||||
self.ui.fonts.insert(font)
|
||||
}
|
||||
|
||||
pub fn id_generator(&mut self) -> Generator {
|
||||
self.ui.widget_id_generator()
|
||||
}
|
||||
|
||||
pub fn set_widgets(&mut self) -> UiCell {
|
||||
self.ui.set_widgets()
|
||||
}
|
||||
|
||||
// Workaround because conrod currently gives us no way to programmatically set which widget is capturing key input
|
||||
// Note the widget must be visible and not covered by other widgets at its center for this to work
|
||||
// Accepts option so widget can be "unfocused"
|
||||
pub fn focus_widget(&mut self, id: Option<WidgId>) {
|
||||
let (x, y) = match id {
|
||||
// get position of widget
|
||||
Some(id) => if let Some([x, y]) = self.ui.xy_of(id) {
|
||||
(x, y)
|
||||
} else {
|
||||
return;
|
||||
},
|
||||
// outside window (origin is center)
|
||||
None => (self.ui.win_h, self.ui.win_w)
|
||||
};
|
||||
// things to consider: does cursor need to be moved back?, should we check if the mouse if already pressed and then trigger a release?
|
||||
self.ui.handle_event(
|
||||
Input::Motion(Motion::MouseCursor { x, y })
|
||||
);
|
||||
self.ui.handle_event(
|
||||
Input::Press(Button::Mouse(MouseButton::Left))
|
||||
);
|
||||
self.ui.handle_event(
|
||||
Input::Release(Button::Mouse(MouseButton::Left))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
pub fn handle_event(&mut self, event: Event) {
|
||||
match event.0 {
|
||||
Input::Resize(w, h) => self.window_resized = Some(Vec2::new(w, h)),
|
||||
Input::Touch(touch) => self.ui.handle_event(
|
||||
Input::Touch(Touch {
|
||||
xy: self.scale.scale_point(touch.xy.into()).into_array(),
|
||||
..touch
|
||||
})
|
||||
),
|
||||
Input::Motion(motion) => self.ui.handle_event(
|
||||
Input::Motion( match motion {
|
||||
Motion::MouseCursor { x, y } => {
|
||||
let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
|
||||
Motion::MouseCursor { x, y }
|
||||
}
|
||||
Motion::MouseRelative { x, y } => {
|
||||
let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
|
||||
Motion::MouseRelative { x, y }
|
||||
}
|
||||
Motion::Scroll { x, y } => {
|
||||
let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
|
||||
Motion::Scroll { x, y }
|
||||
}
|
||||
_ => motion,
|
||||
})
|
||||
),
|
||||
_ => self.ui.handle_event(event.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn widget_input(&self, id: WidgId) -> Widget {
|
||||
self.ui.widget_input(id)
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer) {
|
||||
self.base.maintain(
|
||||
renderer,
|
||||
&self.cache,
|
||||
Bounds::new(0.0, 0.0, 1.0, 1.0),
|
||||
renderer.get_resolution().map(|e| e as f32),
|
||||
)
|
||||
let ref mut ui = self.ui;
|
||||
// Regenerate draw commands and associated models only if the ui changed
|
||||
if let Some(mut primitives) = ui.draw_if_changed() {
|
||||
self.draw_commands.clear();
|
||||
let mut mesh = Mesh::new();
|
||||
|
||||
let mut current_img = None;
|
||||
|
||||
let window_scizzor = default_scissor(renderer);
|
||||
let mut current_scizzor = window_scizzor;
|
||||
|
||||
// Switches to the `Plain` state and completes the previous `Command` if not already in the
|
||||
// `Plain` state.
|
||||
macro_rules! switch_to_plain_state {
|
||||
() => {
|
||||
if let Some(image_id) = current_img.take() {
|
||||
self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id));
|
||||
mesh.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let p_scale_factor = self.scale.scale_factor_physical();
|
||||
|
||||
while let Some(prim) = primitives.next() {
|
||||
let Primitive {kind, scizzor, id, rect} = prim;
|
||||
|
||||
// Check for a change in the scizzor
|
||||
let new_scizzor = {
|
||||
let (l, b, w, h) = scizzor.l_b_w_h();
|
||||
// Calculate minimum x and y coordinates while
|
||||
// - flipping y axis (from +up to +down)
|
||||
// - moving origin to top-left corner (from middle)
|
||||
let min_x = ui.win_w / 2.0 + l;
|
||||
let min_y = ui.win_h / 2.0 - b - h;
|
||||
Aabr {
|
||||
min: Vec2 {
|
||||
x: (min_x * p_scale_factor) as u16,
|
||||
y: (min_y * p_scale_factor) as u16,
|
||||
},
|
||||
max: Vec2 {
|
||||
x: ((min_x + w) * p_scale_factor) as u16,
|
||||
y: ((min_y + h) * p_scale_factor) as u16,
|
||||
}
|
||||
}
|
||||
.intersection(window_scizzor)
|
||||
};
|
||||
if new_scizzor != current_scizzor {
|
||||
// Finish the current command
|
||||
match current_img.take() {
|
||||
None =>
|
||||
self.draw_commands.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())),
|
||||
Some(image_id) =>
|
||||
self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id)),
|
||||
}
|
||||
mesh.clear();
|
||||
|
||||
// Update the scizzor and produce a command.
|
||||
current_scizzor = new_scizzor;
|
||||
self.draw_commands.push(DrawCommand::Scissor(new_scizzor));
|
||||
}
|
||||
|
||||
// Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0)
|
||||
let vx = |x: f64| (x / ui.win_w * 2.0) as f32;
|
||||
let vy = |y: f64| (y / ui.win_h * 2.0) as f32;
|
||||
let gl_aabr = |rect: conrod_core::Rect| {
|
||||
let (l, r, b, t) = rect.l_r_b_t();
|
||||
Aabr {
|
||||
min: Vec2::new(vx(l), vy(b)),
|
||||
max: Vec2::new(vx(r), vy(t)),
|
||||
}
|
||||
};
|
||||
|
||||
use conrod_core::render::PrimitiveKind;
|
||||
match kind {
|
||||
// TODO: use source_rect
|
||||
PrimitiveKind::Image { image_id, color, source_rect } => {
|
||||
|
||||
// Switch to the `Image` state for this image if we're not in it already.
|
||||
let new_image_id = image_id;
|
||||
match current_img {
|
||||
// If we're already in the drawing mode for this image, we're done.
|
||||
Some(image_id) if image_id == new_image_id => (),
|
||||
// If we were in the `Plain` drawing state, switch to Image drawing state.
|
||||
None => {
|
||||
self.draw_commands.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap()));
|
||||
mesh.clear();
|
||||
current_img = Some(new_image_id);
|
||||
}
|
||||
// If we were drawing a different image, switch state to draw *this* image.
|
||||
Some(image_id) => {
|
||||
self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id));
|
||||
mesh.clear();
|
||||
current_img = Some(new_image_id);
|
||||
}
|
||||
}
|
||||
|
||||
let color = color.unwrap_or(conrod_core::color::WHITE).to_fsa();
|
||||
|
||||
// Transform the source rectangle into uv coordinates
|
||||
let (image_w, image_h) = self.image_map
|
||||
.get(&image_id)
|
||||
.expect("Image does not exist in image map")
|
||||
.get_dimensions()
|
||||
.map(|e| e as f64)
|
||||
.into_tuple();
|
||||
let (uv_l, uv_r, uv_t, uv_b) = match source_rect {
|
||||
Some(src_rect) => {
|
||||
let (l, r, b, t) = src_rect.l_r_b_t();
|
||||
((l / image_w) as f32,
|
||||
(r / image_w) as f32,
|
||||
(b / image_h) as f32,
|
||||
(t / image_h) as f32)
|
||||
}
|
||||
None => (0.0, 1.0, 0.0, 1.0),
|
||||
};
|
||||
let uv = Aabr {
|
||||
min: Vec2::new(uv_l, uv_b),
|
||||
max: Vec2::new(uv_r, uv_t),
|
||||
};
|
||||
mesh.push_quad(create_ui_quad(
|
||||
gl_aabr(rect),
|
||||
uv,
|
||||
color,
|
||||
UiMode::Image,
|
||||
));
|
||||
|
||||
}
|
||||
PrimitiveKind::Text { color, text, font_id } => {
|
||||
switch_to_plain_state!();
|
||||
// Get screen width and height
|
||||
let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as f32).into_tuple();
|
||||
// Calculate dpi factor
|
||||
let dpi_factor = screen_w / ui.win_w as f32;
|
||||
|
||||
let positioned_glyphs = text.positioned_glyphs(dpi_factor);
|
||||
let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex();
|
||||
// Queue the glyphs to be cached
|
||||
for glyph in positioned_glyphs {
|
||||
glyph_cache.queue_glyph(font_id.index(), glyph.clone());
|
||||
}
|
||||
|
||||
glyph_cache.cache_queued(|rect, data| {
|
||||
let offset = [rect.min.x as u16, rect.min.y as u16];
|
||||
let size = [rect.width() as u16, rect.height() as u16];
|
||||
|
||||
let new_data = data.iter().map(|x| [255, 255, 255, *x]).collect::<Vec<[u8; 4]>>();
|
||||
|
||||
renderer.update_texture(cache_tex, offset, size, &new_data);
|
||||
}).unwrap();
|
||||
|
||||
// TODO: consider gamma....
|
||||
let color = color.to_fsa();
|
||||
|
||||
for g in positioned_glyphs {
|
||||
if let Ok(Some((uv_rect, screen_rect))) = glyph_cache.rect_for(font_id.index(), g) {
|
||||
let uv = Aabr {
|
||||
min: Vec2::new(uv_rect.min.x, uv_rect.max.y),
|
||||
max: Vec2::new(uv_rect.max.x, uv_rect.min.y),
|
||||
};
|
||||
let rect = Aabr {
|
||||
min: Vec2::new(
|
||||
(screen_rect.min.x as f32 / screen_w - 0.5) * 2.0,
|
||||
(screen_rect.max.y as f32 / screen_h - 0.5) * -2.0,
|
||||
),
|
||||
max: Vec2::new(
|
||||
(screen_rect.max.x as f32 / screen_w - 0.5) * 2.0,
|
||||
(screen_rect.min.y as f32 / screen_h - 0.5) * -2.0,
|
||||
),
|
||||
};
|
||||
mesh.push_quad(create_ui_quad(
|
||||
rect,
|
||||
uv,
|
||||
color,
|
||||
UiMode::Text,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
PrimitiveKind::Rectangle { color } => {
|
||||
// TODO: consider gamma/linear conversion....
|
||||
let color = color.to_fsa();
|
||||
// Don't draw a transparent rectangle
|
||||
if color[3] == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch_to_plain_state!();
|
||||
|
||||
mesh.push_quad(create_ui_quad(
|
||||
gl_aabr(rect),
|
||||
Aabr {
|
||||
min: Vec2::new(0.0, 0.0),
|
||||
max: Vec2::new(0.0, 0.0),
|
||||
},
|
||||
color,
|
||||
UiMode::Geometry,
|
||||
));
|
||||
}
|
||||
PrimitiveKind::TrianglesSingleColor { color, triangles } => {
|
||||
// Don't draw transparent triangle or switch state if there are actually no triangles
|
||||
let color: [f32; 4] = color.into();
|
||||
if triangles.is_empty() || color[3] == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch_to_plain_state!();
|
||||
|
||||
for tri in triangles {
|
||||
let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1]));
|
||||
let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1]));
|
||||
let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1]));
|
||||
// If triangle is clockwise reverse it
|
||||
let (v1, v2): (Vec3<f32>, Vec3<f32>) = ((p2 - p1).into(), (p3 - p1).into());
|
||||
let triangle = if v1.cross(v2).z > 0.0 {[
|
||||
p1.into_array(),
|
||||
p2.into_array(),
|
||||
p3.into_array(),
|
||||
]} else {[
|
||||
p2.into_array(),
|
||||
p1.into_array(),
|
||||
p3.into_array(),
|
||||
]};
|
||||
mesh.push_tri(create_ui_tri(
|
||||
triangle,
|
||||
[[0.0; 2]; 3],
|
||||
color,
|
||||
UiMode::Geometry,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
_ => {}
|
||||
// TODO: Add these
|
||||
//PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);}
|
||||
//PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);}
|
||||
}
|
||||
}
|
||||
// Enter the final command
|
||||
match current_img {
|
||||
None =>
|
||||
self.draw_commands.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())),
|
||||
Some(image_id) =>
|
||||
self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id)),
|
||||
}
|
||||
|
||||
// Handle window resizing
|
||||
if let Some(new_dims) = self.window_resized.take() {
|
||||
self.scale.window_resized(new_dims, renderer);
|
||||
let (w, h) = self.scale.scaled_window_size().into_tuple();
|
||||
self.ui.handle_event(Input::Resize(w, h));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer) {
|
||||
self.base.render(
|
||||
renderer,
|
||||
&self.cache,
|
||||
Bounds::new(0.0, 0.0, 1.0, 1.0),
|
||||
renderer.get_resolution().map(|e| e as f32),
|
||||
);
|
||||
let mut scissor = default_scissor(renderer);
|
||||
for draw_command in self.draw_commands.iter() {
|
||||
match draw_command {
|
||||
DrawCommand::Scissor(scizzor) => {
|
||||
scissor = *scizzor;
|
||||
}
|
||||
DrawCommand::Draw { kind, model } => {
|
||||
let tex = match kind {
|
||||
DrawKind::Image(image_id) => {
|
||||
self.image_map.get(&image_id).expect("Image does not exist in image map")
|
||||
}
|
||||
DrawKind::Plain => {
|
||||
self.cache.glyph_cache_tex()
|
||||
}
|
||||
};
|
||||
renderer.render_ui_element(&model, &tex, scissor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_scissor(renderer: &mut Renderer) -> Aabr<u16> {
|
||||
let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as u16).into_tuple();
|
||||
Aabr {
|
||||
min: Vec2 { x: 0, y: 0 },
|
||||
max: Vec2 { x: screen_w, y: screen_h }
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
// Standard
|
||||
use std::ops::Add;
|
||||
|
||||
// Local
|
||||
use super::Span;
|
||||
|
||||
pub struct SizeRequest {
|
||||
min: Span,
|
||||
max: Span,
|
||||
}
|
||||
|
||||
impl SizeRequest {
|
||||
pub fn indifferent() -> Self {
|
||||
Self {
|
||||
min: Span::rel(0.0),
|
||||
max: Span::rel(std::f32::INFINITY),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Span> for SizeRequest {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, span: Span) -> Self {
|
||||
Self {
|
||||
min: self.min + span,
|
||||
max: self.max + span,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
// Standard
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Span {
|
||||
pub rel: f32,
|
||||
pub abs: f32,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn rel(rel: f32) -> Self { Self { rel, abs: 0.0 } }
|
||||
pub fn abs(abs: f32) -> Self { Self { rel: 0.0, abs } }
|
||||
|
||||
pub fn full() -> Self { Self { rel: 1.0, abs: 0.0 } }
|
||||
pub fn half() -> Self { Self { rel: 0.5, abs: 0.0 } }
|
||||
pub fn none() -> Self { Self { rel: 0.0, abs: 0.0 } }
|
||||
|
||||
pub fn to_abs(self, res: f32) -> Self {
|
||||
Self { rel: 0.0, abs: self.rel * res + self.abs }
|
||||
}
|
||||
|
||||
pub fn to_rel(self, res: f32) -> Self {
|
||||
Self { rel: self.rel + self.abs / res, abs: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Span {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel + other.rel,
|
||||
abs: self.abs + other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Span {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel - other.rel,
|
||||
abs: self.abs - other.abs,
|
||||
}
|
||||
}
|
||||
}
|
1
voxygen/src/ui/widgets/mod.rs
Normal file
1
voxygen/src/ui/widgets/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod toggle_button;
|
118
voxygen/src/ui/widgets/toggle_button.rs
Normal file
118
voxygen/src/ui/widgets/toggle_button.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use conrod_core::{
|
||||
widget::{self, button},
|
||||
image,
|
||||
WidgetCommon,
|
||||
Widget,
|
||||
Sizeable,
|
||||
Color,
|
||||
Rect,
|
||||
Positionable,
|
||||
widget_ids,
|
||||
};
|
||||
|
||||
#[derive(Clone, WidgetCommon)]
|
||||
pub struct ToggleButton {
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
value: bool,
|
||||
f_image: button::Image,
|
||||
t_image: button::Image,
|
||||
}
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
button,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
ids: Ids,
|
||||
}
|
||||
|
||||
impl ToggleButton {
|
||||
pub fn new(value: bool, f_image_id: image::Id, t_image_id: image::Id) -> Self {
|
||||
ToggleButton {
|
||||
common: widget::CommonBuilder::default(),
|
||||
value,
|
||||
f_image: button::Image {
|
||||
image_id: f_image_id,
|
||||
hover_image_id: None,
|
||||
press_image_id: None,
|
||||
src_rect: None,
|
||||
color: button::ImageColor::None,
|
||||
},
|
||||
t_image: button::Image {
|
||||
image_id: t_image_id,
|
||||
hover_image_id: None,
|
||||
press_image_id: None,
|
||||
src_rect: None,
|
||||
color: button::ImageColor::None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_rectangle(mut self, rect: Rect) -> Self {
|
||||
self.f_image.src_rect = Some(rect);
|
||||
self.t_image.src_rect = Some(rect);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn image_colors(mut self, f_color: Color, t_color: Color) -> Self {
|
||||
self.f_image.color = button::ImageColor::Normal(f_color);
|
||||
self.t_image.color = button::ImageColor::Normal(t_color);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn image_color_with_feedback(mut self, f_color: Color, t_color: Color) -> Self {
|
||||
self.f_image.color = button::ImageColor::WithFeedback(f_color);
|
||||
self.t_image.color = button::ImageColor::WithFeedback(t_color);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hover_images(mut self, f_id: image::Id, t_id: image::Id) -> Self {
|
||||
self.f_image.hover_image_id = Some(f_id);
|
||||
self.t_image.hover_image_id = Some(t_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn press_images(mut self, f_id: image::Id, t_id: image::Id) -> Self {
|
||||
self.f_image.press_image_id = Some(f_id);
|
||||
self.t_image.press_image_id = Some(t_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ToggleButton {
|
||||
type State = State;
|
||||
type Style = ();
|
||||
type Event = bool;
|
||||
|
||||
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{ id, state, ui, rect, .. } = args;
|
||||
let ToggleButton { mut value, f_image, t_image, .. } = self;
|
||||
// Check if button was clicked
|
||||
// (can't use .set().was_clicked() because we are changing the image and this is after setting the widget which causes flickering as it takes a frame to change after the mouse button is lifted)
|
||||
if ui.widget_input(state.ids.button).clicks().left().count() % 2 == 1 {
|
||||
value = !value;
|
||||
}
|
||||
let image = if value { t_image } else { f_image };
|
||||
let (x, y, w, h) = rect.x_y_w_h();
|
||||
// Button
|
||||
let mut button = button::Button::image(image.image_id)
|
||||
.x_y(x, y)
|
||||
.w_h(w, h)
|
||||
.parent(id);
|
||||
button.show = image;
|
||||
button.set(state.ids.button, ui);
|
||||
|
||||
value
|
||||
}
|
||||
}
|
@ -1,10 +1,5 @@
|
||||
// Library
|
||||
use glutin;
|
||||
use gfx_window_glutin;
|
||||
use vek::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Crate
|
||||
use crate::{
|
||||
Error,
|
||||
render::{
|
||||
@ -12,6 +7,7 @@ use crate::{
|
||||
TgtColorFmt,
|
||||
TgtDepthFmt,
|
||||
},
|
||||
ui,
|
||||
};
|
||||
|
||||
pub struct Window {
|
||||
@ -19,6 +15,7 @@ pub struct Window {
|
||||
renderer: Renderer,
|
||||
window: glutin::GlWindow,
|
||||
cursor_grabbed: bool,
|
||||
needs_refresh_resize: bool,
|
||||
key_map: HashMap<glutin::VirtualKeyCode, Key>,
|
||||
}
|
||||
|
||||
@ -28,13 +25,13 @@ impl Window {
|
||||
let events_loop = glutin::EventsLoop::new();
|
||||
|
||||
let win_builder = glutin::WindowBuilder::new()
|
||||
.with_title("Veloren (Voxygen)")
|
||||
.with_dimensions(glutin::dpi::LogicalSize::new(800.0, 500.0))
|
||||
.with_maximized(false);
|
||||
.with_title("Veloren")
|
||||
.with_dimensions(glutin::dpi::LogicalSize::new(1366.0, 768.0))
|
||||
.with_maximized(true);
|
||||
|
||||
let ctx_builder = glutin::ContextBuilder::new()
|
||||
.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2)))
|
||||
.with_vsync(true);
|
||||
.with_vsync(false);
|
||||
|
||||
let (
|
||||
window,
|
||||
@ -49,7 +46,9 @@ impl Window {
|
||||
).map_err(|err| Error::BackendError(Box::new(err)))?;
|
||||
|
||||
let mut key_map = HashMap::new();
|
||||
key_map.insert(glutin::VirtualKeyCode::Escape, Key::ToggleCursor);
|
||||
key_map.insert(glutin::VirtualKeyCode::Tab, Key::ToggleCursor);
|
||||
key_map.insert(glutin::VirtualKeyCode::Escape, Key::Escape);
|
||||
key_map.insert(glutin::VirtualKeyCode::Return, Key::Enter);
|
||||
key_map.insert(glutin::VirtualKeyCode::W, Key::MoveForward);
|
||||
key_map.insert(glutin::VirtualKeyCode::A, Key::MoveLeft);
|
||||
key_map.insert(glutin::VirtualKeyCode::S, Key::MoveBack);
|
||||
@ -65,6 +64,7 @@ impl Window {
|
||||
)?,
|
||||
window,
|
||||
cursor_grabbed: false,
|
||||
needs_refresh_resize: false,
|
||||
key_map,
|
||||
});
|
||||
tmp
|
||||
@ -74,6 +74,13 @@ impl Window {
|
||||
pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
|
||||
|
||||
pub fn fetch_events(&mut self) -> Vec<Event> {
|
||||
let mut events = vec![];
|
||||
// Refresh ui size (used when changing playstates)
|
||||
if self.needs_refresh_resize {
|
||||
events.push(Event::Ui(ui::Event::new_resize(self.logical_size())));
|
||||
self.needs_refresh_resize = false;
|
||||
}
|
||||
|
||||
// Copy data that is needed by the events closure to avoid lifetime errors
|
||||
// TODO: Remove this if/when the compiler permits it
|
||||
let cursor_grabbed = self.cursor_grabbed;
|
||||
@ -81,42 +88,49 @@ impl Window {
|
||||
let window = &mut self.window;
|
||||
let key_map = &self.key_map;
|
||||
|
||||
let mut events = vec![];
|
||||
self.events_loop.poll_events(|event| match event {
|
||||
glutin::Event::WindowEvent { event, .. } => match event {
|
||||
glutin::WindowEvent::CloseRequested => events.push(Event::Close),
|
||||
glutin::WindowEvent::Resized(glutin::dpi::LogicalSize { width, height }) => {
|
||||
let (mut color_view, mut depth_view) = renderer.target_views_mut();
|
||||
gfx_window_glutin::update_views(
|
||||
&window,
|
||||
&mut color_view,
|
||||
&mut depth_view,
|
||||
);
|
||||
events.push(Event::Resize(Vec2::new(width as u32, height as u32)));
|
||||
},
|
||||
glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
|
||||
glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode {
|
||||
Some(keycode) => match key_map.get(&keycode) {
|
||||
Some(&key) => events.push(match input.state {
|
||||
glutin::ElementState::Pressed => Event::KeyDown(key),
|
||||
_ => Event::KeyUp(key),
|
||||
}),
|
||||
self.events_loop.poll_events(|event| {
|
||||
// Get events for ui
|
||||
if let Some(event) = ui::Event::try_from(event.clone(), &window) {
|
||||
events.push(Event::Ui(event));
|
||||
}
|
||||
|
||||
match event {
|
||||
glutin::Event::WindowEvent { event, .. } => match event {
|
||||
glutin::WindowEvent::CloseRequested => events.push(Event::Close),
|
||||
glutin::WindowEvent::Resized(glutin::dpi::LogicalSize { width, height }) => {
|
||||
let (mut color_view, mut depth_view) = renderer.target_views_mut();
|
||||
gfx_window_glutin::update_views(
|
||||
&window,
|
||||
&mut color_view,
|
||||
&mut depth_view,
|
||||
);
|
||||
events.push(Event::Resize(Vec2::new(width as u32, height as u32)));
|
||||
},
|
||||
glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
|
||||
|
||||
glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode {
|
||||
Some(keycode) => match key_map.get(&keycode) {
|
||||
Some(&key) => events.push(match input.state {
|
||||
glutin::ElementState::Pressed => Event::KeyDown(key),
|
||||
_ => Event::KeyUp(key),
|
||||
}),
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
glutin::Event::DeviceEvent { event, .. } => match event {
|
||||
glutin::DeviceEvent::MouseMotion { delta: (dx, dy), .. } if cursor_grabbed =>
|
||||
glutin::Event::DeviceEvent { event, .. } => match event {
|
||||
glutin::DeviceEvent::MouseMotion { delta: (dx, dy), .. } if cursor_grabbed =>
|
||||
events.push(Event::CursorPan(Vec2::new(dx as f32, dy as f32))),
|
||||
glutin::DeviceEvent::MouseWheel {
|
||||
delta: glutin::MouseScrollDelta::LineDelta(_x, y),
|
||||
..
|
||||
} if cursor_grabbed => events.push(Event::Zoom(y as f32)),
|
||||
glutin::DeviceEvent::MouseWheel {
|
||||
delta: glutin::MouseScrollDelta::LineDelta(_x, y),
|
||||
..
|
||||
} if cursor_grabbed => events.push(Event::Zoom(y as f32)),
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
events
|
||||
}
|
||||
@ -136,6 +150,15 @@ impl Window {
|
||||
self.window.grab_cursor(grab)
|
||||
.expect("Failed to grab/ungrab cursor");
|
||||
}
|
||||
|
||||
pub fn needs_refresh_resize(&mut self) {
|
||||
self.needs_refresh_resize = true;
|
||||
}
|
||||
|
||||
pub fn logical_size(&self) -> Vec2<f64> {
|
||||
let (w, h) = self.window.get_inner_size().unwrap_or(glutin::dpi::LogicalSize::new(0.0, 0.0)).into();
|
||||
Vec2::new(w, h)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a key that the game recognises after keyboard mapping
|
||||
@ -146,9 +169,12 @@ pub enum Key {
|
||||
MoveBack,
|
||||
MoveLeft,
|
||||
MoveRight,
|
||||
Enter,
|
||||
Escape,
|
||||
}
|
||||
|
||||
/// Represents an incoming event from the window
|
||||
#[derive(Clone)]
|
||||
pub enum Event {
|
||||
/// The window has been requested to close.
|
||||
Close,
|
||||
@ -164,4 +190,6 @@ pub enum Event {
|
||||
KeyDown(Key),
|
||||
/// A key that the game recognises has been released down
|
||||
KeyUp(Key),
|
||||
/// Event that the ui uses
|
||||
Ui(ui::Event),
|
||||
}
|
||||
|
1
voxygen/test_assets/.gitattributes
vendored
1
voxygen/test_assets/.gitattributes
vendored
@ -1 +0,0 @@
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
voxygen/test_assets/test.png
(Stored with Git LFS)
BIN
voxygen/test_assets/test.png
(Stored with Git LFS)
Binary file not shown.
BIN
voxygen/test_assets/wall.png
(Stored with Git LFS)
BIN
voxygen/test_assets/wall.png
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue
Block a user