Compare commits

...

3 Commits

Author SHA1 Message Date
Mark 1a29130419
Updated todo 2024-01-27 08:09:05 -08:00
Mark 34060d9c77
Reorganized ui manager 2024-01-27 08:08:28 -08:00
Mark f1e0a47f25
Minor edit 2024-01-27 08:07:33 -08:00
14 changed files with 422 additions and 74 deletions

View File

@ -1,5 +1,9 @@
# Specific projects # Specific projects
## Now:
- renderer scenes
- outfitter
## Small jobs ## Small jobs
- 🌟 Better planet desc formatting - 🌟 Better planet desc formatting
- Procedural suns - Procedural suns

View File

@ -16,7 +16,7 @@ pub struct InputStatus {
key_guns: bool, key_guns: bool,
key_leftclick: bool, key_leftclick: bool,
// One-shot keys (audomatically released at the end of each frame) // One-shot keys (automatically released at the end of each frame)
key_land: bool, key_land: bool,
v_scroll: f32, v_scroll: f32,
} }
@ -137,6 +137,11 @@ impl InputStatus {
self.key_guns self.key_guns
} }
/// Is the player pressing the left mouse button?
pub fn pressed_leftclick(&self) -> bool {
self.key_leftclick
}
/// Has the player pressed the "land" key? /// Has the player pressed the "land" key?
/// (One-shot, reset to false at the start of each frame) /// (One-shot, reset to false at the start of each frame)
pub fn pressed_land(&self) -> bool { pub fn pressed_land(&self) -> bool {

View File

@ -111,7 +111,7 @@ impl<'a> super::GPUState {
0..self.state.get_radialbar_counter(), 0..self.state.get_radialbar_counter(),
); );
let textareas = self.ui.get_textareas_flying(input, &self.state); let textareas = self.ui.get_textareas(input, &self.state);
self.state self.state
.text_renderer .text_renderer
.prepare( .prepare(
@ -219,7 +219,7 @@ impl<'a> super::GPUState {
0..self.state.get_radialbar_counter(), 0..self.state.get_radialbar_counter(),
); );
let textareas = self.ui.get_textareas_landed(input, &self.state); let textareas = self.ui.get_textareas(input, &self.state);
self.state self.state
.text_renderer .text_renderer
.prepare( .prepare(
@ -281,6 +281,7 @@ impl<'a> super::GPUState {
); );
self.state.frame_reset(); self.state.frame_reset();
self.ui.update_state(&input, &mut self.state);
match input match input
.phys_img .phys_img

View File

