Merge branch 'ingame_ui_attempt' into 'master'

Add Health Bars and Name Tags

See merge request veloren/veloren!155

Former-commit-id: 8ebb8dd060cc66349829b9e4b335b1f42ae949e5
This commit is contained in:
Joshua Barretto 2019-05-24 12:53:42 +00:00
commit 959945e97f
25 changed files with 596 additions and 109 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,9 +1,16 @@
#version 330 core
#include <globals.glsl>
in vec2 f_uv;
in vec4 f_color;
flat in uint f_mode;
layout (std140)
uniform u_locals {
vec4 w_pos;
};
uniform sampler2D u_tex;
out vec4 tgt_color;

View File

@ -1,10 +1,17 @@
#version 330 core
#include <globals.glsl>
in vec2 v_pos;
in vec2 v_uv;
in vec4 v_color;
in uint v_mode;
layout (std140)
uniform u_locals {
vec4 w_pos;
};
uniform sampler2D u_tex;
out vec2 f_uv;
@ -14,6 +21,13 @@ out vec4 f_color;
void main() {
f_uv = v_uv;
f_color = v_color;
gl_Position = vec4(v_pos, 0.0, 1.0);
if (w_pos.w == 1.0) {
// In-game element
gl_Position = proj_mat * (view_mat * w_pos + vec4(v_pos, 0.0, 0.0));
} else {
// Interface element
gl_Position = vec4(v_pos, 0.0, 1.0);
}
f_mode = v_mode;
}

View File

