setup ui image rendering, started creating test ui with iteraction

Former-commit-id: 73235ad6b25ab160e9cde3ad2513d4dd32660161
This commit is contained in:
Imberflur 2019-02-15 22:01:42 -05:00 committed by Imbris
parent d685706c9b
commit 3f052cae1c
20 changed files with 400 additions and 71 deletions

View File

@ -18,7 +18,8 @@ gfx = "0.17"
gfx_device_gl = { version = "0.15", optional = true }
gfx_window_glutin = "0.28"
glutin = "0.19"
conrod_core = "0.62"
conrod_core = "0.63"
conrod_winit = "0.63"
# ECS
specs = "0.14"

View File

@ -76,7 +76,7 @@ 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(),
&mut global_state.window,
))];
states.last().map(|current_state| {
log::info!("Started game with state '{}'", current_state.name())

View File

@ -1,47 +1,34 @@
// Library
use vek::*;
use image;
use conrod_core::widget::image::Image as ImageWidget;
use conrod_core::{Positionable, Sizeable, Widget};
// Crate
use crate::{
PlayState,
PlayStateResult,
GlobalState,
window::Event,
window::{
Event,
Window,
},
session::SessionState,
render::{
Consts,
UiLocals,
Renderer,
},
ui::{
Ui
},
ui::title::TitleUi,
};
pub struct TitleState {
ui: Ui
title_ui: TitleUi,
}
impl TitleState {
/// Create a new `TitleState`
pub fn new(renderer: &mut Renderer) -> Self {
let mut ui = Ui::new(renderer, [500.0, 500.0]).unwrap();
let widget_id = ui.new_widget();
let image = image::open(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/test.png")).unwrap();
let title_img = ui.new_image(renderer, &image).unwrap();
ui.set_widgets(|ui_cell| {
ImageWidget::new(title_img)
.x_y(0.0, 0.0)
.w_h(500.0,500.0)
.set(widget_id, ui_cell);
});
pub fn new(window: &mut Window) -> Self {
Self {
ui
title_ui: TitleUi::new(window)
}
}
}
// The background colour
@ -56,21 +43,24 @@ impl PlayState for TitleState {
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
Box::new(SessionState::new(&mut global_state.window).unwrap()), // TODO: Handle this error
),
// Pass events to ui
Event::UiEvent(input) => {
self.title_ui.handle_event(input);
}
// Ignore all other events
_ => {},
}
}
global_state.window.renderer_mut().clear(BG_COLOR);
// Maintain the UI
//self.ui.maintain(global_state.window.renderer_mut());
self.title_ui.maintain(global_state.window.renderer_mut());
// Draw the UI to the screen
self.ui.render(global_state.window.renderer_mut());
self.title_ui.render(global_state.window.renderer_mut());
// Finish the frame
global_state.window.renderer_mut().flush();

View File

@ -35,7 +35,7 @@ gfx_defines! {
locals: gfx::ConstantBuffer<Locals> = "u_locals",
tex: gfx::TextureSampler<[f32; 4]> = "u_tex",
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
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,
}
}

View File

@ -18,9 +18,10 @@ use crate::{
PlayStateResult,
GlobalState,
key_state::KeyState,
window::{Event, Key},
window::{Event, Key, Window},
render::Renderer,
scene::Scene,
ui::test::TestUi,
};
const FPS: u64 = 60;
@ -29,18 +30,21 @@ pub struct SessionState {
scene: Scene,
client: Client,
key_state: KeyState,
// TODO: remove this
test_ui: TestUi,
}
/// 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(),
test_ui: TestUi::new(window),
})
}
}
@ -78,6 +82,8 @@ impl SessionState {
// Render the screen using the global renderer
self.scene.render_to(renderer);
// Draw the UI to the screen
self.test_ui.render(renderer);
// Finish the frame
renderer.flush();
@ -123,6 +129,10 @@ impl PlayState for SessionState {
Event::KeyUp(Key::MoveBack) => self.key_state.down = false,
Event::KeyUp(Key::MoveLeft) => self.key_state.left = false,
Event::KeyUp(Key::MoveRight) => self.key_state.right = false,
// Pass events to ui
Event::UiEvent(input) => {
self.test_ui.handle_event(input);
}
// Pass all other events to the scene
event => { self.scene.handle_input_event(event); },
};
@ -135,6 +145,8 @@ impl PlayState for SessionState {
// Maintain the scene
self.scene.maintain(global_state.window.renderer_mut(), &self.client);
// Maintain the UI
self.test_ui.maintain(global_state.window.renderer_mut());
// Render the session
self.render(global_state.window.renderer_mut());

View File

@ -1,7 +1,7 @@
// TODO: what was the purpose of size request?
// TODO: cache entire UI render
// TODO: do we need to store locals for each widget?
// TODO: sizing? : renderer.get_resolution().map(|e| e as f32)
pub mod title;
pub mod test;
// TODO: cache entire UI render (would be somewhat pointless if we are planning on constantly animated ui)
// TODO: figure out proper way to propagate events down to the ui
// Library
use image::DynamicImage;
@ -10,7 +10,10 @@ use conrod_core::{
UiBuilder,
UiCell,
image::{Map, Id as ImgId},
widget::Id as WidgId,
widget::{Id as WidgId, id::Generator},
render::Primitive,
event::Input,
input::{Global, Widget},
};
// Crate
@ -26,6 +29,7 @@ use crate::{
Consts,
create_ui_quad_mesh,
},
window::Window,
};
#[derive(Debug)]
@ -38,6 +42,7 @@ pub struct Cache {
blank_texture: Texture<UiPipeline>,
}
// TODO: Should functions be returning UiError instead of Error?
impl Cache {
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
Ok(Self {
@ -50,21 +55,27 @@ impl Cache {
pub fn blank_texture(&self) -> &Texture<UiPipeline> { &self.blank_texture }
}
pub enum UiPrimitive {
Image(Consts<UiLocals>, ImgId)
}
pub struct Ui {
ui: CrUi,
image_map: Map<Texture<UiPipeline>>,
cache: Cache,
locals: Consts<UiLocals>,
// Primatives to draw on the next render
ui_primitives: Vec<UiPrimitive>,
}
impl Ui {
pub fn new(renderer: &mut Renderer, dim: [f64; 2]) -> Result<Self, Error> {
pub fn new(window: &mut Window) -> Result<Self, Error> {
// Retrieve the logical size of the window content
let (w, h) = window.logical_size();
Ok(Self {
ui: UiBuilder::new(dim).build(),
ui: UiBuilder::new([w, h]).build(),
image_map: Map::new(),
cache: Cache::new(renderer)?,
locals: renderer.create_consts(&[UiLocals::default()])?,
cache: Cache::new(window.renderer_mut())?,
ui_primitives: vec![],
})
}
@ -72,42 +83,83 @@ impl Ui {
Ok(self.image_map.insert(renderer.create_texture(image)?))
}
pub fn new_widget(&mut self) -> WidgId {
self.ui.widget_id_generator().next()
pub fn id_generator(&mut self) -> Generator {
self.ui.widget_id_generator()
}
pub fn set_widgets<F>(&mut self, f: F) where F: FnOnce(&mut UiCell) {
f(&mut self.ui.set_widgets());
pub fn set_widgets(&mut self) -> UiCell {
self.ui.set_widgets()
}
pub fn handle_event(&mut self, event: Input) {
self.ui.handle_event(event);
}
pub fn widget_input(&self, id: WidgId) -> Widget {
self.ui.widget_input(id)
}
pub fn global_input(&self) -> &Global {
self.ui.global_input()
}
// TODO: change render back to &self and use maintain for mutable operations
pub fn maintain(&mut self, renderer: &mut Renderer) {
//renderer.get_resolution().map(|e| e as f32),
}
pub fn render(&mut self, renderer: &mut Renderer) {
if let Some(mut primitives) = Some(self.ui.draw()) {
//render the primatives one at a time
let ref mut ui = self.ui;
// removed this because ui will resize itself now that it recieves events
// update window size
//let res = renderer.get_resolution().map(|e| e as f64);
//if res[0] != ui.win_w || res[1] != ui.win_h {
// ui.win_w = res[0];
// ui.win_h = res[1];
// ui.needs_redraw();
//}
// Gather primatives and recreate locals only if ui_changed
if let Some(mut primitives) = ui.draw_if_changed() {
self.ui_primitives.clear();
while let Some(prim) = primitives.next() {
use conrod_core::render::{Primitive, PrimitiveKind};
let Primitive {kind, scizzor, rect, ..} = prim;
// Transform from conrod to our render coords
// Conrod uses the center of the screen as the origin
// Up & Right are positive directions
let x = prim.rect.left();
let y = prim.rect.top();
let (w, h) = prim.rect.w_h();
let bounds = [
(x / ui.win_w + 0.5) as f32,
(-1.0 * (y / ui.win_h) + 0.5) as f32,
(w / ui.win_w) as f32,
(h / ui.win_h) as f32
];
// TODO: Remove this
let new_ui_locals = renderer.create_consts(&[UiLocals::new(bounds)])
.expect("Could not create new const for ui locals");
use conrod_core::render::{PrimitiveKind};
// TODO: Use scizzor
let Primitive {kind, scizzor, id, ..} = prim;
match kind {
PrimitiveKind::Image { image_id, color, source_rect } => {
renderer.update_consts(&mut self.locals, &[UiLocals::new(
[0.0, 0.0, 1.0, 1.0],
)]);
let tex = self.image_map.get(&image_id).expect("Image does not exist in image map");
renderer.render_ui_element(&self.cache.model(), &self.locals, &tex);
//renderer.update_consts(&mut self.locals, &[UiLocals::new(
// [0.0, 0.0, 1.0, 1.0],
// )]);
self.ui_primitives.push(UiPrimitive::Image(new_ui_locals, image_id));
}
PrimitiveKind::Other {..} => {println!("primitive kind other with id");}
PrimitiveKind::Rectangle {..} => {println!("primitive kind rect");}
PrimitiveKind::Text {..} => {println!("primitive kind text");}
PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor");}
PrimitiveKind::TrianglesSingleColor {..} => {println!("primitive kind singlecolor");}
_ => {}
// TODO: Add these
//PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);}
//PrimitiveKind::Rectangle { color } => {println!("primitive kind rect[x:{},y:{},w:{},h:{}] with color {:?} and id {:?}", x, y, w, h, color, id);}
//PrimitiveKind::Text {..} => {println!("primitive kind text with id {:?}", id);}
//PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);}
//PrimitiveKind::TrianglesSingleColor {..} => {println!("primitive kind singlecolor with id {:?}", id);}
}
}
}
}
pub fn render(&self, renderer: &mut Renderer) {
self.ui_primitives.iter().for_each(|ui_primitive| match ui_primitive {
UiPrimitive::Image(ui_locals, image_id) => {
let tex = self.image_map.get(&image_id).expect("Image does not exist in image map");
renderer.render_ui_element(&self.cache.model(), &ui_locals, &tex);
}
});
}
}

163
voxygen/src/ui/test.rs Normal file
View File

@ -0,0 +1,163 @@
// Library
use conrod_core::{
Positionable,
Sizeable,
Widget,
widget_ids,
event::{Widget as WidgetEvent, Input, Click},
image::Id as ImgId,
input::state::mouse::Button as MouseButton,
widget::{
Image,
Button,
Id as WidgId,
}
};
// Crate
use crate::{
window::Window,
render::Renderer,
};
// Local
use super::Ui;
widget_ids!{
struct Ids {
menu_buttons[],
bag,
bag_contents,
bag_close,
menu_top,
menu_mid,
menu_bot,
}
}
// TODO: make macro to mimic widget_ids! for images ids or find another solution to simplify addition of new images to the code.
struct Imgs {
menu_button: ImgId,
bag: ImgId,
bag_hover: ImgId,
bag_press: ImgId,
bag_contents: ImgId,
menu_top: ImgId,
menu_mid: ImgId,
menu_bot: ImgId,
close_button: ImgId,
close_button_hover: ImgId,
close_button_press: ImgId,
}
impl Imgs {
fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs {
let mut load = |filename| {
let image = image::open(&[env!("CARGO_MANIFEST_DIR"), "/test_assets/", filename].concat()).unwrap();
ui.new_image(renderer, &image).unwrap()
};
Imgs {
menu_button: load("test_menu_button_blank.png"),
bag: load("test_bag.png"),
bag_hover: load("test_bag_hover.png"),
bag_press: load("test_bag_press.png"),
bag_contents: load("test_bag_contents.png"),
menu_top: load("test_menu_top.png"),
menu_mid: load("test_menu_midsection.png"),
menu_bot: load("test_menu_bottom.png"),
close_button: load("test_close_btn.png"),
close_button_hover: load("test_close_btn_hover.png"),
close_button_press: load("test_close_btn_press.png"),
}
}
}
pub struct TestUi {
ui: Ui,
ids: Ids,
imgs: Imgs,
bag_open: bool,
}
impl TestUi {
pub fn new(window: &mut Window) -> Self {
let mut ui = Ui::new(window).unwrap();
// Generate ids
let mut ids = Ids::new(ui.id_generator());
ids.menu_buttons.resize(5, &mut ui.id_generator());
// Load images
let imgs = Imgs::new(&mut ui, window.renderer_mut());
Self {
ui,
imgs,
ids,
bag_open: false,
}
}
fn ui_layout(&mut self) {
// Update if a event has occured
if !self.ui.global_input().events().next().is_some() {
return;
}
// Process input
for e in self.ui.widget_input(self.ids.bag).events() {
match e {
WidgetEvent::Click(click) => match click.button {
MouseButton::Left => {
self.bag_open = true;
}
_ => {}
}
_ => {}
}
}
for e in self.ui.widget_input(self.ids.bag_close).events() {
match e {
WidgetEvent::Click(click) => match click.button {
MouseButton::Left => {
self.bag_open = false;
}
_ => {}
}
_ => {}
}
}
let bag_open = self.bag_open;
let mut ui_cell = self.ui.set_widgets();
// Bag
Button::image(self.imgs.bag)
.bottom_right_with_margin(20.0)
.hover_image(if bag_open { self.imgs.bag } else { self.imgs.bag_hover })
.press_image(if bag_open { self.imgs.bag } else { self.imgs.bag_press })
.w_h(51.0, 58.0)
.set(self.ids.bag, &mut ui_cell);
// Bag contents
if self.bag_open {
// Contents
Image::new(self.imgs.bag_contents)
.w_h(212.0, 246.0)
.x_y_relative_to(self.ids.bag, -92.0-25.5+12.0, 109.0+29.0-13.0)
.set(self.ids.bag_contents, &mut ui_cell);
// Close button
Button::image(self.imgs.close_button)
.w_h(20.0, 20.0)
.hover_image(self.imgs.close_button_hover)
.press_image(self.imgs.close_button_press)
.top_right_with_margins_on(self.ids.bag_contents, 0.0, 10.0)
.set(self.ids.bag_close, &mut ui_cell);
}
}
pub fn handle_event(&mut self, input: Input) {
self.ui.handle_event(input);
}
pub fn maintain(&mut self, renderer: &mut Renderer) {
self.ui_layout();
self.ui.maintain(renderer);
}
pub fn render(&self, renderer: &mut Renderer) {
self.ui.render(renderer);
}
}

67
voxygen/src/ui/title.rs Normal file
View File

@ -0,0 +1,67 @@
// Library
use conrod_core::{
Positionable,
Sizeable,
Widget,
event::Input,
image::Id as ImgId,
widget::{
Image as ImageWidget,
Canvas as CanvasWidget,
Id as WidgId,
}
};
// Crate
use crate::{
window::Window,
render::Renderer,
};
// Local
use super::Ui;
pub struct TitleUi {
ui: Ui,
widget_id: WidgId,
title_img_id: ImgId,
}
impl TitleUi {
pub fn new(window: &mut Window) -> Self {
let mut ui = Ui::new(window).unwrap();
let widget_id = ui.id_generator().next();
let image = image::open(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/test.png")).unwrap();
let title_img_id = ui.new_image(window.renderer_mut(), &image).unwrap();
Self {
ui,
widget_id,
title_img_id,
}
}
fn ui_layout(&mut self) {
// Update if a event has occured
if !self.ui.global_input().events().next().is_some() {
return;
}
let mut ui_cell = self.ui.set_widgets();
ImageWidget::new(self.title_img_id)
.top_left()
.w_h(500.0, 500.0)
.set(self.widget_id, &mut ui_cell);
}
pub fn handle_event(&mut self, input: Input) {
self.ui.handle_event(input);
}
pub fn maintain(&mut self, renderer: &mut Renderer) {
self.ui_layout();
self.ui.maintain(renderer);
}
pub fn render(&self, renderer: &mut Renderer) {
self.ui.render(renderer);
}
}

View File

@ -82,7 +82,13 @@ impl Window {
let key_map = &self.key_map;
let mut events = vec![];
self.events_loop.poll_events(|event| match event {
self.events_loop.poll_events(|event| match {
// Hack grab of events for testing ui
if let Some(event) = conrod_winit::convert_event(event.clone(), window.window()) {
events.push(Event::UiEvent(event));
}
event
} {
glutin::Event::WindowEvent { event, .. } => match event {
glutin::WindowEvent::CloseRequested => events.push(Event::Close),
glutin::WindowEvent::Resized(glutin::dpi::LogicalSize { width, height }) => {
@ -136,6 +142,10 @@ impl Window {
self.window.grab_cursor(grab)
.expect("Failed to grab/ungrab cursor");
}
pub fn logical_size(&self) -> (f64, f64) {
self.window.get_inner_size().unwrap_or(glutin::dpi::LogicalSize::new(0.0, 0.0)).into()
}
}
/// Represents a key that the game recognises after keyboard mapping
@ -164,4 +174,5 @@ pub enum Event {
KeyDown(Key),
/// A key that the game recognises has been released down
KeyUp(Key),
UiEvent(conrod_core::event::Input)
}

BIN
voxygen/test_assets/test_bag.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_bag_contents.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_bag_hover.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_bag_press.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_close_btn.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_close_btn_hover.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_close_btn_press.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_menu_bottom.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_menu_button_blank.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_menu_midsection.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/test_menu_top.png (Stored with Git LFS) Normal file

Binary file not shown.