@ -1,29 +1,121 @@
use std::fmt::Debug;
use galactica_content::Content; use galactica_content::Content;
use galactica_system::{data::ShipState, phys::PhysSimShipHandle}; use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
use glyphon::TextArea; use glyphon::TextArea;
use log::info;
use super::{fpsindicator::FpsIndicator, planet::Planet, radar::Radar, status::Status}; use super::scenes::{FlyingScene, LandedScene, OutfitterScene};
use crate::{RenderInput, RenderState}; use crate::{RenderInput, RenderState};
/// Output from a ui scene step
pub struct UiSceneStepResult {
/// If Some, switch to this scene
pub new_scene: Option<UiScenes>,
}
pub trait UiScene<'this>
where
Self: 'this,
{
/// Draw this scene
fn draw(&mut self, input: &RenderInput, state: &mut RenderState);
/// Update this scene's state for this frame.
/// Handles clicks, keys, etc.
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult;
fn get_textareas(
&'this self,
v: &mut Vec<TextArea<'this>>,
input: &RenderInput,
state: &RenderState,
);
}
pub(super) enum UiScenes {
Landed(LandedScene),
Flying(FlyingScene),
Outfitter(OutfitterScene),
}
impl Debug for UiScenes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Flying(_) => write!(f, "UiScenes::Flying"),
Self::Landed(_) => write!(f, "UiScenes::Landed"),
Self::Outfitter(_) => write!(f, "UiScenes::Outfitter"),
}
}
}
impl UiScenes {
fn is_flying(&self) -> bool {
match self {
Self::Flying(_) => true,
_ => false,
}
}
fn is_landed(&self) -> bool {
match self {
Self::Landed(_) => true,
_ => false,
}
}
fn is_outfitter(&self) -> bool {
match self {
Self::Outfitter(_) => true,
_ => false,
}
}
}
impl<'a> UiScene<'a> for UiScenes {
fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
match self {
Self::Flying(s) => s.draw(input, state),
Self::Landed(s) => s.draw(input, state),
Self::Outfitter(s) => s.draw(input, state),
}
}
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
match self {
Self::Flying(s) => s.step(input, state),
Self::Landed(s) => s.step(input, state),
Self::Outfitter(s) => s.step(input, state),
}
}
fn get_textareas(
&'a self,
v: &mut Vec<TextArea<'a>>,
input: &RenderInput,
state: &RenderState,
) {
match self {
Self::Flying(s) => s.get_textareas(v, input, state),
Self::Landed(s) => s.get_textareas(v, input, state),
Self::Outfitter(s) => s.get_textareas(v, input, state),
}
}
}
pub struct UiManager { pub struct UiManager {
radar: Radar, current_scene: UiScenes,
status: Status,
fps: FpsIndicator,
planet: Planet,
} }
impl UiManager { impl UiManager {
pub fn new(ct: &Content, state: &mut RenderState) -> Self { pub fn new(ct: &Content, state: &mut RenderState) -> Self {
Self { Self {
planet: Planet::new(ct, state), current_scene: UiScenes::Flying(FlyingScene::new(ct, state)),
radar: Radar::new(),
status: Status::new(),
fps: FpsIndicator::new(state),
} }
} }
/// Draw all ui elements // TODO: remove this.
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { pub fn update_state(&mut self, input: &RenderInput, state: &mut RenderState) {
let ship_handle = input.player.ship.unwrap(); let ship_handle = input.player.ship.unwrap();
let ship = &input let ship = &input
.phys_img .phys_img
@ -31,41 +123,44 @@ impl UiManager {
.unwrap() .unwrap()
.ship; .ship;
self.fps.update(input, state);
match ship.get_data().get_state() { match ship.get_data().get_state() {
ShipState::Collapsing ShipState::Collapsing
| ShipState::Dead | ShipState::Dead
| ShipState::Flying { .. } | ShipState::Flying { .. }
| ShipState::Landing { .. } | ShipState::Landing { .. }
| ShipState::UnLanding { .. } => { | ShipState::UnLanding { .. } => {
self.radar.draw(input, state); if !self.current_scene.is_flying() {
self.status.draw(input, state); self.current_scene = UiScenes::Flying(FlyingScene::new(input.ct, state));
}
} }
ShipState::Landed { .. } => { ShipState::Landed { .. } => {
self.planet.draw(input, state); if !self.current_scene.is_landed() && !self.current_scene.is_outfitter() {
self.current_scene = UiScenes::Landed(LandedScene::new(input.ct, state))
}
} }
} }
} }
/// Textareas to show while player is flying /// Draw all ui elements
pub fn get_textareas_flying( pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
&self, loop {
_input: &RenderInput, let r = self.current_scene.step(input, state);
_state: &RenderState, if let Some(new_state) = r.new_scene {
) -> Vec<TextArea> { info!("switching to {:?}", new_state);
let mut v = Vec::with_capacity(5); self.current_scene = new_state;
v.push(self.fps.get_textarea()); } else {
break;
}
}
return v; self.current_scene.draw(input, state);
} }
/// Textareas to show when player is landed /// Textareas to show while player is flying
pub fn get_textareas_landed(&self, input: &RenderInput, state: &RenderState) -> Vec<TextArea> { pub fn get_textareas(&self, input: &RenderInput, state: &RenderState) -> Vec<TextArea> {
let mut v = Vec::with_capacity(5); let mut v = Vec::with_capacity(5);
v.extend(self.planet.get_textarea(input, state)); self.current_scene.get_textareas(&mut v, input, state);
return v; return v;
} }
} }

View File

@ -1,8 +1,5 @@
mod fpsindicator;
mod manager; mod manager;
mod planet; mod scenes;
mod radar;
mod status;
mod util; mod util;
pub use manager::UiManager; pub use manager::UiManager;

