From 1d9c35b91d1025850cded5da338fa7e864af3b29 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 19 May 2019 10:15:50 -0400 Subject: [PATCH] Add ability to position tuples of widgets ingame Former-commit-id: e2958a37e7062132d6a7b6d5ad61bd4002768532 --- voxygen/src/hud/mod.rs | 35 ++-- voxygen/src/ui/event.rs | 1 - voxygen/src/ui/mod.rs | 22 ++- voxygen/src/ui/widgets/ingame.rs | 317 +++++++++++++++++++++++++------ 4 files changed, 294 insertions(+), 81 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index a211ad2484..bdd487253a 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -25,7 +25,7 @@ use small_window::{SmallWindow, SmallWindowType}; use crate::{ render::{Consts, Globals, Renderer}, settings::{ControlSettings, Settings}, - ui::{Ingame, ScaleMode, Ui}, + ui::{Ingame, Ingameable, ScaleMode, Ui}, window::{Event as WinEvent, Key, Window}, GlobalState, }; @@ -287,27 +287,24 @@ impl Hud { }) .enumerate() { - Ingame::from_primitive( - pos + Vec3::new(0.0, 0.0, 3.0), - Text::new(&name) - .font_size(15) - .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)), - ) - .resolution(50.0) - .set(self.ids.name_tags[i], ui_widgets); + 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(self.ids.name_tags[i], ui_widgets); } } // test - Ingame::from_primitive( - [0.0, 25.0, 25.0].into(), - //Rectangle::fill_with([1.0, 1.0], Color::Rgba(0.2, 0.0, 0.4, 1.0)), - Text::new("Squarefection") - .font_size(20) - .color(TEXT_COLOR) - .font_id(self.fonts.opensans), - ) - .resolution(40.0) - .set(self.ids.temp, ui_widgets); + Text::new("Squarefection") + .font_size(20) + .color(TEXT_COLOR) + .font_id(self.fonts.opensans) + .x_y(0.0, 0.0) + .position_ingame([0.0, 25.0, 25.0].into()) + .resolution(40.0) + .set(self.ids.temp, ui_widgets); // Display debug window. if self.show.debug { diff --git a/voxygen/src/ui/event.rs b/voxygen/src/ui/event.rs index 1a6dd709ae..82203a8807 100644 --- a/voxygen/src/ui/event.rs +++ b/voxygen/src/ui/event.rs @@ -6,7 +6,6 @@ pub struct Event(pub Input); impl Event { pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option { 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); diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 2237ce32c1..2ec835d68d 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -12,7 +12,11 @@ mod font_ids; pub use event::Event; pub use graphic::Graphic; pub use scale::ScaleMode; -pub use widgets::{image_slider::ImageSlider, ingame::Ingame, toggle_button::ToggleButton}; +pub use widgets::{ + image_slider::ImageSlider, + ingame::{Ingame, Ingameable}, + toggle_button::ToggleButton, +}; use crate::{ render::{ @@ -294,8 +298,12 @@ impl Ui { // Push new position command self.draw_commands.push(DrawCommand::WorldPos(None)); } - Some((n, res)) => in_world = Some((n - 1, res)), - None => (), + Some((n, res)) => match kind { + // Other types don't need to be drawn in the game + PrimitiveKind::Other(_) => (), + _ => in_world = Some((n - 1, res)), + }, + None => {} } // Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0). @@ -500,11 +508,11 @@ impl Ui { PrimitiveKind::Other(container) => { if container.type_id == std::any::TypeId::of::() { // Retrieve world position - let (pos, res) = container + let parameters = container .state_and_style::() .unwrap() .state - .pos_res(); + .parameters; // Finish current state self.draw_commands.push(match current_state { State::Plain => DrawCommand::plain(start..mesh.vertices().len()), @@ -513,10 +521,10 @@ impl Ui { start = mesh.vertices().len(); // Push new position command self.draw_commands.push(DrawCommand::WorldPos(Some( - renderer.create_consts(&[pos.into()]).unwrap(), + renderer.create_consts(&[parameters.pos.into()]).unwrap(), ))); - in_world = Some((1, res)); + in_world = Some((parameters.num, parameters.res)); p_scale_factor = 1.0; } } diff --git a/voxygen/src/ui/widgets/ingame.rs b/voxygen/src/ui/widgets/ingame.rs index 6b295d4a40..ede764a4cc 100644 --- a/voxygen/src/ui/widgets/ingame.rs +++ b/voxygen/src/ui/widgets/ingame.rs @@ -1,82 +1,232 @@ use conrod_core::{ builder_methods, image, position::Dimension, - widget::{self, button}, - widget_ids, Color, Position, Positionable, Rect, Sizeable, Ui, Widget, WidgetCommon, + 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 -where - W: Widget, -{ +pub struct Ingame { #[conrod(common_builder)] common: widget::CommonBuilder, widget: W, - pos: Vec3, + parameters: IngameParameters, +} + +pub trait Ingameable: Sized { + type Event; + fn prim_count(&self) -> usize; + fn set_ingame(self, ids: Ids, parent_id: Id, ui: &mut UiCell) -> Self::Event; + fn init_ids(mut id_gen: widget::id::Generator) -> Ids; + fn position_ingame(self, pos: Vec3) -> Ingame { + Ingame::new(pos, self) + } +} + +// Note this is not responsible for the positioning +// Only call this directly if using IngameAnchor +pub fn set_ingame(widget: W, parent_id: Id, id: Id, ui: &mut UiCell) -> W::Event { + widget + // should pass focus to the window if these are clicked + // (they are not displayed where conrod thinks they are) + .graphics_for(ui.window) + //.parent(id) // is this needed + .set(id, ui) +} + +pub trait PrimitiveMarker {} +impl PrimitiveMarker for widget::Line {} +impl PrimitiveMarker for widget::Image {} +impl PrimitiveMarker for widget::PointPath {} +impl PrimitiveMarker for widget::Circle {} +impl PrimitiveMarker for widget::Oval {} +impl PrimitiveMarker for widget::Polygon {} +impl PrimitiveMarker for widget::Rectangle {} +impl PrimitiveMarker for widget::Triangles {} +impl<'a> PrimitiveMarker for widget::Text<'a> {} + +impl