@ -23,18 +23,23 @@ use skillbar::Skillbar;
use small_window::{SmallWindow, SmallWindowType};
use crate::{
render::Renderer,
render::{Consts, Globals, Renderer},
scene::camera::Camera,
settings::{ControlSettings, Settings},
ui::{ScaleMode, Ui},
ui::{Ingame, Ingameable, ScaleMode, Ui},
window::{Event as WinEvent, Key, Window},
GlobalState,
};
use client::Client;
use common::comp;
use conrod_core::{
color, graph,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
};
use specs::Join;
use std::collections::VecDeque;
use vek::*;
const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0);
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
@ -43,6 +48,12 @@ const MANA_COLOR: Color = Color::Rgba(0.42, 0.41, 0.66, 1.0);
widget_ids! {
struct Ids {
// Character Names
name_tags[],
// Health Bars
health_bars[],
health_bar_backs[],
// Test
bag_space_add,
// Debug
@ -124,6 +135,7 @@ pub struct Show {
map: bool,
inventory_test_button: bool,
mini_map: bool,
ingame: bool,
want_grab: bool,
}
@ -247,13 +259,19 @@ impl Hud {
inventory_test_button: false,
mini_map: false,
want_grab: true,
ingame: true,
},
to_focus: None,
force_ungrab: false,
}
}
fn update_layout(&mut self, global_state: &GlobalState, debug_info: DebugInfo) -> Vec<Event> {
fn update_layout(
&mut self,
client: &Client,
global_state: &GlobalState,
debug_info: DebugInfo,
) -> Vec<Event> {
let mut events = Vec::new();
let ref mut ui_widgets = self.ui.set_widgets();
let version = env!("CARGO_PKG_VERSION");
@ -263,6 +281,76 @@ impl Hud {
return events;
}
// Nametags and healthbars
if self.show.ingame {
let ecs = client.state().ecs();
let actor = ecs.read_storage::<comp::Actor>();
let pos = ecs.read_storage::<comp::phys::Pos>();
let stats = ecs.read_storage::<comp::Stats>();
let entities = ecs.entities();
let player = client.entity();
let mut name_id_walker = self.ids.name_tags.walk();
let mut health_id_walker = self.ids.health_bars.walk();
let mut health_back_id_walker = self.ids.health_bar_backs.walk();
for (pos, name) in
(&entities, &pos, &actor)
.join()
.filter_map(|(entity, pos, actor)| match actor {
comp::Actor::Character { name, .. } if entity != player => {
Some((pos.0, name))
}
_ => None,
})
{
let id = name_id_walker.next(
&mut self.ids.name_tags,
&mut ui_widgets.widget_id_generator(),
);
Text::new(&name)
.font_size(20)
.color(Color::Rgba(1.0, 1.0, 1.0, 1.0))
.x_y(0.0, 0.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 3.0))
.resolution(100.0)
.set(id, ui_widgets);
}
for (pos, hp) in (&entities, &pos, &stats)
.join()
.filter_map(|(entity, pos, stats)| {
if entity != player {
Some((pos.0, stats.hp))
} else {
None
}
})
{
let back_id = health_back_id_walker.next(
&mut self.ids.health_bar_backs,
&mut ui_widgets.widget_id_generator(),
);
let bar_id = health_id_walker.next(
&mut self.ids.health_bars,
&mut ui_widgets.widget_id_generator(),
);
// Healh Bar
Rectangle::fill_with([120.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5))
.x_y(0.0, -25.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 3.0))
.resolution(100.0)
.set(back_id, ui_widgets);
// Filling
Rectangle::fill_with(
[120.0 * (hp.current as f64 / hp.maximum as f64), 8.0],
HP_COLOR,
)
.x_y(0.0, -25.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 3.0))
.resolution(100.0)
.set(bar_id, ui_widgets);
}
}
// Display debug window.
if self.show.debug {
// Alpha Version
@ -362,7 +450,15 @@ impl Hud {
}
// Skillbar
Skillbar::new(&self.imgs, &self.fonts).set(self.ids.skillbar, ui_widgets);
// Get player stats
let stats = client
.state()
.ecs()
.read_storage::<comp::Stats>()
.get(client.entity())
.map(|&s| s)
.unwrap_or_default();
Skillbar::new(&self.imgs, &self.fonts, stats).set(self.ids.skillbar, ui_widgets);
// Chat box
match Chat::new(&mut self.new_messages, &self.imgs, &self.fonts)
@ -474,7 +570,9 @@ impl Hud {
self.ui
.widget_graph()
.widget(id)
.and_then(graph::Container::unique_widget_state::<widget::TextEdit>)
.filter(|c| {
c.type_id == std::any::TypeId::of::<<widget::TextEdit as Widget>::State>()
})
.is_some()
} else {
false
@ -559,6 +657,10 @@ impl Hud {
self.show.debug = !self.show.debug;
true
}
Key::ToggleIngameUi => {
self.show.ingame = !self.show.ingame;
true
}
_ => false,
},
WinEvent::KeyDown(key) | WinEvent::KeyUp(key) => match key {
@ -580,19 +682,26 @@ impl Hud {
pub fn maintain(
&mut self,
client: &Client,
global_state: &mut GlobalState,
debug_info: DebugInfo,
camera: &Camera,
) -> Vec<Event> {
if let Some(maybe_id) = self.to_focus.take() {
self.ui.focus_widget(maybe_id);
}
let events = self.update_layout(&global_state, debug_info);
self.ui.maintain(&mut global_state.window.renderer_mut());
let events = self.update_layout(client, global_state, debug_info);
let (view_mat, _, _) = camera.compute_dependents(client);
let fov = camera.get_fov();
self.ui.maintain(
&mut global_state.window.renderer_mut(),
Some((view_mat, fov)),
);
events
}
pub fn render(&self, renderer: &mut Renderer) {
self.ui.render(renderer);
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
self.ui.render(renderer, Some(globals));
}
}

View File

@ -1,4 +1,5 @@
use super::{img_ids::Imgs, Fonts, HP_COLOR, MANA_COLOR, TEXT_COLOR, XP_COLOR};
use common::comp::Stats;
use conrod_core::{
widget::{self, Image, Rectangle, Text},
widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
@ -30,15 +31,18 @@ pub struct Skillbar<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
stats: Stats,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
impl<'a> Skillbar<'a> {
pub fn new(imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
pub fn new(imgs: &'a Imgs, fonts: &'a Fonts, stats: Stats) -> Self {
Self {
imgs,
fonts,
stats,
common: widget::CommonBuilder::default(),
}
}
@ -68,9 +72,13 @@ impl<'a> Widget for Skillbar<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args;
// TODO: Read from parameter/character struct
let xp_percentage = 0.4;
let hp_percentage = 1.0;
// TODO: remove this
let level = (self.stats.xp as f64).log(4.0).trunc() as u32 + 1;
let start_level_xp = ((level - 1) as f64).powi(4);
let next_level_xp = (level as f64).powi(4) - start_level_xp;
// TODO: We need a max xp value
let xp_percentage = (self.stats.xp as f64 - start_level_xp) / next_level_xp;
let hp_percentage = self.stats.hp.current as f64 / self.stats.hp.maximum as f64;
let mana_percentage = 1.0;
// TODO: Only show while aiming with a bow or when casting a spell.
@ -82,7 +90,7 @@ impl<'a> Widget for Skillbar<'a> {
// Experience-Bar
Image::new(self.imgs.xp_bar)
.w_h(2688.0 / 6.0, 116.0 / 6.0)
.w_h(672.0 / 1.5, 29.0 / 1.5)
.mid_bottom_of(ui.window)
.set(state.ids.xp_bar, ui);
@ -92,37 +100,37 @@ impl<'a> Widget for Skillbar<'a> {
// Left Grid
Image::new(self.imgs.sb_grid)
.w_h(2240.0 / 12.0, 448.0 / 12.0)
.w_h(280.0 / 1.5, 56.0 / 1.5)
.up_from(state.ids.xp_bar, 0.0)
.align_left_of(state.ids.xp_bar)
.set(state.ids.sb_grid_l, ui);
Image::new(self.imgs.sb_grid_bg)
.w_h(2240.0 / 12.0, 448.0 / 12.0)
.w_h(280.0 / 1.5, 56.0 / 1.5)
.middle_of(state.ids.sb_grid_l)
.set(state.ids.sb_grid_bg_l, ui);
// Right Grid
Image::new(self.imgs.sb_grid)
.w_h(2240.0 / 12.0, 448.0 / 12.0)
.w_h(280.0 / 1.5, 56.0 / 1.5)
.up_from(state.ids.xp_bar, 0.0)
.align_right_of(state.ids.xp_bar)
.set(state.ids.sb_grid_r, ui);
Image::new(self.imgs.sb_grid_bg)
.w_h(2240.0 / 12.0, 448.0 / 12.0)
.w_h(280.0 / 1.5, 56.0 / 1.5)
.middle_of(state.ids.sb_grid_r)
.set(state.ids.sb_grid_bg_r, ui);
// Right and Left Click
Image::new(self.imgs.l_click)
.w_h(224.0 / 6.0, 320.0 / 6.0)
.w_h(56.0 / 1.5, 80.0 / 1.5)
.right_from(state.ids.sb_grid_bg_l, 0.0)
.align_bottom_of(state.ids.sb_grid_bg_l)
.set(state.ids.l_click, ui);
Image::new(self.imgs.r_click)
.w_h(224.0 / 6.0, 320.0 / 6.0)
.w_h(56.0 / 1.5, 80.0 / 1.5)
.left_from(state.ids.sb_grid_bg_r, 0.0)
.align_bottom_of(state.ids.sb_grid_bg_r)
.set(state.ids.r_click, ui);
@ -135,7 +143,7 @@ impl<'a> Widget for Skillbar<'a> {
.set(state.ids.health_bar, ui);
// Filling
Rectangle::fill_with([182.0 * (hp_percentage), 6.0], HP_COLOR) // "W=182.0 * [Health. %]"
Rectangle::fill_with([182.0 * hp_percentage, 6.0], HP_COLOR) // "W=182.0 * [Health. %]"
.top_right_with_margins_on(state.ids.health_bar, 5.0, 0.0)
.set(state.ids.health_bar_color, ui);
@ -147,7 +155,7 @@ impl<'a> Widget for Skillbar<'a> {
.set(state.ids.mana_bar, ui);
// Filling
Rectangle::fill_with([182.0 * (mana_percentage), 6.0], MANA_COLOR) // "W=182.0 * [Mana. %]"
Rectangle::fill_with([182.0 * mana_percentage, 6.0], MANA_COLOR) // "W=182.0 * [Mana. %]"
.top_left_with_margins_on(state.ids.mana_bar, 5.0, 0.0)
.set(state.ids.mana_bar_color, ui);
@ -159,15 +167,16 @@ impl<'a> Widget for Skillbar<'a> {
// Level Display
// TODO: don't construct a new string here
// TODO: Insert actual Level here.
Text::new("1")
Text::new(&level.to_string())
.left_from(state.ids.xp_bar, -15.0)
.font_size(10)
.color(TEXT_COLOR)
.set(state.ids.level_text, ui);
// TODO: Insert next Level here.
Text::new("2")
Text::new(&(level + 1).to_string())
.right_from(state.ids.xp_bar, -15.0)
.font_size(10)
.color(TEXT_COLOR)

View File

@ -106,7 +106,7 @@ impl PlayState for CharSelectionState {
// Draw the UI to the screen.
self.char_selection_ui
.render(global_state.window.renderer_mut());
.render(global_state.window.renderer_mut(), self.scene.globals());
// Tick the client (currently only to keep the connection alive).
self.client

View File

@ -72,6 +72,10 @@ impl Scene {
}
}
pub fn globals(&self) -> &Consts<Globals> {
&self.globals
}
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
self.camera.set_focus_pos(Vec3::unit_z() * 2.0);
self.camera.update(client.state().get_time());

View File

@ -1,5 +1,5 @@
use crate::{
render::Renderer,
render::{Consts, Globals, Renderer},
ui::{
self,
img_ids::{ImageGraphic, VoxelGraphic},
@ -1081,11 +1081,11 @@ impl CharSelectionUi {
pub fn maintain(&mut self, renderer: &mut Renderer) -> Vec<Event> {
let events = self.update_layout();
self.ui.maintain(renderer);
self.ui.maintain(renderer, None);
events
}
pub fn render(&self, renderer: &mut Renderer) {
self.ui.render(renderer);
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
self.ui.render(renderer, Some(globals));
}
}

View File

@ -508,11 +508,11 @@ impl MainMenuUi {
pub fn maintain(&mut self, global_state: &mut GlobalState) -> Vec<Event> {
let events = self.update_layout(global_state);
self.ui.maintain(global_state.window.renderer_mut());
self.ui.maintain(global_state.window.renderer_mut(), None);
events
}
pub fn render(&self, renderer: &mut Renderer) {
self.ui.render(renderer);
self.ui.render(renderer, None);
}
}

View File

@ -19,7 +19,8 @@ pub use self::{
skybox::{create_mesh as create_skybox_mesh, Locals as SkyboxLocals, SkyboxPipeline},
terrain::{Locals as TerrainLocals, TerrainPipeline},
ui::{
create_quad as create_ui_quad, create_tri as create_ui_tri, Mode as UiMode, UiPipeline,
create_quad as create_ui_quad, create_tri as create_ui_tri, Locals as UiLocals,
Mode as UiMode, UiPipeline,
},
Globals,
},

View File

@ -1,6 +1,7 @@
use super::super::{Pipeline, Quad, Tri, WinColorFmt, WinDepthFmt};
use super::super::{Globals, Pipeline, Quad, Tri, WinColorFmt, WinDepthFmt};
use gfx::{
self,
gfx_constant_struct_meta,
// Macros
gfx_defines,
gfx_impl_struct_meta,
@ -18,15 +19,21 @@ gfx_defines! {
mode: u32 = "v_mode",
}
constant Locals {
pos: [f32; 4] = "w_pos",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
locals: gfx::ConstantBuffer<Locals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
tex: gfx::TextureSampler<[f32; 4]> = "u_tex",
scissor: gfx::Scissor = (),
tgt_color: gfx::BlendTarget<WinColorFmt> = ("tgt_color", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA),
tgt_depth: gfx::DepthTarget<WinDepthFmt> = gfx::preset::depth::PASS_TEST,
tgt_depth: gfx::DepthTarget<WinDepthFmt> = gfx::preset::depth::LESS_EQUAL_TEST,
}
}
@ -36,6 +43,20 @@ impl Pipeline for UiPipeline {
type Vertex = Vertex;
}
impl From<Vec3<f32>> for Locals {
fn from(pos: Vec3<f32>) -> Self {
Self {
pos: [pos[0], pos[1], pos[2], 1.0],
}
}
}
impl Default for Locals {
fn default() -> Self {
Self { pos: [0.0; 4] }
}
}
/// 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.

View File

@ -314,7 +314,7 @@ impl Renderer {
memory::Typed,
};
type WinSurfaceData = <<WinColorFmt as Formatted>::Surface as SurfaceTyped>::DataType;
let mut download = self
let download = self
.factory
.create_download_buffer::<WinSurfaceData>(width as usize * height as usize)
.map_err(|err| RenderError::BufferCreationError(err))?;
@ -423,6 +423,8 @@ impl Renderer {
model: &Model<ui::UiPipeline>,
tex: &Texture<ui::UiPipeline>,
scissor: Aabr<u16>,
globals: &Consts<Globals>,
locals: &Consts<ui::Locals>,
) {
let Aabr { min, max } = scissor;
self.encoder.draw(
@ -437,6 +439,8 @@ impl Renderer {
h: max.y - min.y,
},
tex: (tex.srv.clone(), tex.sampler.clone()),
locals: locals.buf.clone(),
globals: globals.buf.clone(),
tgt_color: self.win_color_view.clone(),
tgt_depth: self.win_depth_view.clone(),
},

View File

@ -150,4 +150,9 @@ impl Camera {
pub fn get_orientation(&self) -> Vec3<f32> {
self.ori
}
/// Get the field of view of the camera in radians.
pub fn get_fov(&self) -> f32 {
self.fov
}
}

View File

@ -68,6 +68,11 @@ impl Scene {
}
}
/// Get a reference to the scene's globals
pub fn globals(&self) -> &Consts<Globals> {
&self.globals
}
/// Get a reference to the scene's camera.
pub fn camera(&self) -> &Camera {
&self.camera

View File

@ -100,7 +100,7 @@ impl SessionState {
// Render the screen using the global renderer
self.scene.render(renderer, &mut self.client.borrow_mut());
// Draw the UI to the screen
self.hud.render(renderer);
self.hud.render(renderer, self.scene.globals());
// Finish the frame
renderer.flush();
@ -171,18 +171,18 @@ impl PlayState for SessionState {
global_state.maintain();
// Maintain the scene.
self.scene.maintain(
global_state.window.renderer_mut(),
&self.client.borrow_mut(),
);
self.scene
.maintain(global_state.window.renderer_mut(), &self.client.borrow());
// extract HUD events ensuring the client borrow gets dropped
let hud_events = self.hud.maintain(
&self.client.borrow(),
global_state,
DebugInfo {
tps: clock.get_tps(),
ping_ms: self.client.borrow().get_ping_ms(),
},
&self.scene.camera(),
);
// Maintain the UI.
for event in hud_events {
@ -215,6 +215,7 @@ impl PlayState for SessionState {
}
}
}
{}
// Render the session.
self.render(global_state.window.renderer_mut());

View File

@ -40,6 +40,7 @@ pub struct ControlSettings {
pub toggle_debug: VirtualKeyCode,
pub fullscreen: VirtualKeyCode,
pub screenshot: VirtualKeyCode,
pub toggle_ingame_ui: VirtualKeyCode,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -95,6 +96,7 @@ impl Default for Settings {
toggle_debug: VirtualKeyCode::F3,
fullscreen: VirtualKeyCode::F11,
screenshot: VirtualKeyCode::F4,
toggle_ingame_ui: VirtualKeyCode::F6,
},
networking: NetworkingSettings {
username: "Username".to_string(),

View File

@ -6,7 +6,6 @@ pub struct Event(pub Input);
impl Event {
pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option<Self> {
use conrod_winit::*;
use winit;
// A wrapper around the winit window that allows us to implement the trait necessary for
// enabling the winit <-> conrod conversion functions.
struct WindowRef<'a>(&'a winit::Window);

View File

@ -12,13 +12,18 @@ mod font_ids;
pub use event::Event;
pub use graphic::Graphic;
pub use scale::ScaleMode;
pub use widgets::{image_slider::ImageSlider, toggle_button::ToggleButton};
pub use widgets::{
image_slider::ImageSlider,
ingame::{Ingame, IngameAnchor, Ingameable},
toggle_button::ToggleButton,
};
use crate::{
render::{
create_ui_quad, create_ui_tri, DynamicModel, Mesh, RenderError, Renderer, UiMode,
UiPipeline,
create_ui_quad, create_ui_tri, Consts, DynamicModel, Globals, Mesh, RenderError, Renderer,
UiLocals, UiMode, UiPipeline,
},
scene::camera::Camera,
window::Window,
Error,
};
@ -27,12 +32,12 @@ use common::assets;
use conrod_core::{
event::Input,
graph::Graph,
image::{Id as ImgId, Map},
image::{self, Map},
input::{touch::Touch, Motion, Widget},
render::Primitive,
render::{Primitive, PrimitiveKind},
text::{self, font},
widget::{id::Generator, Id as WidgId},
Ui as CrUi, UiBuilder, UiCell,
widget::{self, id::Generator},
Rect, UiBuilder, UiCell,
};
use graphic::Id as GraphicId;
use scale::Scale;
@ -55,6 +60,7 @@ enum DrawKind {
enum DrawCommand {
Draw { kind: DrawKind, verts: Range<usize> },
Scissor(Aabr<u16>),
WorldPos(Option<Consts<UiLocals>>),
}
impl DrawCommand {
fn image(verts: Range<usize>) -> DrawCommand {
@ -81,13 +87,16 @@ impl assets::Asset for Font {
}
pub struct Ui {
ui: CrUi,
ui: conrod_core::Ui,
image_map: Map<GraphicId>,
cache: Cache,
// Draw commands for the next render
draw_commands: Vec<DrawCommand>,
// Model for drawing the ui
model: DynamicModel<UiPipeline>,
// Consts for default ui drawing position (ie the interface)
interface_locals: Consts<UiLocals>,
default_globals: Consts<Globals>,
// Window size for updating scaling
window_resized: Option<Vec2<f64>>,
// Scaling of the ui
@ -99,12 +108,16 @@ impl Ui {
let scale = Scale::new(window, ScaleMode::Absolute(1.0));
let win_dims = scale.scaled_window_size().into_array();
let mut renderer = window.renderer_mut();
Ok(Self {
ui: UiBuilder::new(win_dims).build(),
image_map: Map::new(),
cache: Cache::new(window.renderer_mut())?,
cache: Cache::new(renderer)?,
draw_commands: vec![],
model: window.renderer_mut().create_dynamic_model(100)?,
model: renderer.create_dynamic_model(100)?,
interface_locals: renderer.create_consts(&[UiLocals::default()])?,
default_globals: renderer.create_consts(&[Globals::default()])?,
window_resized: None,
scale,
})
@ -118,7 +131,7 @@ impl Ui {
self.ui.handle_event(Input::Resize(w, h));
}
pub fn add_graphic(&mut self, graphic: Graphic) -> ImgId {
pub fn add_graphic(&mut self, graphic: Graphic) -> image::Id {
self.image_map.insert(self.cache.add_graphic(graphic))
}
@ -135,7 +148,7 @@ impl Ui {
}
// Accepts Option so widget can be unfocused.
pub fn focus_widget(&mut self, id: Option<WidgId>) {
pub fn focus_widget(&mut self, id: Option<widget::Id>) {
self.ui.keyboard_capture(match id {
Some(id) => id,
None => self.ui.window,
@ -143,7 +156,7 @@ impl Ui {
}
// Get id of current widget capturing keyboard.
pub fn widget_capturing_keyboard(&self) -> Option<WidgId> {
pub fn widget_capturing_keyboard(&self) -> Option<widget::Id> {
self.ui.global_input().current.widget_capturing_keyboard
}
@ -189,11 +202,11 @@ impl Ui {
}
}
pub fn widget_input(&self, id: WidgId) -> Widget {
pub fn widget_input(&self, id: widget::Id) -> Widget {
self.ui.widget_input(id)
}
pub fn maintain(&mut self, renderer: &mut Renderer) {
pub fn maintain(&mut self, renderer: &mut Renderer, cam_params: Option<(Mat4<f32>, f32)>) {
// Regenerate draw commands and associated models only if the ui changed
let mut primitives = match self.ui.draw_if_changed() {
Some(primitives) => primitives,
@ -216,6 +229,16 @@ impl Ui {
let window_scissor = default_scissor(renderer);
let mut current_scissor = window_scissor;
enum Placement {
Interface,
// Number and resolution
InWorld(usize, Option<f32>),
};
let mut placement = Placement::Interface;
// TODO: maybe mutate an ingame scale factor instead of this, depends on if we want them to scale with other ui scaling or not
let mut p_scale_factor = self.scale.scale_factor_physical();
// Switches to the `Plain` state and completes the previous `Command` if not already in the
// `Plain` state.
macro_rules! switch_to_plain_state {
@ -223,14 +246,12 @@ impl Ui {
if let State::Image = current_state {
self.draw_commands
.push(DrawCommand::image(start..mesh.vertices().len()));
current_state = State::Plain;
start = mesh.vertices().len();
current_state = State::Plain;
}
};
}
let p_scale_factor = self.scale.scale_factor_physical();
while let Some(prim) = primitives.next() {
let Primitive {
kind,
@ -242,6 +263,7 @@ impl Ui {
// Check for a change in the scissor.
let new_scissor = {
let (l, b, w, h) = scizzor.l_b_w_h();
let scale_factor = self.scale.scale_factor_physical();
// Calculate minimum x and y coordinates while
// flipping y axis (from +up to +down) and
// moving origin to top-left corner (from middle).
@ -249,12 +271,12 @@ impl Ui {
let min_y = self.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,
x: (min_x * scale_factor) as u16,
y: (min_y * scale_factor) as u16,
},
max: Vec2 {
x: ((min_x + w) * p_scale_factor) as u16,
y: ((min_y + h) * p_scale_factor) as u16,
x: ((min_x + w) * scale_factor) as u16,
y: ((min_y + h) * scale_factor) as u16,
},
}
.intersection(window_scissor)
@ -272,12 +294,41 @@ impl Ui {
self.draw_commands.push(DrawCommand::Scissor(new_scissor));
}
match placement {
Placement::InWorld(0, _) => {
placement = Placement::Interface;
p_scale_factor = self.scale.scale_factor_physical();
// Finish current state
self.draw_commands.push(match current_state {
State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
State::Image => DrawCommand::image(start..mesh.vertices().len()),
});
start = mesh.vertices().len();
// Push new position command
self.draw_commands.push(DrawCommand::WorldPos(None));
}
Placement::InWorld(n, res) => match kind {
// Other types don't need to be drawn in the game
PrimitiveKind::Other(_) => {}
_ => {
placement = Placement::InWorld(n - 1, res);
if res.is_none() {
continue;
}
}
},
Placement::Interface => {}
}
// Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0).
let ui_win_w = self.ui.win_w;
let ui_win_h = self.ui.win_h;
let (ui_win_w, ui_win_h) = if let Placement::InWorld(_, Some(res)) = placement {
(res as f64, res as f64)
} else {
(self.ui.win_w, self.ui.win_h)
};
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 gl_aabr = |rect: Rect| {
let (l, r, b, t) = rect.l_r_b_t();
Aabr {
min: Vec2::new(vx(l), vy(b)),
@ -285,7 +336,6 @@ impl Ui {
}
};
use conrod_core::render::PrimitiveKind;
match kind {
PrimitiveKind::Image {
image_id,
@ -321,16 +371,17 @@ impl Ui {
// Transform the source rectangle into uv coordinate.
// TODO: Make sure this is right.
let source_aabr = {
let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0); /*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_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0);
/*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),
};*/
Aabr {
min: Vec2::new(uv_l, uv_b),
max: Vec2::new(uv_r, uv_t),
@ -371,13 +422,8 @@ impl Ui {
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 positioned_glyphs = text.positioned_glyphs(p_scale_factor as f32);
let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex();
// Queue the glyphs to be cached.
for glyph in positioned_glyphs {
@ -410,12 +456,16 @@ impl Ui {
};
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,
vx(screen_rect.min.x as f64 / p_scale_factor
- self.ui.win_w / 2.0),
vy(self.ui.win_h / 2.0
- screen_rect.max.y as f64 / p_scale_factor),
),
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,
vx(screen_rect.max.x as f64 / p_scale_factor
- self.ui.win_w / 2.0),
vy(self.ui.win_h / 2.0
- screen_rect.min.y as f64 / p_scale_factor),
),
};
mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text));
@ -469,10 +519,48 @@ impl Ui {
));
}
}
PrimitiveKind::Other(container) => {
if container.type_id == std::any::TypeId::of::<widgets::ingame::State>() {
// Calculate the scale factor to pixels at this 3d point using the camera.
if let Some((view_mat, fov)) = cam_params {
// Retrieve world position
let parameters = container
.state_and_style::<widgets::ingame::State, widgets::ingame::Style>()
.unwrap()
.state
.parameters;
// Finish current state
self.draw_commands.push(match current_state {
State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
State::Image => DrawCommand::image(start..mesh.vertices().len()),
});
start = mesh.vertices().len();
// Push new position command
self.draw_commands.push(DrawCommand::WorldPos(Some(
renderer.create_consts(&[parameters.pos.into()]).unwrap(),
)));
let pos_in_view = view_mat * Vec4::from_point(parameters.pos);
let scale_factor = self.ui.win_w as f64
/ (-2.0
* pos_in_view.z as f64
* (0.5 * fov as f64).tan()
* parameters.res as f64);
// Don't process ingame elements behind the camera or very far away
placement = if scale_factor > 0.1 {
p_scale_factor = ((scale_factor * 10.0).log2().round().powi(2)
/ 10.0)
.min(1.6)
.max(0.2);
Placement::InWorld(parameters.num, Some(parameters.res))
} else {
Placement::InWorld(parameters.num, None)
};
}
}
}
_ => {} // TODO: Add this.
//PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);}
// Other unneeded for now.
//PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);}
}
}
// Enter the final command.
@ -481,6 +569,25 @@ impl Ui {
State::Image => DrawCommand::image(start..mesh.vertices().len()),
});
// Draw glyph cache (use for debugging).
/*self.draw_commands
.push(DrawCommand::Scissor(default_scissor(renderer)));
start = mesh.vertices().len();
mesh.push_quad(create_ui_quad(
Aabr {
min: (-1.0, -1.0).into(),
max: (1.0, 1.0).into(),
},
Aabr {
min: (0.0, 1.0).into(),
max: (1.0, 0.0).into(),
},
Rgba::new(1.0, 1.0, 1.0, 0.8),
UiMode::Text,
));
self.draw_commands
.push(DrawCommand::plain(start..mesh.vertices().len()));*/
// Create a larger dynamic model if the mesh is larger than the current model size.
if self.model.vbuf.len() < mesh.vertices().len() {
self.model = renderer
@ -506,12 +613,17 @@ impl Ui {
}
}
pub fn render(&self, renderer: &mut Renderer) {
let mut scissor_to_render = default_scissor(renderer);
pub fn render(&self, renderer: &mut Renderer, maybe_globals: Option<&Consts<Globals>>) {
let mut scissor = default_scissor(renderer);
let globals = maybe_globals.unwrap_or(&self.default_globals);
let mut locals = &self.interface_locals;
for draw_command in self.draw_commands.iter() {
match draw_command {
DrawCommand::Scissor(scissor) => {
scissor_to_render = *scissor;
DrawCommand::Scissor(new_scissor) => {
scissor = *new_scissor;
}
DrawCommand::WorldPos(ref pos) => {
locals = pos.as_ref().unwrap_or(&self.interface_locals);
}
DrawCommand::Draw { kind, verts } => {
let tex = match kind {
@ -519,14 +631,14 @@ impl Ui {
DrawKind::Plain => self.cache.glyph_cache_tex(),
};
let model = self.model.submodel(verts.clone());
renderer.render_ui_element(&model, &tex, scissor_to_render);
renderer.render_ui_element(&model, &tex, scissor, globals, locals);
}
}
}
}
}
fn default_scissor(renderer: &mut Renderer) -> Aabr<u16> {
fn default_scissor(renderer: &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 },

View File

@ -48,6 +48,10 @@ impl Scale {
pub fn scale_factor_physical(&self) -> f64 {
self.scale_factor_logical() * self.dpi_factor
}
// Get the dpi factor (ratio between physical and logical coordinates)
pub fn dpi_factor(&self) -> f64 {
self.dpi_factor
}
// Updates internal window size (and/or dpi_factor).
pub fn window_resized(&mut self, new_dims: Vec2<f64>, renderer: &Renderer) {
self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x;

View File

@ -0,0 +1,199 @@
use conrod_core::{
builder_methods, image,
position::Dimension,
widget::{self, button, Id},
widget_ids, Color, Position, Positionable, Rect, Sizeable, Ui, UiCell, Widget, WidgetCommon,
};
use std::slice;
use vek::*;
#[derive(Clone, WidgetCommon)]
pub struct Ingame<W> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
widget: W,
parameters: IngameParameters,
}
pub trait Ingameable: Widget + Sized {
fn prim_count(&self) -> usize;
// Note this is not responsible for the 3d positioning
// Only call this directly if using IngameAnchor
fn set_ingame(self, id: widget::Id, parent_id: Id, ui: &mut UiCell) -> Self::Event {
self
// should pass focus to the window if these are clicked
// (they are not displayed where conrod thinks they are)
.graphics_for(ui.window)
//.parent(parent_id) // is this needed
.set(id, ui)
}
fn position_ingame(self, pos: Vec3<f32>) -> Ingame<Self> {
Ingame::new(pos, self)
}
}
pub trait PrimitiveMarker {}
impl PrimitiveMarker for widget::Line {}
impl PrimitiveMarker for widget::Image {}
impl<I> PrimitiveMarker for widget::PointPath<I> {}
impl PrimitiveMarker for widget::Circle {}
impl<S> PrimitiveMarker for widget::Oval<S> {}
impl<I> PrimitiveMarker for widget::Polygon<I> {}
impl PrimitiveMarker for widget::Rectangle {}
impl<S, I> PrimitiveMarker for widget::Triangles<S, I> {}
impl<'a> PrimitiveMarker for widget::Text<'a> {}
impl<P> Ingameable for P
where
P: Widget + PrimitiveMarker,
{
fn prim_count(&self) -> usize {
1
}
}
#[derive(Copy, Clone, PartialEq)]
pub struct IngameParameters {
// Number of primitive widgets to position in the game at the specified position
// Note this could be more than the number of widgets in the widgets field since widgets can contain widgets
pub num: usize,
pub pos: Vec3<f32>,
// Number of pixels per 1 unit in world coordinates (ie a voxel)
// Used for widgets that are rasterized before being sent to the gpu (text & images)
// Potentially make this autmatic based on distance to camera?
pub res: f32,
}
pub struct State {
id: Option<widget::Id>,
pub parameters: IngameParameters,
}
pub type Style = ();
impl<W: Ingameable> Ingame<W> {
pub fn new(pos: Vec3<f32>, widget: W) -> Self {
Self {
common: widget::CommonBuilder::default(),
parameters: IngameParameters {
num: widget.prim_count(),
pos,
res: 1.0,
},
widget,
}
}
builder_methods! {
pub resolution { parameters.res = f32 }
}
}
impl<W: Ingameable> Widget for Ingame<W> {
type State = State;
type Style = Style;
type Event = W::Event;
fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State {
State {
id: Some(id_gen.next()),
parameters: self.parameters,
}
}
fn style(&self) -> Self::Style {
()
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, ui, .. } = args;
let Ingame {
widget, parameters, ..
} = self;
// Update pos if it has changed
if state.parameters != parameters {
state.update(|s| {
s.parameters = parameters;
});
}
widget.set_ingame(state.id.unwrap(), id, ui)
}
fn default_x_position(&self, ui: &Ui) -> Position {
Position::Absolute(0.0)
}
fn default_y_position(&self, ui: &Ui) -> Position {
Position::Absolute(0.0)
}
fn default_x_dimension(&self, ui: &Ui) -> Dimension {
Dimension::Absolute(1.0)
}
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
Dimension::Absolute(1.0)
}
}
// Use this if you have multiple widgets that you want to place at the same spot in-game
// but don't want to create a new custom widget to contain them both
// Note: widgets must be set immediately after settings this
// Note: remove this if it ends up unused
#[derive(Clone, WidgetCommon)]
pub struct IngameAnchor {
#[conrod(common_builder)]
common: widget::CommonBuilder,
parameters: IngameParameters,
}
impl IngameAnchor {
pub fn new(pos: Vec3<f32>) -> Self {
IngameAnchor {
common: widget::CommonBuilder::default(),
parameters: IngameParameters {
num: 0,
pos,
res: 1.0,
},
}
}
pub fn for_widget(mut self, widget: impl Ingameable) -> Self {
self.parameters.num += widget.prim_count();
self
}
pub fn for_widgets(mut self, widget: impl Ingameable, n: usize) -> Self {
self.parameters.num += n * widget.prim_count();
self
}
pub fn for_prims(mut self, num: usize) -> Self {
self.parameters.num += num;
self
}
}
impl Widget for IngameAnchor {
type State = State;
type Style = Style;
type Event = ();
fn init_state(&self, _: widget::id::Generator) -> Self::State {
State {
id: None,
parameters: self.parameters,
}
}
fn style(&self) -> Self::Style {
()
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, ui, .. } = args;
let IngameAnchor { parameters, .. } = self;
// Update pos if it has changed
if state.parameters != parameters {
state.update(|s| {
s.parameters = parameters;
});
}
}
}

View File

@ -1,2 +1,3 @@
pub mod image_slider;
pub mod ingame;
pub mod toggle_button;

View File

@ -60,6 +60,7 @@ impl Window {
key_map.insert(settings.controls.toggle_debug, Key::ToggleDebug);
key_map.insert(settings.controls.fullscreen, Key::Fullscreen);
key_map.insert(settings.controls.screenshot, Key::Screenshot);
key_map.insert(settings.controls.toggle_ingame_ui, Key::ToggleIngameUi);
let tmp = Ok(Self {
events_loop,
@ -219,7 +220,7 @@ impl Window {
std::thread::spawn(move || {
use std::{path::PathBuf, time::SystemTime};
// Check if folder exists and create it if it does not
let mut path = std::path::PathBuf::from("./screenshots");
let mut path = PathBuf::from("./screenshots");
if !path.exists() {
if let Err(err) = std::fs::create_dir(&path) {
log::error!("Coudn't create folder for screenshot: {:?}", err);
@ -266,6 +267,7 @@ pub enum Key {
ToggleDebug,
Fullscreen,
Screenshot,
ToggleIngameUi,
}
/// Represents an incoming event from the window.