View File

@ -0,0 +1,6 @@
mod fpsindicator;
mod radar;
mod scene;
mod status;
pub use scene::FlyingScene;

View File

@ -0,0 +1,45 @@
use galactica_content::Content;
use glyphon::TextArea;
use super::{fpsindicator::FpsIndicator, radar::Radar, status::Status};
use crate::{
ui::manager::{UiScene, UiSceneStepResult},
RenderInput, RenderState,
};
pub struct FlyingScene {
radar: Radar,
status: Status,
fps: FpsIndicator,
}
impl FlyingScene {
pub fn new(_ct: &Content, state: &mut RenderState) -> Self {
Self {
radar: Radar::new(),
status: Status::new(),
fps: FpsIndicator::new(state),
}
}
}
impl<'this> UiScene<'this> for FlyingScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
self.fps.update(input, state);
return UiSceneStepResult { new_scene: None };
}
fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
self.radar.draw(input, state);
self.status.draw(input, state);
}
fn get_textareas(
&'this self,
v: &mut Vec<TextArea<'this>>,
_input: &RenderInput,
_state: &RenderState,
) {
v.push(self.fps.get_textarea());
}
}

View File

@ -3,13 +3,20 @@ use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight}; use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight};
use nalgebra::{Point2, Vector2}; use nalgebra::{Point2, Vector2};
use super::util::{SpriteRect, UiSprite, UiTextArea}; use crate::{
use crate::{RenderInput, RenderState}; ui::{
manager::{UiScene, UiSceneStepResult, UiScenes},
util::{SpriteRect, UiSprite, UiTextArea},
},
RenderInput, RenderState,
};
pub(super) struct Planet { use super::OutfitterScene;
pub struct LandedScene {
// UI elements // UI elements
planet_desc: UiTextArea, description: UiTextArea,
planet_name: UiTextArea, title: UiTextArea,
frame: UiSprite, frame: UiSprite,
landscape: UiSprite, landscape: UiSprite,
button: UiSprite, button: UiSprite,
@ -17,11 +24,13 @@ pub(super) struct Planet {
/// What object we're displaying currently. /// What object we're displaying currently.
/// Whenever this changes, we need to reflow text. /// Whenever this changes, we need to reflow text.
current_object: Option<SystemObjectHandle>, current_object: Option<SystemObjectHandle>,
/// True if we've caught a left click event.
/// Used for edge detection.
leftclick_down: bool,
} }
// TODO: animate in/out impl LandedScene {
impl Planet {
pub fn new(ct: &Content, state: &mut RenderState) -> Self { pub fn new(ct: &Content, state: &mut RenderState) -> Self {
let frame = UiSprite::from(ct, &ct.get_ui().landed_frame); let frame = UiSprite::from(ct, &ct.get_ui().landed_frame);
let button = UiSprite::from(ct, &ct.get_ui().landed_button); let button = UiSprite::from(ct, &ct.get_ui().landed_button);
@ -34,8 +43,9 @@ impl Planet {
let s = Self { let s = Self {
// height of element in logical pixels // height of element in logical pixels
current_object: None, current_object: None,
leftclick_down: false,
planet_desc: UiTextArea::new( description: UiTextArea::new(
ct, ct,
state, state,
frame.get_sprite(), frame.get_sprite(),
@ -50,7 +60,7 @@ impl Planet {
Align::Left, Align::Left,
), ),
planet_name: UiTextArea::new( title: UiTextArea::new(
ct, ct,
state, state,
frame.get_sprite(), frame.get_sprite(),
@ -72,11 +82,9 @@ impl Planet {
return s; return s;
} }
}
impl Planet {
fn reflow(&mut self, planet: &SystemObject, state: &mut RenderState) { fn reflow(&mut self, planet: &SystemObject, state: &mut RenderState) {
self.planet_desc.set_text( self.description.set_text(
state, state,
&planet.desc, &planet.desc,
Attrs::new() Attrs::new()
@ -84,7 +92,7 @@ impl Planet {
.family(glyphon::Family::SansSerif), .family(glyphon::Family::SansSerif),
); );
self.planet_name.set_text( self.title.set_text(
state, state,
&planet.name, &planet.name,
Attrs::new() Attrs::new()
@ -93,8 +101,29 @@ impl Planet {
); );
self.current_object = Some(planet.handle); self.current_object = Some(planet.handle);
} }
}
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { impl<'this> UiScene<'this> for LandedScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
let frame_rect = Some(self.frame.get_rect(input));
self.button.step(input, state, frame_rect);
self.landscape.step(input, state, frame_rect);
self.frame.step(input, state, None);
let mut new_scene = None;
if input.player.input.pressed_leftclick() && !self.leftclick_down {
self.leftclick_down = true;
if self.button.contains_mouse(input, state, frame_rect) {
new_scene = Some(UiScenes::Outfitter(OutfitterScene::new(input.ct, state)));
}
} else if !input.player.input.pressed_leftclick() {
self.leftclick_down = false;
}
return UiSceneStepResult { new_scene };
}
fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
// Get required data // Get required data
let ship_handle = input.player.ship.unwrap(); let ship_handle = input.player.ship.unwrap();
let ship_data = &input let ship_data = &input
@ -117,24 +146,20 @@ impl Planet {
self.reflow(planet, state); self.reflow(planet, state);
} }
self.button
.step(input, state, Some(self.frame.get_rect(input)));
self.landscape
.step(input, state, Some(self.frame.get_rect(input)));
self.frame.step(input, state, None);
// Draw elements // Draw elements
self.button let frame_rect = Some(self.frame.get_rect(input));
.push_to_buffer(input, state, Some(self.frame.get_rect(input))); self.button.push_to_buffer(input, state, frame_rect);
self.landscape self.landscape.push_to_buffer(input, state, frame_rect);
.push_to_buffer(input, state, Some(self.frame.get_rect(input)));
self.frame.push_to_buffer(input, state, None); self.frame.push_to_buffer(input, state, None);
} }
pub fn get_textarea(&self, input: &RenderInput, state: &RenderState) -> [TextArea; 2] { fn get_textareas(
[ &'this self,
self.planet_desc.get_textarea(input, state), v: &mut Vec<TextArea<'this>>,
self.planet_name.get_textarea(input, state), input: &RenderInput,
] state: &RenderState,
) {
v.push(self.description.get_textarea(input, state));
v.push(self.title.get_textarea(input, state));
} }
} }

View File

@ -0,0 +1,7 @@
mod flying;
mod landed;
mod outfitter;
pub use flying::FlyingScene;
pub use landed::LandedScene;
pub use outfitter::OutfitterScene;

View File

@ -0,0 +1,145 @@
use galactica_content::{Content, SystemObject, SystemObjectHandle};
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight};
use nalgebra::{Point2, Vector2};
use crate::{
ui::{
manager::{UiScene, UiSceneStepResult, UiScenes},
util::{SpriteRect, UiSprite, UiTextArea},
},
RenderInput, RenderState,
};
use super::LandedScene;
pub struct OutfitterScene {
// UI elements
description: UiTextArea,
landscape: UiSprite,
button: UiSprite,
/// What object we're displaying currently.
/// Whenever this changes, we need to reflow text.
current_object: Option<SystemObjectHandle>,
/// True if we've caught a left click event.
/// Used for edge detection.
leftclick_down: bool,
}
impl OutfitterScene {
pub fn new(ct: &Content, state: &mut RenderState) -> Self {
let button = UiSprite::new(
ct,
ct.get_sprite_handle("ui::button"),
None,
SpriteRect {
pos: Point2::new(0.0, 0.0),
dim: Vector2::new(200.0, 200.0),
},
None,
None,
);
let landscape = UiSprite::from_with_sprite(
ct,
&ct.get_ui().landed_landscape,
ct.get_sprite_handle("ui::landscape::test"),
);
let s = Self {
// height of element in logical pixels
current_object: None,
leftclick_down: false,
description: UiTextArea::new(
ct,
state,
button.get_sprite(),
Point2::new(0.0, 0.0),
800.0,
SpriteRect {
pos: Point2::new(25.831, 284.883) / 512.0,
dim: Vector2::new(433.140, 97.220) / 512.0,
},
Metrics::new(16.0, 18.0),
Color::rgb(255, 255, 255),
Align::Left,
),
landscape,
button,
};
return s;
}
fn reflow(&mut self, planet: &SystemObject, state: &mut RenderState) {
self.description.set_text(
state,
&planet.desc,
Attrs::new()
.weight(Weight::NORMAL)
.family(glyphon::Family::SansSerif),
);
self.current_object = Some(planet.handle);
}
}
impl<'this> UiScene<'this> for OutfitterScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
self.button.step(input, state, None);
self.landscape.step(input, state, None);
let mut new_scene = None;
if input.player.input.pressed_leftclick() && !self.leftclick_down {
self.leftclick_down = true;
if self.button.contains_mouse(input, state, None) {
new_scene = Some(UiScenes::Landed(LandedScene::new(input.ct, state)));
}
} else if !input.player.input.pressed_leftclick() {
self.leftclick_down = false;
}
return UiSceneStepResult { new_scene };
}
fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
// Get required data
let ship_handle = input.player.ship.unwrap();
let ship_data = &input
.phys_img
.get_ship(&PhysSimShipHandle(ship_handle))
.unwrap()
.ship;
let planet_handle = match ship_data.get_data().get_state() {
ShipState::Landed { target } => *target,
_ => unreachable!("tried to draw planet interface while not landed!"),
};
let planet = input.ct.get_system_object(planet_handle);
// Reconfigure for new planet if necessary
if self
.current_object
.map(|x| x != planet_handle)
.unwrap_or(true)
{
self.reflow(planet, state);
}
// Draw elements
self.button.push_to_buffer(input, state, None);
self.landscape.push_to_buffer(input, state, None);
}
fn get_textareas(
&'this self,
v: &mut Vec<TextArea<'this>>,
input: &RenderInput,
state: &RenderState,
) {
v.push(self.description.get_textarea(input, state));
}
}

View File

@ -17,6 +17,24 @@ pub struct UiSprite {
} }
impl UiSprite { impl UiSprite {
pub fn new(
ct: &Content,
sprite: SpriteHandle,
mask: Option<SpriteHandle>,
rect: SpriteRect,
on_mouse_enter: Option<SectionEdge>,
on_mouse_leave: Option<SectionEdge>,
) -> Self {
return Self {
anim: SpriteAutomaton::new(ct, sprite),
mask,
rect,
has_mouse: false,
on_mouse_enter,
on_mouse_leave,
};
}
pub fn from(ct: &Content, ui: &UiSpriteConfig) -> Self { pub fn from(ct: &Content, ui: &UiSpriteConfig) -> Self {
if ui.sprite.is_none() { if ui.sprite.is_none() {
unreachable!("called `UiSprite.from()` on a UiSprite with a None sprite!") unreachable!("called `UiSprite.from()` on a UiSprite with a None sprite!")
@ -26,17 +44,17 @@ impl UiSprite {
} }
pub fn from_with_sprite(ct: &Content, ui: &UiSpriteConfig, sprite: SpriteHandle) -> Self { pub fn from_with_sprite(ct: &Content, ui: &UiSpriteConfig, sprite: SpriteHandle) -> Self {
return Self { Self::new(
anim: SpriteAutomaton::new(ct, sprite), ct,
mask: ui.mask, sprite,
rect: SpriteRect { ui.mask,
SpriteRect {
pos: *ui.rect.get_pos(), pos: *ui.rect.get_pos(),
dim: *ui.rect.get_dim(), dim: *ui.rect.get_dim(),
}, },
has_mouse: false, ui.on_mouse_enter,
on_mouse_enter: ui.on_mouse_enter, ui.on_mouse_leave,
on_mouse_leave: ui.on_mouse_leave, )
};
} }
pub fn step(&mut self, input: &RenderInput, state: &RenderState, parent: Option<SpriteRect>) { pub fn step(&mut self, input: &RenderInput, state: &RenderState, parent: Option<SpriteRect>) {