Reorganized ui manager

master
Mark 2024-01-27 08:08:28 -08:00
parent f1e0a47f25
commit 34060d9c77
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
12 changed files with 412 additions and 73 deletions

View File

@ -111,7 +111,7 @@ impl<'a> super::GPUState {
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
.text_renderer
.prepare(
@ -219,7 +219,7 @@ impl<'a> super::GPUState {
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
.text_renderer
.prepare(
@ -281,6 +281,7 @@ impl<'a> super::GPUState {
);
self.state.frame_reset();
self.ui.update_state(&input, &mut self.state);
match input
.phys_img

View File

@ -1,29 +1,121 @@
use std::fmt::Debug;
use galactica_content::Content;
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
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};
/// 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 {
radar: Radar,
status: Status,
fps: FpsIndicator,
planet: Planet,
current_scene: UiScenes,
}
impl UiManager {
pub fn new(ct: &Content, state: &mut RenderState) -> Self {
Self {
planet: Planet::new(ct, state),
radar: Radar::new(),
status: Status::new(),
fps: FpsIndicator::new(state),
current_scene: UiScenes::Flying(FlyingScene::new(ct, state)),
}
}
/// Draw all ui elements
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
// TODO: remove this.
pub fn update_state(&mut self, input: &RenderInput, state: &mut RenderState) {
let ship_handle = input.player.ship.unwrap();
let ship = &input
.phys_img
@ -31,41 +123,44 @@ impl UiManager {
.unwrap()
.ship;
self.fps.update(input, state);
match ship.get_data().get_state() {
ShipState::Collapsing
| ShipState::Dead
| ShipState::Flying { .. }
| ShipState::Landing { .. }
| ShipState::UnLanding { .. } => {
self.radar.draw(input, state);
self.status.draw(input, state);
if !self.current_scene.is_flying() {
self.current_scene = UiScenes::Flying(FlyingScene::new(input.ct, state));
}
}
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
pub fn get_textareas_flying(
&self,
_input: &RenderInput,
_state: &RenderState,
) -> Vec<TextArea> {
let mut v = Vec::with_capacity(5);
v.push(self.fps.get_textarea());
/// Draw all ui elements
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
loop {
let r = self.current_scene.step(input, state);
if let Some(new_state) = r.new_scene {
info!("switching to {:?}", new_state);
self.current_scene = new_state;
} else {
break;
}
}
return v;
self.current_scene.draw(input, state);
}
/// Textareas to show when player is landed
pub fn get_textareas_landed(&self, input: &RenderInput, state: &RenderState) -> Vec<TextArea> {
/// Textareas to show while player is flying
pub fn get_textareas(&self, input: &RenderInput, state: &RenderState) -> Vec<TextArea> {
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;
}
}

View File

@ -1,8 +1,5 @@
mod fpsindicator;
mod manager;
mod planet;
mod radar;
mod status;
mod scenes;
mod util;
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 nalgebra::{Point2, Vector2};
use super::util::{SpriteRect, UiSprite, UiTextArea};
use crate::{RenderInput, RenderState};
use crate::{
ui::{
manager::{UiScene, UiSceneStepResult, UiScenes},
util::{SpriteRect, UiSprite, UiTextArea},
},
RenderInput, RenderState,
};
pub(super) struct Planet {
use super::OutfitterScene;
pub struct LandedScene {
// UI elements
planet_desc: UiTextArea,
planet_name: UiTextArea,
description: UiTextArea,
title: UiTextArea,
frame: UiSprite,
landscape: UiSprite,
button: UiSprite,
@ -17,11 +24,13 @@ pub(super) struct Planet {
/// 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,
}
// TODO: animate in/out
impl Planet {
impl LandedScene {
pub fn new(ct: &Content, state: &mut RenderState) -> Self {
let frame = UiSprite::from(ct, &ct.get_ui().landed_frame);
let button = UiSprite::from(ct, &ct.get_ui().landed_button);
@ -34,8 +43,9 @@ impl Planet {
let s = Self {
// height of element in logical pixels
current_object: None,
leftclick_down: false,
planet_desc: UiTextArea::new(
description: UiTextArea::new(
ct,
state,
frame.get_sprite(),
@ -50,7 +60,7 @@ impl Planet {
Align::Left,
),
planet_name: UiTextArea::new(
title: UiTextArea::new(
ct,
state,
frame.get_sprite(),
@ -72,11 +82,9 @@ impl Planet {
return s;
}
}
impl Planet {
fn reflow(&mut self, planet: &SystemObject, state: &mut RenderState) {
self.planet_desc.set_text(
self.description.set_text(
state,
&planet.desc,
Attrs::new()
@ -84,7 +92,7 @@ impl Planet {
.family(glyphon::Family::SansSerif),
);
self.planet_name.set_text(
self.title.set_text(
state,
&planet.name,
Attrs::new()
@ -93,8 +101,29 @@ impl Planet {
);
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
let ship_handle = input.player.ship.unwrap();
let ship_data = &input
@ -117,24 +146,20 @@ impl Planet {
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
self.button
.push_to_buffer(input, state, Some(self.frame.get_rect(input)));
self.landscape
.push_to_buffer(input, state, Some(self.frame.get_rect(input)));
let frame_rect = Some(self.frame.get_rect(input));
self.button.push_to_buffer(input, state, frame_rect);
self.landscape.push_to_buffer(input, state, frame_rect);
self.frame.push_to_buffer(input, state, None);
}
pub fn get_textarea(&self, input: &RenderInput, state: &RenderState) -> [TextArea; 2] {
[
self.planet_desc.get_textarea(input, state),
self.planet_name.get_textarea(input, state),
]
fn get_textareas(
&'this self,
v: &mut Vec<TextArea<'this>>,
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 {
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 {
if ui.sprite.is_none() {
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 {
return Self {
anim: SpriteAutomaton::new(ct, sprite),
mask: ui.mask,
rect: SpriteRect {
Self::new(
ct,
sprite,
ui.mask,
SpriteRect {
pos: *ui.rect.get_pos(),
dim: *ui.rect.get_dim(),
},
has_mouse: false,
on_mouse_enter: ui.on_mouse_enter,
on_mouse_leave: ui.on_mouse_leave,
};
ui.on_mouse_enter,
ui.on_mouse_leave,
)
}
pub fn step(&mut self, input: &RenderInput, state: &RenderState, parent: Option<SpriteRect>) {