Ingameable for P +where + P: Widget + PrimitiveMarker, +{ + type Event = P::Event; + fn prim_count(&self) -> usize { + 1 + } + fn set_ingame(self, ids: Ids, parent_id: Id, ui: &mut UiCell) -> Self::Event { + let id = ids.one().unwrap(); + + set_ingame(self, parent_id, id, ui) + } + fn init_ids(mut id_gen: widget::id::Generator) -> Ids { + Ids::One(id_gen.next()) + } +} + +trait IngameWidget: Ingameable + Widget {} +impl IngameWidget for T where T: Ingameable + Widget {} + +impl Ingameable for (W, E) +where + W: IngameWidget, + E: IngameWidget, +{ + type Event = (::Event, ::Event); + fn prim_count(&self) -> usize { + self.0.prim_count() + self.1.prim_count() + } + fn set_ingame(self, ids: Ids, parent_id: Id, ui: &mut UiCell) -> Self::Event { + let (w1, w2) = self; + let [id1, id2] = ids.two().unwrap(); + ( + set_ingame(w1, parent_id, id1, ui), + set_ingame(w2, parent_id, id2, ui), + ) + } + fn init_ids(mut id_gen: widget::id::Generator) -> Ids { + Ids::Two([id_gen.next(), id_gen.next()]) + } +} +impl Ingameable for (W, E, R) +where + W: IngameWidget, + E: IngameWidget, + R: IngameWidget, +{ + type Event = ( + ::Event, + ::Event, + ::Event, + ); + fn prim_count(&self) -> usize { + self.0.prim_count() + self.1.prim_count() + self.2.prim_count() + } + fn set_ingame(self, ids: Ids, parent_id: Id, ui: &mut UiCell) -> Self::Event { + let (w1, w2, w3) = self; + let ids = ids.three().unwrap(); + ( + set_ingame(w1, parent_id, ids[0], ui), + set_ingame(w2, parent_id, ids[1], ui), + set_ingame(w3, parent_id, ids[2], ui), + ) + } + fn init_ids(mut id_gen: widget::id::Generator) -> Ids { + Ids::Three([id_gen.next(), id_gen.next(), id_gen.next()]) + } +} +impl Ingameable for (W, E, R, T) +where + W: IngameWidget, + E: IngameWidget, + R: IngameWidget, + T: IngameWidget, +{ + type Event = ( + ::Event, + ::Event, + ::Event, + ::Event, + ); + fn prim_count(&self) -> usize { + self.0.prim_count() + self.1.prim_count() + self.2.prim_count() + self.3.prim_count() + } + fn set_ingame(self, ids: Ids, parent_id: Id, ui: &mut UiCell) -> Self::Event { + let (w1, w2, w3, w4) = self; + let ids = ids.four().unwrap(); + ( + set_ingame(w1, parent_id, ids[0], ui), + set_ingame(w2, parent_id, ids[1], ui), + set_ingame(w3, parent_id, ids[2], ui), + set_ingame(w4, parent_id, ids[3], ui), + ) + } + fn init_ids(mut id_gen: widget::id::Generator) -> Ids { + Ids::Four([id_gen.next(), id_gen.next(), id_gen.next(), id_gen.next()]) + } +} + +#[derive(Clone, Copy)] +enum Ids { + None, + One(Id), + Two([Id; 2]), + Three([Id; 3]), + Four([Id; 4]), +} +impl Ids { + fn one(self) -> Option { + match self { + Ids::One(id) => Some(id), + _ => None, + } + } + fn two(self) -> Option<[Id; 2]> { + match self { + Ids::Two(ids) => Some(ids), + _ => None, + } + } + fn three(self) -> Option<[Id; 3]> { + match self { + Ids::Three(ids) => Some(ids), + _ => None, + } + } + fn four(self) -> Option<[Id; 4]> { + match self { + Ids::Four(ids) => Some(ids), + _ => None, + } + } +} + +#[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, // 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? - res: f32, -} - -// TODO: add convenience function to this trait -pub trait Primitive {} -impl Primitive for widget::Line {} -impl Primitive for widget::Image {} -impl Primitive for widget::PointPath {} -impl Primitive for widget::Circle {} -impl Primitive for widget::Oval {} -impl Primitive for widget::Polygon {} -impl Primitive for widget::Rectangle {} -impl Primitive for widget::Triangles {} -impl<'a> Primitive for widget::Text<'a> {} - -widget_ids! { - struct Ids { - prim, - } + pub res: f32, } pub struct State { ids: Ids, - pos: Vec3, - res: f32, -} -impl State { - // retrieve the postion and resolution as a tuple - pub fn pos_res(&self) -> (Vec3, f32) { - (self.pos, self.res) - } + pub parameters: IngameParameters, } pub type Style = (); -impl Ingame { - pub fn from_primitive(pos: Vec3, widget: W) -> Self { +impl Ingame { + pub fn new(pos: Vec3, widget: W) -> Self { Self { common: widget::CommonBuilder::default(), - pos, + parameters: IngameParameters { + num: widget.prim_count(), + pos, + res: 1.0, + }, widget, - res: 1.0, } } builder_methods! { - pub resolution { res = f32 } + pub resolution { parameters.res = f32 } } } -impl Widget for Ingame { +impl Widget for Ingame { type State = State; type Style = Style; - type Event = (); + type Event = W::Event; - fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State { State { - ids: Ids::new(id_gen), - pos: Vec3::default(), - res: 1.0, + ids: W::init_ids(id_gen), + parameters: self.parameters, } } @@ -87,22 +237,17 @@ impl Widget for Ingame { fn update(self, args: widget::UpdateArgs) -> Self::Event { let widget::UpdateArgs { id, state, ui, .. } = args; let Ingame { - widget, pos, res, .. + widget, parameters, .. } = self; // Update pos if it has changed - if state.pos != pos || state.res != res { + if state.parameters != parameters { state.update(|s| { - s.pos = pos; - s.res = res; + s.parameters = parameters; }); } - widget - .graphics_for(ui.window) - .x_y(0.0, 0.0) - .parent(id) // is this needed - .set(state.ids.prim, ui); + widget.set_ingame(state.ids, id, ui) } fn default_x_position(&self, ui: &Ui) -> Position { @@ -118,3 +263,67 @@ impl Widget for Ingame { 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) -> 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 { + ids: Ids::None, + parameters: self.parameters, + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> 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; + }); + } + } +}