diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index a9d08f596c..d004032b70 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -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" diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index a3b1518a0d..da36849d3e 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -76,7 +76,7 @@ fn main() { // Set up the initial play state let mut states: Vec> = 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()) diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index 868a86f739..9cdf4b72cf 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -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(); diff --git a/voxygen/src/render/pipelines/ui.rs b/voxygen/src/render/pipelines/ui.rs index e152da68e6..449b07fe54 100644 --- a/voxygen/src/render/pipelines/ui.rs +++ b/voxygen/src/render/pipelines/ui.rs @@ -35,7 +35,7 @@ gfx_defines! { locals: gfx::ConstantBuffer = "u_locals", tex: gfx::TextureSampler<[f32; 4]> = "u_tex", - tgt_color: gfx::RenderTarget = "tgt_color", + tgt_color: gfx::BlendTarget = ("tgt_color", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA), tgt_depth: gfx::DepthTarget = gfx::preset::depth::PASS_TEST, } } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index c694f2c6b3..0ebb183d1f 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -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 { + pub fn new(window: &mut Window) -> Result { 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()); diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index e08423a7af..ce09e6f5f7 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -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, } +// TODO: Should functions be returning UiError instead of Error? impl Cache { pub fn new(renderer: &mut Renderer) -> Result { Ok(Self { @@ -50,21 +55,27 @@ impl Cache { pub fn blank_texture(&self) -> &Texture { &self.blank_texture } } +pub enum UiPrimitive { + Image(Consts, ImgId) +} + pub struct Ui { ui: CrUi, image_map: Map>, cache: Cache, - locals: Consts, + // Primatives to draw on the next render + ui_primitives: Vec, } impl Ui { - pub fn new(renderer: &mut Renderer, dim: [f64; 2]) -> Result { + pub fn new(window: &mut Window) -> Result { + // 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(&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); + } + }); + } } diff --git a/voxygen/src/ui/test.rs b/voxygen/src/ui/test.rs new file mode 100644 index 0000000000..947f01e599 --- /dev/null +++ b/voxygen/src/ui/test.rs @@ -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); + } +} diff --git a/voxygen/src/ui/title.rs b/voxygen/src/ui/title.rs new file mode 100644 index 0000000000..c16718b524 --- /dev/null +++ b/voxygen/src/ui/title.rs @@ -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); + } +} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 37c38bc232..251660f146 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -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) } diff --git a/voxygen/test_assets/test_bag.png b/voxygen/test_assets/test_bag.png new file mode 100644 index 0000000000..fa923309b5 Binary files /dev/null and b/voxygen/test_assets/test_bag.png differ diff --git a/voxygen/test_assets/test_bag_contents.png b/voxygen/test_assets/test_bag_contents.png new file mode 100644 index 0000000000..faf8480211 Binary files /dev/null and b/voxygen/test_assets/test_bag_contents.png differ diff --git a/voxygen/test_assets/test_bag_hover.png b/voxygen/test_assets/test_bag_hover.png new file mode 100644 index 0000000000..5ad264212a Binary files /dev/null and b/voxygen/test_assets/test_bag_hover.png differ diff --git a/voxygen/test_assets/test_bag_press.png b/voxygen/test_assets/test_bag_press.png new file mode 100644 index 0000000000..4c8be52292 Binary files /dev/null and b/voxygen/test_assets/test_bag_press.png differ diff --git a/voxygen/test_assets/test_close_btn.png b/voxygen/test_assets/test_close_btn.png new file mode 100644 index 0000000000..66e2ad8c02 Binary files /dev/null and b/voxygen/test_assets/test_close_btn.png differ diff --git a/voxygen/test_assets/test_close_btn_hover.png b/voxygen/test_assets/test_close_btn_hover.png new file mode 100644 index 0000000000..6a50055ebb Binary files /dev/null and b/voxygen/test_assets/test_close_btn_hover.png differ diff --git a/voxygen/test_assets/test_close_btn_press.png b/voxygen/test_assets/test_close_btn_press.png new file mode 100644 index 0000000000..280cb98913 Binary files /dev/null and b/voxygen/test_assets/test_close_btn_press.png differ diff --git a/voxygen/test_assets/test_menu_bottom.png b/voxygen/test_assets/test_menu_bottom.png new file mode 100644 index 0000000000..9344ea0c77 Binary files /dev/null and b/voxygen/test_assets/test_menu_bottom.png differ diff --git a/voxygen/test_assets/test_menu_button_blank.png b/voxygen/test_assets/test_menu_button_blank.png new file mode 100644 index 0000000000..86c54a7f02 Binary files /dev/null and b/voxygen/test_assets/test_menu_button_blank.png differ diff --git a/voxygen/test_assets/test_menu_midsection.png b/voxygen/test_assets/test_menu_midsection.png new file mode 100644 index 0000000000..f11e82eae1 Binary files /dev/null and b/voxygen/test_assets/test_menu_midsection.png differ diff --git a/voxygen/test_assets/test_menu_top.png b/voxygen/test_assets/test_menu_top.png new file mode 100644 index 0000000000..7851741b15 Binary files /dev/null and b/voxygen/test_assets/test_menu_top.png differ