Compare commits
No commits in common. "5dfbb04dc5728a7970e78ee9121b4a8c7115ca5b" and "b5cc0b555e750a027e2ace0b5b875393b6b79466" have entirely different histories.
5dfbb04dc5
...
b5cc0b555e
12
TODO.md
12
TODO.md
|
@ -2,18 +2,10 @@
|
||||||
|
|
||||||
## Now:
|
## Now:
|
||||||
- outfitter
|
- outfitter
|
||||||
- Clean up scripting & errors
|
|
||||||
- Clean up sprite content (and content in general)
|
|
||||||
- Arbitrary scene names
|
|
||||||
- Text style, color, formatting
|
|
||||||
- Flying UI
|
|
||||||
- Mouse colliders
|
|
||||||
- Clean renderscene/UI scene/change scene on land
|
|
||||||
- UI captures input?
|
|
||||||
- Scriptable renderscene: script decides which parts to show
|
|
||||||
|
|
||||||
|
|
||||||
## Small jobs
|
## Small jobs
|
||||||
|
- Clean up scripting & errors
|
||||||
|
- Clean up sprite content (and content in general)
|
||||||
- Fix window resizing
|
- Fix window resizing
|
||||||
- Debug: show mouse position
|
- Debug: show mouse position
|
||||||
- 🌟 Better planet desc formatting
|
- 🌟 Better planet desc formatting
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit dc5962ffb96f6d4c7fc271a1b132865b280b528c
|
Subproject commit e5898796144c7590c3cf18e14867a5ec47637fd4
|
|
@ -39,6 +39,4 @@ zoom_max = 2000.0
|
||||||
|
|
||||||
# TODO: move to user config file
|
# TODO: move to user config file
|
||||||
ui_scale = 2
|
ui_scale = 2
|
||||||
ui_landed_scene = "ui/landed.rhai"
|
ui_landed_scene = "landed.rhai"
|
||||||
ui_flying_scene = "ui/flying.rhai"
|
|
||||||
ui_outfitter_scene = "ui/outfitter.rhai"
|
|
||||||
|
|
|
@ -60,12 +60,8 @@ fn hover(element, hover_state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn click(element, click_state) {
|
//fn click(scene, name) {
|
||||||
if !click_state {
|
// if name = "button" {
|
||||||
return SceneAction::None;
|
// return SceneAction::Outfitter();
|
||||||
}
|
// }
|
||||||
|
//}
|
||||||
if element.has_name("button") {
|
|
||||||
return SceneAction::SceneOutfitter;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -100,12 +100,6 @@ file = "ui/outfitter-box.png"
|
||||||
[sprite."ui::shopsepbar"]
|
[sprite."ui::shopsepbar"]
|
||||||
file = "ui/shop-sep-bar.png"
|
file = "ui/shop-sep-bar.png"
|
||||||
|
|
||||||
[sprite."ui::outfitter-ship-bg"]
|
|
||||||
file = "ui/outfitter-ship-bg.png"
|
|
||||||
|
|
||||||
[sprite."ui::outfitter-outfit-bg"]
|
|
||||||
file = "ui/outfitter-outfit-bg.png"
|
|
||||||
|
|
||||||
[sprite."icon::blaster"]
|
[sprite."icon::blaster"]
|
||||||
file = "icon/blaster.png"
|
file = "icon/blaster.png"
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
fn init(state) { return []; }
|
|
||||||
fn hover(element, hover_state) {}
|
|
||||||
fn click(element, click_state) {}
|
|
|
@ -1,187 +0,0 @@
|
||||||
|
|
||||||
fn init(state) {
|
|
||||||
let se_box = SpriteBuilder(
|
|
||||||
"se_box",
|
|
||||||
"ui::outfitterbox",
|
|
||||||
Rect(
|
|
||||||
-1.0, -1.0, 202.345, 133.409,
|
|
||||||
SpriteAnchor::SouthWest,
|
|
||||||
SpriteAnchor::SouthWest
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let exit_text = TextBoxBuilder(
|
|
||||||
"exit_text",
|
|
||||||
10.0, 10.0, TextBoxFont::Serif, TextBoxJustify::Center,
|
|
||||||
Rect(
|
|
||||||
122.71, 48.0, 51.0, 12.0,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
SpriteAnchor::SouthWest
|
|
||||||
)
|
|
||||||
);
|
|
||||||
exit_text.set_text(state.planet_name);
|
|
||||||
|
|
||||||
let exit_button = SpriteBuilder(
|
|
||||||
"exit_button",
|
|
||||||
"ui::button",
|
|
||||||
Rect(
|
|
||||||
113.35, 52.0, 69.8, 18.924,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
SpriteAnchor::SouthWest
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let ship_bg = SpriteBuilder(
|
|
||||||
"ship_bg",
|
|
||||||
"ui::outfitter-ship-bg",
|
|
||||||
Rect(
|
|
||||||
16.0, -16.0, 190.0, 353.0,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
SpriteAnchor::NorthWest
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let ship_thumb = SpriteBuilder(
|
|
||||||
"ship_thumb",
|
|
||||||
"icon::gypsum",
|
|
||||||
Rect(
|
|
||||||
111.0, -95.45, 90.0, 90.0,
|
|
||||||
SpriteAnchor::Center,
|
|
||||||
SpriteAnchor::NorthWest
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let ship_name = TextBoxBuilder(
|
|
||||||
"ship_name",
|
|
||||||
10.0, 10.0, TextBoxFont::Serif, TextBoxJustify::Center,
|
|
||||||
Rect(
|
|
||||||
111.0, -167.27, 145.0, 10.0,
|
|
||||||
SpriteAnchor::Center,
|
|
||||||
SpriteAnchor::NorthWest
|
|
||||||
)
|
|
||||||
);
|
|
||||||
ship_name.set_text(state.planet_name);
|
|
||||||
|
|
||||||
let ship_type = TextBoxBuilder(
|
|
||||||
"ship_type",
|
|
||||||
7.0, 8.5, TextBoxFont::SansSerif, TextBoxJustify::Center,
|
|
||||||
Rect(
|
|
||||||
111.0, -178.0, 145.0, 8.5,
|
|
||||||
SpriteAnchor::Center,
|
|
||||||
SpriteAnchor::NorthWest
|
|
||||||
)
|
|
||||||
);
|
|
||||||
ship_type.set_text(state.planet_name);
|
|
||||||
|
|
||||||
let ship_stats = TextBoxBuilder(
|
|
||||||
"ship_stats",
|
|
||||||
7.0, 8.5, TextBoxFont::Monospace, TextBoxJustify::Left,
|
|
||||||
Rect(
|
|
||||||
38.526, -192.332, 144.948, 154.5,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
ship_stats.set_text(state.planet_name);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let outfit_bg = SpriteBuilder(
|
|
||||||
"outfit_bg",
|
|
||||||
"ui::outfitter-outfit-bg",
|
|
||||||
Rect(
|
|
||||||
-16.0, -16.0, 300.0, 480.0,
|
|
||||||
SpriteAnchor::NorthEast,
|
|
||||||
SpriteAnchor::NorthEast
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let outfit_thumb = SpriteBuilder(
|
|
||||||
"outfit_thumb",
|
|
||||||
"icon::engine",
|
|
||||||
Rect(
|
|
||||||
-166.0, -109.0, 90.0, 90.0,
|
|
||||||
SpriteAnchor::Center,
|
|
||||||
SpriteAnchor::NorthEast
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let outfit_name = TextBoxBuilder(
|
|
||||||
"outfit_name",
|
|
||||||
16.0, 16.0, TextBoxFont::Serif, TextBoxJustify::Left,
|
|
||||||
Rect(
|
|
||||||
-312.0, -20.0, 200.0, 16.0,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
SpriteAnchor::NorthEast,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
outfit_name.set_text(state.planet_name);
|
|
||||||
|
|
||||||
let outfit_desc = TextBoxBuilder(
|
|
||||||
"outfit_desc",
|
|
||||||
7.0, 8.5, TextBoxFont::SansSerif, TextBoxJustify::Left,
|
|
||||||
Rect(
|
|
||||||
-166.0, -219.0, 260.0, 78.0,
|
|
||||||
SpriteAnchor::Center,
|
|
||||||
SpriteAnchor::NorthEast,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
outfit_desc.set_text(state.planet_name);
|
|
||||||
|
|
||||||
let outfit_stats = TextBoxBuilder(
|
|
||||||
"outfit_stats",
|
|
||||||
7.0, 8.5, TextBoxFont::Monospace, TextBoxJustify::Left,
|
|
||||||
Rect(
|
|
||||||
-295.0, -271.0, 164.0, 216.0,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
SpriteAnchor::NorthEast,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
outfit_stats.set_text(state.planet_name);
|
|
||||||
|
|
||||||
return [
|
|
||||||
ship_bg,
|
|
||||||
ship_thumb,
|
|
||||||
ship_name,
|
|
||||||
ship_type,
|
|
||||||
ship_stats,
|
|
||||||
|
|
||||||
outfit_bg,
|
|
||||||
outfit_thumb,
|
|
||||||
outfit_name,
|
|
||||||
outfit_desc,
|
|
||||||
outfit_stats,
|
|
||||||
|
|
||||||
se_box,
|
|
||||||
exit_button,
|
|
||||||
exit_text
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hover(element, hover_state) {
|
|
||||||
if element.has_name("exit_button") {
|
|
||||||
if hover_state {
|
|
||||||
element.take_edge("on:top", 0.1);
|
|
||||||
} else {
|
|
||||||
element.take_edge("off:top", 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn click(element, click_state) {
|
|
||||||
if !click_state {
|
|
||||||
return SceneAction::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if element.has_name("exit_button") {
|
|
||||||
return SceneAction::SceneLanded;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,9 +20,7 @@ pub(crate) mod syntax {
|
||||||
pub zoom_min: f32,
|
pub zoom_min: f32,
|
||||||
pub zoom_max: f32,
|
pub zoom_max: f32,
|
||||||
pub ui_scale: f32,
|
pub ui_scale: f32,
|
||||||
pub ui_flying_scene: PathBuf,
|
|
||||||
pub ui_landed_scene: PathBuf,
|
pub ui_landed_scene: PathBuf,
|
||||||
pub ui_outfitter_scene: PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -63,14 +61,6 @@ pub(crate) mod syntax {
|
||||||
.compile_file(content_root.join(self.ui_landed_scene))
|
.compile_file(content_root.join(self.ui_landed_scene))
|
||||||
.with_context(|| format!("while loading `landed` scene"))
|
.with_context(|| format!("while loading `landed` scene"))
|
||||||
.with_context(|| format!("while loading config"))?;
|
.with_context(|| format!("while loading config"))?;
|
||||||
let ui_outfitter_scene = engine
|
|
||||||
.compile_file(content_root.join(self.ui_outfitter_scene))
|
|
||||||
.with_context(|| format!("while loading `outfitter` scene"))
|
|
||||||
.with_context(|| format!("while loading config"))?;
|
|
||||||
let ui_flying_scene = engine
|
|
||||||
.compile_file(content_root.join(self.ui_flying_scene))
|
|
||||||
.with_context(|| format!("while loading `flying` scene"))
|
|
||||||
.with_context(|| format!("while loading config"))?;
|
|
||||||
|
|
||||||
return Ok(super::Config {
|
return Ok(super::Config {
|
||||||
sprite_root: asset_root.join(self.sprite_root),
|
sprite_root: asset_root.join(self.sprite_root),
|
||||||
|
@ -99,8 +89,6 @@ pub(crate) mod syntax {
|
||||||
zoom_min: self.zoom_min,
|
zoom_min: self.zoom_min,
|
||||||
ui_scale: self.ui_scale,
|
ui_scale: self.ui_scale,
|
||||||
ui_landed_scene,
|
ui_landed_scene,
|
||||||
ui_flying_scene,
|
|
||||||
ui_outfitter_scene,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,12 +175,6 @@ pub struct Config {
|
||||||
/// Ui scale factor
|
/// Ui scale factor
|
||||||
pub ui_scale: f32,
|
pub ui_scale: f32,
|
||||||
|
|
||||||
/// Ui landed scene script
|
/// Ui landed scene
|
||||||
pub ui_landed_scene: AST,
|
pub ui_landed_scene: AST,
|
||||||
|
|
||||||
/// Ui flying scene script
|
|
||||||
pub ui_flying_scene: AST,
|
|
||||||
|
|
||||||
/// Ui outfitter scene script
|
|
||||||
pub ui_outfitter_scene: AST,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ fn try_main() -> Result<()> {
|
||||||
| ShipState::Flying { .. } => {
|
| ShipState::Flying { .. } => {
|
||||||
if was_landed {
|
if was_landed {
|
||||||
was_landed = false;
|
was_landed = false;
|
||||||
gpu.set_scene(RenderScenes::System);
|
gpu.set_scene(&content, RenderScenes::System);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(*o.rigidbody.translation())
|
Some(*o.rigidbody.translation())
|
||||||
|
@ -192,7 +192,7 @@ fn try_main() -> Result<()> {
|
||||||
ShipState::Landed { target } => {
|
ShipState::Landed { target } => {
|
||||||
if !was_landed {
|
if !was_landed {
|
||||||
was_landed = true;
|
was_landed = true;
|
||||||
gpu.set_scene(RenderScenes::Landed);
|
gpu.set_scene(&content, RenderScenes::Landed);
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = content.get_system_object(*target);
|
let b = content.get_system_object(*target);
|
||||||
|
|
|
@ -32,4 +32,3 @@ wgpu = { workspace = true }
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
glyphon = { workspace = true }
|
glyphon = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
rhai = { workspace = true }
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
|
@ -15,7 +13,10 @@ use crate::{
|
||||||
shaderprocessor::preprocess_shader,
|
shaderprocessor::preprocess_shader,
|
||||||
starfield::Starfield,
|
starfield::Starfield,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
ui::{UiManager, UiScene},
|
ui::{
|
||||||
|
scenes::{UiFlyingScene, UiLandedScene},
|
||||||
|
UiManager, UiScenes,
|
||||||
|
},
|
||||||
RenderInput, RenderScenes, RenderState, VertexBuffers,
|
RenderInput, RenderScenes, RenderState, VertexBuffers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ impl GPUState {
|
||||||
/// Make a new GPUState that draws on `window`
|
/// Make a new GPUState that draws on `window`
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
window: winit::window::Window,
|
window: winit::window::Window,
|
||||||
ct: Rc<Content>,
|
ct: &Content,
|
||||||
scene: RenderScenes,
|
scene: RenderScenes,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let window_size = window.inner_size();
|
let window_size = window.inner_size();
|
||||||
|
@ -105,11 +106,11 @@ impl GPUState {
|
||||||
surface.configure(&device, &config);
|
surface.configure(&device, &config);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vertex_buffers = VertexBuffers::new(&device, &ct);
|
let vertex_buffers = VertexBuffers::new(&device, ct);
|
||||||
|
|
||||||
// Load uniforms
|
// Load uniforms
|
||||||
let global_uniform = GlobalUniform::new(&device);
|
let global_uniform = GlobalUniform::new(&device);
|
||||||
let texture_array = TextureArray::new(&device, &queue, &ct)?;
|
let texture_array = TextureArray::new(&device, &queue, ct)?;
|
||||||
|
|
||||||
// Make sure these match the indices in each shader
|
// Make sure these match the indices in each shader
|
||||||
let bind_group_layouts = &[
|
let bind_group_layouts = &[
|
||||||
|
@ -216,10 +217,23 @@ impl GPUState {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let mut starfield = Starfield::new();
|
let mut starfield = Starfield::new();
|
||||||
starfield.regenerate(&ct);
|
starfield.regenerate(ct);
|
||||||
|
|
||||||
|
let mut state = RenderState {
|
||||||
|
queue,
|
||||||
|
window,
|
||||||
|
window_size,
|
||||||
|
window_aspect,
|
||||||
|
global_uniform,
|
||||||
|
vertex_buffers,
|
||||||
|
text_atlas,
|
||||||
|
text_cache,
|
||||||
|
text_font_system,
|
||||||
|
text_renderer,
|
||||||
|
};
|
||||||
|
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
ui: UiManager::new(ct),
|
ui: UiManager::new(ct, &mut state),
|
||||||
device,
|
device,
|
||||||
config,
|
config,
|
||||||
surface,
|
surface,
|
||||||
|
@ -230,19 +244,7 @@ impl GPUState {
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
radialbar_pipeline,
|
radialbar_pipeline,
|
||||||
scene,
|
scene,
|
||||||
|
state,
|
||||||
state: RenderState {
|
|
||||||
queue,
|
|
||||||
window,
|
|
||||||
window_size,
|
|
||||||
window_aspect,
|
|
||||||
global_uniform,
|
|
||||||
vertex_buffers,
|
|
||||||
text_atlas,
|
|
||||||
text_cache,
|
|
||||||
text_font_system,
|
|
||||||
text_renderer,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,14 +255,18 @@ impl GPUState {
|
||||||
&self.state.window
|
&self.state.window
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the current scenection
|
/// Change the current scene
|
||||||
pub fn set_scene(&mut self, scene: RenderScenes) {
|
pub fn set_scene(&mut self, ct: &Content, scene: RenderScenes) {
|
||||||
debug!("switching to {:?}", scene);
|
debug!("switching to {:?}", scene);
|
||||||
|
|
||||||
match scene {
|
match scene {
|
||||||
RenderScenes::Landed => self.ui.set_scene(&mut self.state, UiScene::Landed).unwrap(),
|
RenderScenes::Landed => self
|
||||||
RenderScenes::System => self.ui.set_scene(&mut self.state, UiScene::Flying).unwrap(),
|
.ui
|
||||||
};
|
.set_scene(UiScenes::Landed(UiLandedScene::new(ct, &mut self.state))),
|
||||||
|
RenderScenes::System => self
|
||||||
|
.ui
|
||||||
|
.set_scene(UiScenes::Flying(UiFlyingScene::new(ct, &mut self.state))),
|
||||||
|
}
|
||||||
|
|
||||||
self.scene = scene;
|
self.scene = scene;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use galactica_content::{Content, SystemHandle};
|
use galactica_content::{Content, SystemHandle};
|
||||||
use galactica_playeragent::PlayerAgent;
|
use galactica_playeragent::PlayerAgent;
|
||||||
use galactica_system::phys::PhysImage;
|
use galactica_system::phys::PhysImage;
|
||||||
|
@ -31,7 +29,7 @@ pub struct RenderInput<'a> {
|
||||||
pub time_since_last_run: f32,
|
pub time_since_last_run: f32,
|
||||||
|
|
||||||
/// Game content
|
/// Game content
|
||||||
pub ct: Rc<Content>,
|
pub ct: &'a Content,
|
||||||
|
|
||||||
/// Time we spent in each part of the game loop
|
/// Time we spent in each part of the game loop
|
||||||
pub timing: Timing,
|
pub timing: Timing,
|
||||||
|
|
|
@ -40,7 +40,7 @@ impl RenderScene for LandedScene {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
g.ui.draw(&input, &mut g.state)?;
|
g.ui.draw(&input, &mut g.state);
|
||||||
|
|
||||||
// These should match the indices in each shader,
|
// These should match the indices in each shader,
|
||||||
// and should each have a corresponding bind group layout.
|
// and should each have a corresponding bind group layout.
|
||||||
|
|
|
@ -54,7 +54,7 @@ impl RenderScene for SystemScene {
|
||||||
Self::push_ships(g, &input, (clip_ne, clip_sw));
|
Self::push_ships(g, &input, (clip_ne, clip_sw));
|
||||||
Self::push_projectiles(g, &input, (clip_ne, clip_sw));
|
Self::push_projectiles(g, &input, (clip_ne, clip_sw));
|
||||||
Self::push_effects(g, &input, (clip_ne, clip_sw));
|
Self::push_effects(g, &input, (clip_ne, clip_sw));
|
||||||
g.ui.draw(&input, &mut g.state)?;
|
g.ui.draw(&input, &mut g.state);
|
||||||
|
|
||||||
// These should match the indices in each shader,
|
// These should match the indices in each shader,
|
||||||
// and should each have a corresponding bind group layout.
|
// and should each have a corresponding bind group layout.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_util::constants::{OBJECT_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT};
|
use galactica_util::constants::{
|
||||||
|
OBJECT_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT,
|
||||||
|
};
|
||||||
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use wgpu::BufferAddress;
|
use wgpu::BufferAddress;
|
||||||
|
@ -152,7 +154,6 @@ impl RenderState {
|
||||||
self.vertex_buffers.object_counter as u32
|
self.vertex_buffers.object_counter as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
pub fn push_radialbar_buffer(&mut self, instance: RadialBarInstance) {
|
pub fn push_radialbar_buffer(&mut self, instance: RadialBarInstance) {
|
||||||
// Enforce buffer limit
|
// Enforce buffer limit
|
||||||
if self.vertex_buffers.radialbar_counter as u64 > RADIALBAR_SPRITE_INSTANCE_LIMIT {
|
if self.vertex_buffers.radialbar_counter as u64 > RADIALBAR_SPRITE_INSTANCE_LIMIT {
|
||||||
|
@ -167,7 +168,6 @@ impl RenderState {
|
||||||
);
|
);
|
||||||
self.vertex_buffers.radialbar_counter += 1;
|
self.vertex_buffers.radialbar_counter += 1;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
pub fn get_radialbar_counter(&self) -> u32 {
|
pub fn get_radialbar_counter(&self) -> u32 {
|
||||||
self.vertex_buffers.radialbar_counter as u32
|
self.vertex_buffers.radialbar_counter as u32
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
mod rect;
|
|
||||||
mod sprite;
|
|
||||||
mod spritebuilder;
|
|
||||||
mod state;
|
|
||||||
mod textboxbuilder;
|
|
||||||
mod util;
|
|
||||||
|
|
||||||
use rhai::{exported_module, Engine};
|
|
||||||
|
|
||||||
pub fn register_into_engine(engine: &mut Engine) {
|
|
||||||
engine
|
|
||||||
.build_type::<State>()
|
|
||||||
.build_type::<Rect>()
|
|
||||||
.build_type::<SpriteElement>()
|
|
||||||
.build_type::<SpriteBuilder>()
|
|
||||||
.build_type::<TextBoxBuilder>()
|
|
||||||
.register_type_with_name::<SpriteAnchor>("SpriteAnchor")
|
|
||||||
.register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into())
|
|
||||||
.register_type_with_name::<TextBoxFont>("TextBoxFont")
|
|
||||||
.register_static_module(
|
|
||||||
"TextBoxFont",
|
|
||||||
exported_module!(util::textboxfont_mod).into(),
|
|
||||||
)
|
|
||||||
.register_type_with_name::<TextBoxJustify>("TextBoxJustify")
|
|
||||||
.register_static_module(
|
|
||||||
"TextBoxJustify",
|
|
||||||
exported_module!(textboxjustify_mod).into(),
|
|
||||||
)
|
|
||||||
.register_type_with_name::<SceneAction>("SceneAction")
|
|
||||||
.register_static_module("SceneAction", exported_module!(sceneaction_mod).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use rect::*;
|
|
||||||
pub use sprite::*;
|
|
||||||
pub use spritebuilder::*;
|
|
||||||
pub use state::*;
|
|
||||||
pub use textboxbuilder::*;
|
|
||||||
pub use util::*;
|
|
|
@ -1,126 +0,0 @@
|
||||||
use nalgebra::{Point2, Vector2};
|
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
use winit::dpi::LogicalSize;
|
|
||||||
|
|
||||||
use super::SpriteAnchor;
|
|
||||||
use crate::{RenderInput, RenderState};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Rect {
|
|
||||||
pub pos: Point2<f32>,
|
|
||||||
pub dim: Vector2<f32>,
|
|
||||||
pub anchor_self: SpriteAnchor,
|
|
||||||
pub anchor_parent: SpriteAnchor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Rect {
|
|
||||||
pub fn new(
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
w: f32,
|
|
||||||
h: f32,
|
|
||||||
anchor_self: SpriteAnchor,
|
|
||||||
anchor_parent: SpriteAnchor,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
pos: Point2::new(x, y),
|
|
||||||
dim: Vector2::new(w, h),
|
|
||||||
anchor_self,
|
|
||||||
anchor_parent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_centered(&self, state: &RenderState, ui_scale: f32) -> CenteredRect {
|
|
||||||
let w: LogicalSize<f32> = state.window_size.to_logical(state.window.scale_factor());
|
|
||||||
let w = Vector2::new(w.width, w.height);
|
|
||||||
|
|
||||||
let mut pos = self.pos * ui_scale;
|
|
||||||
let dim = self.dim * ui_scale;
|
|
||||||
|
|
||||||
// Origin
|
|
||||||
match self.anchor_parent {
|
|
||||||
SpriteAnchor::Center => {}
|
|
||||||
|
|
||||||
SpriteAnchor::NorthWest => {
|
|
||||||
pos += Vector2::new(-w.x, w.y) / 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteAnchor::SouthWest => {
|
|
||||||
pos += Vector2::new(-w.x, -w.y) / 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteAnchor::NorthEast => {
|
|
||||||
pos += Vector2::new(w.x, w.y) / 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteAnchor::SouthEast => {
|
|
||||||
pos += Vector2::new(w.x, -w.y) / 2.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offset for self dimensions
|
|
||||||
match self.anchor_self {
|
|
||||||
SpriteAnchor::Center => {}
|
|
||||||
|
|
||||||
SpriteAnchor::NorthWest => {
|
|
||||||
pos += Vector2::new(dim.x, -dim.y) / 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteAnchor::NorthEast => {
|
|
||||||
pos += Vector2::new(-dim.x, -dim.y) / 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteAnchor::SouthWest => {
|
|
||||||
pos += Vector2::new(dim.x, dim.y) / 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteAnchor::SouthEast => {
|
|
||||||
pos += Vector2::new(-dim.x, dim.y) / 2.0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return CenteredRect { pos, dim };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for Rect {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder.with_name("Rect").with_fn("Rect", Self::new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a rectangular region, in absolute coordinates relative to the screen center.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub(crate) struct CenteredRect {
|
|
||||||
/// The position of the top-left corner of this rectangle, in fractional units.
|
|
||||||
/// (0.0 is left edge of sprite, 1.0 is right edge)
|
|
||||||
pub pos: Point2<f32>,
|
|
||||||
|
|
||||||
/// The width and height of this rectangle, in fractional units.
|
|
||||||
/// 1.0 will be as tall as the sprite, 0.5 will be half as tall
|
|
||||||
pub dim: Vector2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CenteredRect {
|
|
||||||
pub fn contains_point(&self, pt: Point2<f32>) -> bool {
|
|
||||||
let ne = self.pos + Vector2::new(-self.dim.x, self.dim.y) / 2.0;
|
|
||||||
let sw = self.pos + Vector2::new(self.dim.x, -self.dim.y) / 2.0;
|
|
||||||
return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains_mouse(&self, input: &RenderInput, state: &RenderState) -> bool {
|
|
||||||
let fac = state.window.scale_factor() as f32;
|
|
||||||
let window_size = Vector2::new(
|
|
||||||
state.window_size.width as f32 / fac,
|
|
||||||
state.window_size.height as f32 / fac,
|
|
||||||
);
|
|
||||||
|
|
||||||
let pos = input.player.input.get_mouse_pos();
|
|
||||||
let mouse_pos = Point2::new(
|
|
||||||
pos.x / fac - window_size.x / 2.0,
|
|
||||||
window_size.y / 2.0 - pos.y / fac,
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.contains_point(mouse_pos);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
use galactica_content::{resolve_edge_as_edge, Content};
|
|
||||||
use log::error;
|
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
|
|
||||||
use crate::ui::util::Sprite;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct SpriteElement {
|
|
||||||
pub sprite: Rc<RefCell<Sprite>>,
|
|
||||||
pub ct: Rc<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for SpriteElement {}
|
|
||||||
unsafe impl Sync for SpriteElement {}
|
|
||||||
|
|
||||||
impl SpriteElement {
|
|
||||||
pub fn new(ct: Rc<Content>, sprite: Rc<RefCell<Sprite>>) -> Self {
|
|
||||||
Self { ct, sprite }
|
|
||||||
}
|
|
||||||
|
|
||||||
// This MUST be &mut, or rhai won't recognize it.
|
|
||||||
pub fn has_name(&mut self, s: String) -> bool {
|
|
||||||
self.sprite.as_ref().borrow().name == s
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take_edge(&mut self, edge_name: &str, duration: f32) {
|
|
||||||
let sprite_handle = self.sprite.as_ref().borrow().anim.get_sprite();
|
|
||||||
let sprite = self.ct.get_sprite(sprite_handle);
|
|
||||||
|
|
||||||
let edge = resolve_edge_as_edge(edge_name, duration, |x| {
|
|
||||||
sprite.get_section_handle_by_name(x)
|
|
||||||
});
|
|
||||||
let edge = match edge {
|
|
||||||
Err(x) => {
|
|
||||||
error!(
|
|
||||||
"UI script reference invalid section `{}` in sprite `{}`, skipping",
|
|
||||||
edge_name, sprite.name
|
|
||||||
);
|
|
||||||
error!("error: {:?}", x);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Ok(s) => s,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.sprite
|
|
||||||
.as_ref()
|
|
||||||
.borrow_mut()
|
|
||||||
.anim
|
|
||||||
.jump_to(&self.ct, edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for SpriteElement {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("SpriteElement")
|
|
||||||
.with_fn("has_name", Self::has_name)
|
|
||||||
.with_fn("take_edge", Self::take_edge);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
|
|
||||||
use super::Rect;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct SpriteBuilder {
|
|
||||||
pub name: String,
|
|
||||||
pub rect: Rect,
|
|
||||||
pub sprite: String,
|
|
||||||
pub mask: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpriteBuilder {
|
|
||||||
pub fn new(name: String, sprite: String, rect: Rect) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
rect,
|
|
||||||
sprite,
|
|
||||||
mask: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_mask(&mut self, mask: String) {
|
|
||||||
self.mask = Some(mask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for SpriteBuilder {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("SpriteBuilder")
|
|
||||||
.with_fn("SpriteBuilder", Self::new)
|
|
||||||
.with_fn("set_mask", Self::set_mask);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, CustomType)]
|
|
||||||
pub struct State {
|
|
||||||
pub planet_landscape: String,
|
|
||||||
pub planet_name: String,
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
|
|
||||||
use super::{Rect, TextBoxFont, TextBoxJustify};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TextBoxBuilder {
|
|
||||||
pub name: String,
|
|
||||||
pub font_size: f32,
|
|
||||||
pub line_height: f32,
|
|
||||||
pub font: TextBoxFont,
|
|
||||||
pub justify: TextBoxJustify,
|
|
||||||
pub rect: Rect,
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextBoxBuilder {
|
|
||||||
pub fn new(
|
|
||||||
name: String,
|
|
||||||
font_size: f32,
|
|
||||||
line_height: f32,
|
|
||||||
font: TextBoxFont,
|
|
||||||
justify: TextBoxJustify,
|
|
||||||
rect: Rect,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
font_size,
|
|
||||||
line_height,
|
|
||||||
font,
|
|
||||||
justify,
|
|
||||||
rect,
|
|
||||||
text: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: String) {
|
|
||||||
self.text = text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for TextBoxBuilder {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("TextBoxBuilder")
|
|
||||||
.with_fn("TextBoxBuilder", Self::new)
|
|
||||||
.with_fn("set_text", Self::set_text);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
use rhai::plugin::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum SpriteAnchor {
|
|
||||||
Center,
|
|
||||||
NorthWest,
|
|
||||||
SouthWest,
|
|
||||||
NorthEast,
|
|
||||||
SouthEast,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_module]
|
|
||||||
pub mod spriteanchor_mod {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const Center: SpriteAnchor = SpriteAnchor::Center;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const NorthWest: SpriteAnchor = SpriteAnchor::NorthWest;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const NorthEast: SpriteAnchor = SpriteAnchor::NorthEast;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const SouthWest: SpriteAnchor = SpriteAnchor::SouthWest;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const SouthEast: SpriteAnchor = SpriteAnchor::SouthEast;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum TextBoxFont {
|
|
||||||
Serif,
|
|
||||||
SansSerif,
|
|
||||||
Monospace,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_module]
|
|
||||||
pub mod textboxfont_mod {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const Serif: TextBoxFont = TextBoxFont::Serif;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const SansSerif: TextBoxFont = TextBoxFont::SansSerif;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const Monospace: TextBoxFont = TextBoxFont::Monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum TextBoxJustify {
|
|
||||||
Center,
|
|
||||||
Left,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_module]
|
|
||||||
pub mod textboxjustify_mod {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const Center: TextBoxJustify = TextBoxJustify::Center;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const Left: TextBoxJustify = TextBoxJustify::Left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum SceneAction {
|
|
||||||
None,
|
|
||||||
SceneOutfitter,
|
|
||||||
SceneLanded,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_module]
|
|
||||||
pub mod sceneaction_mod {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const None: SceneAction = SceneAction::None;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const SceneOutfitter: SceneAction = SceneAction::SceneOutfitter;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const SceneLanded: SceneAction = SceneAction::SceneLanded;
|
|
||||||
}
|
|
|
@ -1,265 +1,146 @@
|
||||||
use anyhow::Result;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use glyphon::TextArea;
|
use glyphon::TextArea;
|
||||||
use log::{debug, error, trace};
|
use log::debug;
|
||||||
use rhai::{Array, Dynamic, Engine, Scope, AST};
|
|
||||||
use std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc};
|
|
||||||
|
|
||||||
use super::{
|
use super::scenes::{UiFlyingScene, UiLandedScene, UiOutfitterScene};
|
||||||
api::{self, SceneAction, SpriteElement, TextBoxBuilder},
|
use crate::{RenderInput, RenderState};
|
||||||
util::{Sprite, TextBox},
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
ui::api::{SpriteBuilder, State},
|
|
||||||
RenderInput, RenderState,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
/// Output from a ui scene step
|
||||||
pub enum MouseEvent {
|
pub struct UiSceneStepResult {
|
||||||
Click,
|
/// If Some, switch to this scene
|
||||||
Release,
|
pub new_scene: Option<UiScenes>,
|
||||||
Enter,
|
|
||||||
Leave,
|
|
||||||
None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MouseEvent {
|
pub trait UiScene<'this>
|
||||||
pub fn is_enter(&self) -> bool {
|
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;
|
||||||
|
|
||||||
|
/// Add all textareas in this scene to `v`
|
||||||
|
fn get_textareas(
|
||||||
|
&'this self,
|
||||||
|
v: &mut Vec<TextArea<'this>>,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &RenderState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum UiScenes {
|
||||||
|
Landed(UiLandedScene),
|
||||||
|
Flying(UiFlyingScene),
|
||||||
|
Outfitter(UiOutfitterScene),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for UiScenes {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Enter => true,
|
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,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_click(&self) -> bool {
|
fn is_landed(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Click => true,
|
Self::Landed(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_outfitter(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Outfitter(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
impl<'a> UiScene<'a> for UiScenes {
|
||||||
pub(crate) enum UiScene {
|
fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||||
Landed,
|
|
||||||
Flying,
|
|
||||||
Outfitter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for UiScene {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Flying => "flying".to_string(),
|
Self::Flying(s) => s.draw(input, state),
|
||||||
Self::Landed => "landed".to_string(),
|
Self::Landed(s) => s.draw(input, state),
|
||||||
Self::Outfitter => "outfitter".to_string(),
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UiElement {
|
pub struct UiManager {
|
||||||
Sprite(Rc<RefCell<Sprite>>),
|
current_scene: UiScenes,
|
||||||
Text(TextBox),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiElement {
|
|
||||||
pub fn new_sprite(sprite: Sprite) -> Self {
|
|
||||||
Self::Sprite(Rc::new(RefCell::new(sprite)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_text(text: TextBox) -> Self {
|
|
||||||
Self::Text(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sprite(&self) -> Option<Rc<RefCell<Sprite>>> {
|
|
||||||
match self {
|
|
||||||
Self::Sprite(s) => Some(s.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(&self) -> Option<&TextBox> {
|
|
||||||
match self {
|
|
||||||
Self::Text(t) => Some(t),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct UiManager {
|
|
||||||
current_scene: UiScene,
|
|
||||||
engine: Engine,
|
|
||||||
scope: Scope<'static>,
|
|
||||||
elements: Vec<UiElement>,
|
|
||||||
ct: Rc<Content>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiManager {
|
impl UiManager {
|
||||||
pub fn new(ct: Rc<Content>) -> Self {
|
pub fn new(ct: &Content, state: &mut RenderState) -> Self {
|
||||||
let scope = Scope::new();
|
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
api::register_into_engine(&mut engine);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ct,
|
current_scene: UiScenes::Flying(UiFlyingScene::new(ct, state)),
|
||||||
current_scene: UiScene::Flying,
|
|
||||||
engine,
|
|
||||||
scope,
|
|
||||||
elements: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_scene_ast(ct: &Content, scene: UiScene) -> &AST {
|
|
||||||
match scene {
|
|
||||||
UiScene::Landed => &ct.get_config().ui_landed_scene,
|
|
||||||
UiScene::Flying => &ct.get_config().ui_flying_scene,
|
|
||||||
UiScene::Outfitter => &ct.get_config().ui_outfitter_scene,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the current scene
|
/// Change the current scene
|
||||||
pub fn set_scene(&mut self, state: &mut RenderState, scene: UiScene) -> Result<()> {
|
pub fn set_scene(&mut self, scene: UiScenes) {
|
||||||
debug!("switching to {:?}", scene);
|
debug!("switching to {:?}", scene);
|
||||||
self.current_scene = scene;
|
self.current_scene = scene;
|
||||||
|
|
||||||
self.scope.clear();
|
|
||||||
self.elements.clear();
|
|
||||||
let mut used_names = HashSet::new();
|
|
||||||
|
|
||||||
trace!("running init for `{}`", self.current_scene.to_string());
|
|
||||||
|
|
||||||
let builders: Array = self.engine.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
Self::get_scene_ast(&self.ct, self.current_scene),
|
|
||||||
"init",
|
|
||||||
(State {
|
|
||||||
planet_landscape: "ui::landscape::test".to_string(),
|
|
||||||
planet_name: "Earth".to_string(),
|
|
||||||
},),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
trace!("found {:?} builders", builders.len());
|
|
||||||
|
|
||||||
for v in builders {
|
|
||||||
if v.is::<SpriteBuilder>() {
|
|
||||||
let s = v.cast::<SpriteBuilder>();
|
|
||||||
if used_names.contains(&s.name) {
|
|
||||||
error!(
|
|
||||||
"UI scene `{}` re-uses element name `{}`",
|
|
||||||
self.current_scene.to_string(),
|
|
||||||
s.name
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
used_names.insert(s.name.clone());
|
|
||||||
}
|
|
||||||
self.elements.push(UiElement::new_sprite(Sprite::new(
|
|
||||||
&self.ct, s.name, s.sprite, s.mask, s.rect,
|
|
||||||
)));
|
|
||||||
} else if v.is::<TextBoxBuilder>() {
|
|
||||||
let t = v.cast::<TextBoxBuilder>();
|
|
||||||
if used_names.contains(&t.name) {
|
|
||||||
error!(
|
|
||||||
"UI scene `{}` re-uses element name `{}`",
|
|
||||||
self.current_scene.to_string(),
|
|
||||||
t.name
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
used_names.insert(t.name.clone());
|
|
||||||
}
|
|
||||||
let mut b = TextBox::new(
|
|
||||||
state,
|
|
||||||
t.name,
|
|
||||||
t.font_size,
|
|
||||||
t.line_height,
|
|
||||||
t.font,
|
|
||||||
t.justify,
|
|
||||||
t.rect,
|
|
||||||
);
|
|
||||||
b.set_text(state, &t.text);
|
|
||||||
self.elements.push(UiElement::new_text(b));
|
|
||||||
} else {
|
|
||||||
// TODO: better message
|
|
||||||
error!("bad type in builder array")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw all ui elements
|
/// Draw all ui elements
|
||||||
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) -> Result<()> {
|
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||||
let mut iter = self
|
loop {
|
||||||
.elements
|
let r = self.current_scene.step(input, state);
|
||||||
.iter()
|
if let Some(new_scene) = r.new_scene {
|
||||||
.map(|x| x.sprite())
|
debug!("{:?} changed scene", self.current_scene);
|
||||||
.filter(|x| x.is_some())
|
self.set_scene(new_scene)
|
||||||
.map(|x| x.unwrap());
|
} else {
|
||||||
|
break;
|
||||||
let action: SceneAction = loop {
|
|
||||||
let e = match iter.next() {
|
|
||||||
Some(e) => e,
|
|
||||||
None => break SceneAction::None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut x = (*e).borrow_mut();
|
|
||||||
let m = x.check_mouse(input, state);
|
|
||||||
x.step(input, state);
|
|
||||||
x.push_to_buffer(input, state);
|
|
||||||
drop(x);
|
|
||||||
// we MUST drop here, since script calls mutate the sprite RefCell
|
|
||||||
|
|
||||||
let action: Dynamic = match m {
|
|
||||||
MouseEvent::None => Dynamic::from(SceneAction::None),
|
|
||||||
|
|
||||||
MouseEvent::Release | MouseEvent::Click => self.engine.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
Self::get_scene_ast(&self.ct, self.current_scene),
|
|
||||||
"click",
|
|
||||||
(SpriteElement::new(self.ct.clone(), e.clone()), m.is_click()),
|
|
||||||
)?,
|
|
||||||
|
|
||||||
MouseEvent::Leave | MouseEvent::Enter => self.engine.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
Self::get_scene_ast(&self.ct, self.current_scene),
|
|
||||||
"hover",
|
|
||||||
(SpriteElement::new(self.ct.clone(), e.clone()), m.is_enter()),
|
|
||||||
)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(action) = action.try_cast::<SceneAction>() {
|
|
||||||
match action {
|
|
||||||
SceneAction::None => {}
|
|
||||||
_ => {
|
|
||||||
break action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
drop(iter);
|
|
||||||
|
|
||||||
match action {
|
|
||||||
SceneAction::None => {}
|
|
||||||
SceneAction::SceneOutfitter => self.set_scene(state, UiScene::Outfitter)?,
|
|
||||||
SceneAction::SceneLanded => self.set_scene(state, UiScene::Landed)?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
self.current_scene.draw(input, state);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> UiManager {
|
/// Textareas to show while player is flying
|
||||||
/// Get textareas
|
pub fn get_textareas(&self, input: &RenderInput, state: &RenderState) -> Vec<TextArea> {
|
||||||
pub fn get_textareas(
|
let mut v = Vec::with_capacity(5);
|
||||||
&'a mut self,
|
self.current_scene.get_textareas(&mut v, input, state);
|
||||||
input: &RenderInput,
|
return v;
|
||||||
state: &RenderState,
|
|
||||||
) -> Vec<TextArea<'a>> {
|
|
||||||
self.elements
|
|
||||||
.iter()
|
|
||||||
.map(|x| x.text())
|
|
||||||
.filter(|x| x.is_some())
|
|
||||||
.map(|x| x.unwrap())
|
|
||||||
.map(|x| x.get_textarea(state, input))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod api;
|
|
||||||
mod manager;
|
mod manager;
|
||||||
|
pub(crate) mod scenes;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub(crate) use manager::UiManager;
|
pub use manager::UiManager;
|
||||||
pub(crate) use manager::UiScene;
|
pub(crate) use manager::UiScenes;
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
use glyphon::{Attrs, Buffer, Color, Family, Metrics, Shaping, TextArea, TextBounds};
|
||||||
|
|
||||||
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
|
pub(super) struct FpsIndicator {
|
||||||
|
buffer: Buffer,
|
||||||
|
update_counter: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FpsIndicator {
|
||||||
|
pub fn new(state: &mut RenderState) -> Self {
|
||||||
|
let mut buffer = Buffer::new(&mut state.text_font_system, Metrics::new(15.0, 20.0));
|
||||||
|
buffer.set_size(
|
||||||
|
&mut state.text_font_system,
|
||||||
|
state.window_size.width as f32,
|
||||||
|
state.window_size.height as f32,
|
||||||
|
);
|
||||||
|
buffer.shape_until_scroll(&mut state.text_font_system);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
buffer,
|
||||||
|
update_counter: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FpsIndicator {
|
||||||
|
pub fn update(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
// Update once every n frames
|
||||||
|
if self.update_counter > 0 {
|
||||||
|
self.update_counter -= 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.update_counter = 100;
|
||||||
|
|
||||||
|
self.buffer.set_text(
|
||||||
|
&mut state.text_font_system,
|
||||||
|
&input.timing.get_string(),
|
||||||
|
Attrs::new().family(Family::Monospace),
|
||||||
|
Shaping::Basic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_textarea(&self) -> TextArea {
|
||||||
|
TextArea {
|
||||||
|
buffer: &self.buffer,
|
||||||
|
left: 10.0,
|
||||||
|
top: 400.0,
|
||||||
|
scale: 1.0,
|
||||||
|
bounds: TextBounds {
|
||||||
|
left: 10,
|
||||||
|
top: 400,
|
||||||
|
right: 300,
|
||||||
|
bottom: 800,
|
||||||
|
},
|
||||||
|
default_color: Color::rgb(255, 255, 255),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
mod fpsindicator;
|
||||||
|
mod radar;
|
||||||
|
mod scene;
|
||||||
|
mod status;
|
||||||
|
|
||||||
|
pub use scene::UiFlyingScene;
|
|
@ -0,0 +1,265 @@
|
||||||
|
use galactica_system::data::ShipState;
|
||||||
|
use galactica_util::{clockwise_angle, to_radians};
|
||||||
|
use nalgebra::{Point2, Rotation2, Vector2};
|
||||||
|
|
||||||
|
use crate::{vertexbuffer::types::UiInstance, PositionAnchor, RenderInput, RenderState};
|
||||||
|
|
||||||
|
pub(super) struct Radar {
|
||||||
|
last_player_position: Point2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Radar {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
last_player_position: Point2::new(0.0, 0.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Radar {
|
||||||
|
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
let radar_range = 4000.0;
|
||||||
|
let radar_size = 300.0;
|
||||||
|
let hide_range = 0.85;
|
||||||
|
let shrink_distance = 20.0;
|
||||||
|
let system_object_scale = 1.0 / 600.0;
|
||||||
|
let ship_scale = 1.0 / 10.0;
|
||||||
|
|
||||||
|
// TODO: maybe a cleaner solution for last posititon?
|
||||||
|
// This is necessary because the player may be dead or landed
|
||||||
|
let player_ship = input
|
||||||
|
.phys_img
|
||||||
|
.get_ship(&galactica_system::phys::PhysSimShipHandle(
|
||||||
|
input.player.ship.unwrap(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match player_ship.ship.get_data().get_state() {
|
||||||
|
ShipState::Dead => {}
|
||||||
|
|
||||||
|
ShipState::Landed { target } => {
|
||||||
|
let landed_body = input.ct.get_system_object(*target);
|
||||||
|
self.last_player_position = Point2::new(landed_body.pos.x, landed_body.pos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::UnLanding { .. }
|
||||||
|
| ShipState::Landing { .. }
|
||||||
|
| ShipState::Flying { .. }
|
||||||
|
| ShipState::Collapsing { .. } => {
|
||||||
|
self.last_player_position = (*player_ship.rigidbody.translation()).into();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: don't hard-code these, add config options
|
||||||
|
let planet_sprite = input.ct.get_sprite_handle("ui::planetblip");
|
||||||
|
let ship_sprite = input.ct.get_sprite_handle("ui::shipblip");
|
||||||
|
let arrow_sprite = input.ct.get_sprite_handle("ui::centerarrow");
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(input.ct.get_sprite_handle("ui::radar"));
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
// Push this object's instance
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwNw.to_int(),
|
||||||
|
position: [10.0, -10.0],
|
||||||
|
angle: 0.0,
|
||||||
|
dim: [sprite.aspect * radar_size, radar_size],
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw system objects
|
||||||
|
let system = input.ct.get_system(input.current_system);
|
||||||
|
for o in &system.objects {
|
||||||
|
let size = (o.size / o.pos.z) / (radar_range * system_object_scale);
|
||||||
|
let p = Point2::new(o.pos.x, o.pos.y);
|
||||||
|
let d = (p - self.last_player_position) / radar_range;
|
||||||
|
// Add half the blip sprite's height to distance
|
||||||
|
let m = d.magnitude() + (size / (2.0 * radar_size));
|
||||||
|
if m < hide_range {
|
||||||
|
// Shrink blips as they get closeto the edge
|
||||||
|
let size = size.min((hide_range - m) * size * shrink_distance);
|
||||||
|
if size <= 2.0 {
|
||||||
|
// Don't draw super tiny sprites, they flicker
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(planet_sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
// Push this object's instance
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwC.to_int(),
|
||||||
|
position: (Point2::new(radar_size / 2.0 + 10.0, radar_size / -2.0 - 10.0)
|
||||||
|
+ (d * (radar_size / 2.0)))
|
||||||
|
.into(),
|
||||||
|
angle: o.angle,
|
||||||
|
dim: [sprite.aspect * size, size],
|
||||||
|
color: [0.5, 0.5, 0.5, 1.0],
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw ships
|
||||||
|
for s in input.phys_img.iter_ships() {
|
||||||
|
let ship = input.ct.get_ship(s.ship.get_data().get_content());
|
||||||
|
let (color, z_scale) = match s.ship.get_data().get_state() {
|
||||||
|
ShipState::Dead | ShipState::Landed { .. } => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: different color for landing?
|
||||||
|
// TODO: scale blip for ship z-position
|
||||||
|
ShipState::Landing { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0),
|
||||||
|
|
||||||
|
ShipState::UnLanding { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0),
|
||||||
|
|
||||||
|
ShipState::Collapsing { .. } => {
|
||||||
|
// TODO: configurable
|
||||||
|
([0.2, 0.2, 0.2, 1.0], 1.0)
|
||||||
|
}
|
||||||
|
ShipState::Flying { .. } => {
|
||||||
|
let c = input.ct.get_faction(s.ship.get_data().get_faction()).color;
|
||||||
|
([c[0], c[1], c[2], 1.0], 1.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let size = (ship.size * input.ct.get_sprite(ship.sprite).aspect) * ship_scale * z_scale;
|
||||||
|
let p: Point2<f32> = {
|
||||||
|
if s.ship.collider == input.player.ship.unwrap() {
|
||||||
|
self.last_player_position
|
||||||
|
} else {
|
||||||
|
(*s.rigidbody.translation()).into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let d = (p - self.last_player_position) / radar_range;
|
||||||
|
let m = d.magnitude() + (size / (2.0 * radar_size));
|
||||||
|
if m < hide_range {
|
||||||
|
let size = size.min((hide_range - m) * size * shrink_distance);
|
||||||
|
if size < 2.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let position = Point2::new(radar_size / 2.0 + 10.0, radar_size / -2.0 - 10.0)
|
||||||
|
+ (d * (radar_size / 2.0));
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(ship_sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwC.to_int(),
|
||||||
|
position: position.into(),
|
||||||
|
angle: player_ship.rigidbody.rotation().angle(),
|
||||||
|
dim: [sprite.aspect * size, size],
|
||||||
|
color,
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw viewport frame
|
||||||
|
let d = Vector2::new(
|
||||||
|
(input.camera_zoom / 2.0) * state.window_aspect,
|
||||||
|
input.camera_zoom / 2.0,
|
||||||
|
) / radar_range;
|
||||||
|
let m = d.magnitude();
|
||||||
|
let d = d * (radar_size / 2.0);
|
||||||
|
let color = [0.3, 0.3, 0.3, 1.0];
|
||||||
|
if m < 0.8 {
|
||||||
|
let sprite = input.ct.get_sprite_handle("ui::radarframe");
|
||||||
|
let size = 7.0f32.min((0.8 - m) * 70.0);
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwNw.to_int(),
|
||||||
|
position: Point2::new(
|
||||||
|
(radar_size / 2.0 + 10.0) - d.x,
|
||||||
|
(radar_size / -2.0 - 10.0) + d.y,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
angle: to_radians(90.0),
|
||||||
|
dim: [sprite.aspect * size, size],
|
||||||
|
color,
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwSw.to_int(),
|
||||||
|
position: Point2::new(
|
||||||
|
(radar_size / 2.0 + 10.0) - d.x,
|
||||||
|
(radar_size / -2.0 - 10.0) - d.y,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
angle: to_radians(180.0),
|
||||||
|
dim: [sprite.aspect * size, size],
|
||||||
|
color,
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwSe.to_int(),
|
||||||
|
position: Point2::new(
|
||||||
|
(radar_size / 2.0 + 10.0) + d.x,
|
||||||
|
(radar_size / -2.0 - 10.0) - d.y,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
angle: to_radians(270.0),
|
||||||
|
dim: [sprite.aspect * size, size],
|
||||||
|
color,
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwNe.to_int(),
|
||||||
|
position: Point2::new(
|
||||||
|
(radar_size / 2.0 + 10.0) + d.x,
|
||||||
|
(radar_size / -2.0 - 10.0) + d.y,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
angle: to_radians(0.0),
|
||||||
|
dim: [sprite.aspect * size, size],
|
||||||
|
color,
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrow to center of system
|
||||||
|
let q = Point2::new(0.0, 0.0) - self.last_player_position;
|
||||||
|
let m = q.magnitude();
|
||||||
|
if m > 200.0 {
|
||||||
|
let angle = clockwise_angle(&Vector2::new(1.0, 0.0), &q);
|
||||||
|
let position = Point2::new(10.0 + (radar_size / 2.0), -10.0 - (radar_size / 2.0))
|
||||||
|
+ Rotation2::new(angle) * Vector2::new(0.915 * (radar_size / 2.0), 0.0);
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(arrow_sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NwC.to_int(),
|
||||||
|
position: position.into(),
|
||||||
|
angle,
|
||||||
|
dim: [10.0 * sprite.aspect, 10.0],
|
||||||
|
color: [1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)],
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 UiFlyingScene {
|
||||||
|
radar: Radar,
|
||||||
|
status: Status,
|
||||||
|
fps: FpsIndicator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiFlyingScene {
|
||||||
|
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 UiFlyingScene {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
use galactica_system::data::ShipState;
|
||||||
|
use std::f32::consts::TAU;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
vertexbuffer::types::{RadialBarInstance, UiInstance},
|
||||||
|
PositionAnchor, RenderInput, RenderState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) struct Status {}
|
||||||
|
impl Status {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
let max_shields;
|
||||||
|
let current_shields;
|
||||||
|
let current_hull;
|
||||||
|
let max_hull;
|
||||||
|
let player_ship = input
|
||||||
|
.phys_img
|
||||||
|
.get_ship(&galactica_system::phys::PhysSimShipHandle(
|
||||||
|
input.player.ship.unwrap(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match player_ship.ship.get_data().get_state() {
|
||||||
|
ShipState::Dead => {
|
||||||
|
current_shields = 0.0;
|
||||||
|
current_hull = 0.0;
|
||||||
|
max_shields = player_ship
|
||||||
|
.ship
|
||||||
|
.get_data()
|
||||||
|
.get_outfits()
|
||||||
|
.get_shield_strength();
|
||||||
|
max_hull = input
|
||||||
|
.ct
|
||||||
|
.get_ship(player_ship.ship.get_data().get_content())
|
||||||
|
.hull;
|
||||||
|
}
|
||||||
|
ShipState::UnLanding { .. }
|
||||||
|
| ShipState::Landing { .. }
|
||||||
|
| ShipState::Landed { .. }
|
||||||
|
| ShipState::Collapsing { .. }
|
||||||
|
| ShipState::Flying { .. } => {
|
||||||
|
current_shields = player_ship.ship.get_data().get_shields();
|
||||||
|
current_hull = player_ship.ship.get_data().get_hull();
|
||||||
|
max_shields = player_ship
|
||||||
|
.ship
|
||||||
|
.get_data()
|
||||||
|
.get_outfits()
|
||||||
|
.get_shield_strength();
|
||||||
|
max_hull = input
|
||||||
|
.ct
|
||||||
|
.get_ship(player_ship.ship.get_data().get_content())
|
||||||
|
.hull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sprite = input
|
||||||
|
.ct
|
||||||
|
.get_sprite(input.ct.get_sprite_handle("ui::status"));
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::NeNe.to_int(),
|
||||||
|
position: [-10.0, -10.0],
|
||||||
|
angle: 0.0,
|
||||||
|
dim: [sprite.aspect * 200.0, 200.0],
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: [0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
state.push_radialbar_buffer(RadialBarInstance {
|
||||||
|
position: [-19.0, -19.0],
|
||||||
|
anchor: PositionAnchor::NeNe.to_int(),
|
||||||
|
diameter: 182.0,
|
||||||
|
stroke: 5.0,
|
||||||
|
color: [0.3, 0.6, 0.8, 1.0],
|
||||||
|
angle: (current_shields / max_shields) * TAU,
|
||||||
|
});
|
||||||
|
|
||||||
|
state.push_radialbar_buffer(RadialBarInstance {
|
||||||
|
position: [-27.0, -27.0],
|
||||||
|
anchor: PositionAnchor::NeNe.to_int(),
|
||||||
|
diameter: 166.0,
|
||||||
|
stroke: 5.0,
|
||||||
|
color: [0.8, 0.7, 0.5, 1.0],
|
||||||
|
angle: (current_hull / max_hull) * TAU,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
use galactica_content::{Content, SystemObject, SystemObjectHandle};
|
||||||
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
|
||||||
|
use glyphon::{Attrs, TextArea, Weight};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
ui::{
|
||||||
|
manager::{UiScene, UiSceneStepResult, UiScenes},
|
||||||
|
util::{UiSprite, UiTextArea},
|
||||||
|
},
|
||||||
|
RenderInput, RenderState,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::UiOutfitterScene;
|
||||||
|
|
||||||
|
pub struct UiLandedScene {
|
||||||
|
// UI elements
|
||||||
|
description: UiTextArea,
|
||||||
|
title: UiTextArea,
|
||||||
|
frame: UiSprite,
|
||||||
|
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 UiLandedScene {
|
||||||
|
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);
|
||||||
|
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::from(ct, state, &ct.get_ui().landed_planet_desc),
|
||||||
|
title: UiTextArea::from(ct, state, &ct.get_ui().landed_planet_name),
|
||||||
|
|
||||||
|
frame,
|
||||||
|
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.title.set_text(
|
||||||
|
state,
|
||||||
|
&planet.name,
|
||||||
|
Attrs::new()
|
||||||
|
.weight(Weight::BOLD)
|
||||||
|
.family(glyphon::Family::Serif),
|
||||||
|
);
|
||||||
|
self.current_object = Some(planet.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'this> UiScene<'this> for UiLandedScene {
|
||||||
|
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
|
||||||
|
self.button.step(input, state);
|
||||||
|
self.landscape.step(input, state);
|
||||||
|
self.frame.step(input, state);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
new_scene = Some(UiScenes::Outfitter(UiOutfitterScene::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);
|
||||||
|
self.landscape.push_to_buffer(input, state);
|
||||||
|
self.frame.push_to_buffer(input, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_textareas(
|
||||||
|
&'this self,
|
||||||
|
v: &mut Vec<TextArea<'this>>,
|
||||||
|
_input: &RenderInput,
|
||||||
|
state: &RenderState,
|
||||||
|
) {
|
||||||
|
v.push(self.description.get_textarea(state));
|
||||||
|
v.push(self.title.get_textarea(state));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod flying;
|
||||||
|
mod landed;
|
||||||
|
mod outfitter;
|
||||||
|
|
||||||
|
pub use flying::UiFlyingScene;
|
||||||
|
pub use landed::UiLandedScene;
|
||||||
|
pub use outfitter::UiOutfitterScene;
|
|
@ -0,0 +1,129 @@
|
||||||
|
use galactica_content::{Content, SystemObject, SystemObjectHandle, UiPositionAnchor};
|
||||||
|
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::UiLandedScene;
|
||||||
|
|
||||||
|
pub struct UiOutfitterScene {
|
||||||
|
// UI elements
|
||||||
|
se_box: UiSprite,
|
||||||
|
exit_button: UiSprite,
|
||||||
|
exit_text: UiTextArea,
|
||||||
|
|
||||||
|
/// 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 UiOutfitterScene {
|
||||||
|
pub fn new(ct: &Content, state: &mut RenderState) -> Self {
|
||||||
|
let exit_button = UiSprite::from(ct, &ct.get_ui().outfitter_exit_button);
|
||||||
|
let se_box = UiSprite::from(ct, &ct.get_ui().outfitter_se_box);
|
||||||
|
|
||||||
|
let s = Self {
|
||||||
|
// height of element in logical pixels
|
||||||
|
current_object: None,
|
||||||
|
leftclick_down: false,
|
||||||
|
|
||||||
|
exit_text: UiTextArea::new(
|
||||||
|
ct,
|
||||||
|
state,
|
||||||
|
SpriteRect {
|
||||||
|
pos: Point2::new(0.0, 0.0),
|
||||||
|
dim: Vector2::new(200.0, 200.0), // TODO: do this better
|
||||||
|
anchor_self: UiPositionAnchor::Center,
|
||||||
|
anchor_parent: UiPositionAnchor::Center,
|
||||||
|
},
|
||||||
|
Metrics::new(16.0, 18.0),
|
||||||
|
Color::rgb(255, 255, 255),
|
||||||
|
Align::Center,
|
||||||
|
),
|
||||||
|
|
||||||
|
se_box,
|
||||||
|
exit_button,
|
||||||
|
};
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reflow(&mut self, planet: &SystemObject, state: &mut RenderState) {
|
||||||
|
self.exit_text.set_text(
|
||||||
|
state,
|
||||||
|
"Exit",
|
||||||
|
Attrs::new()
|
||||||
|
.weight(Weight::NORMAL)
|
||||||
|
.family(glyphon::Family::SansSerif),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.current_object = Some(planet.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'this> UiScene<'this> for UiOutfitterScene {
|
||||||
|
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
|
||||||
|
self.se_box.step(input, state);
|
||||||
|
self.exit_button.step(input, state);
|
||||||
|
|
||||||
|
let mut new_scene = None;
|
||||||
|
if input.player.input.pressed_leftclick() && !self.leftclick_down {
|
||||||
|
self.leftclick_down = true;
|
||||||
|
if self.exit_button.contains_mouse(input, state) {
|
||||||
|
new_scene = Some(UiScenes::Landed(UiLandedScene::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.se_box.push_to_buffer(input, state);
|
||||||
|
self.exit_button.push_to_buffer(input, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_textareas(
|
||||||
|
&'this self,
|
||||||
|
v: &mut Vec<TextArea<'this>>,
|
||||||
|
_input: &RenderInput,
|
||||||
|
state: &RenderState,
|
||||||
|
) {
|
||||||
|
v.push(self.exit_text.get_textarea(state));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,171 @@
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod textbox;
|
mod textarea;
|
||||||
|
|
||||||
pub use sprite::*;
|
use galactica_content::UiPositionAnchor;
|
||||||
pub use textbox::*;
|
pub(super) use sprite::UiSprite;
|
||||||
|
pub(super) use textarea::UiTextArea;
|
||||||
|
|
||||||
|
use nalgebra::{Point2, Vector2};
|
||||||
|
use winit::dpi::LogicalSize;
|
||||||
|
|
||||||
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
|
/// Represents a rectangular region
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) struct SpriteRect {
|
||||||
|
/// The position of the top-left corner of this rectangle, in fractional units.
|
||||||
|
/// (0.0 is left edge of sprite, 1.0 is right edge)
|
||||||
|
pub pos: Point2<f32>,
|
||||||
|
|
||||||
|
/// The width and height of this rectangle, in fractional units.
|
||||||
|
/// 1.0 will be as tall as the sprite, 0.5 will be half as tall
|
||||||
|
pub dim: Vector2<f32>,
|
||||||
|
|
||||||
|
/// How to compute this rectangle's coordinates relative to its parent
|
||||||
|
pub anchor_self: UiPositionAnchor,
|
||||||
|
|
||||||
|
/// How to compute this rectangle's coordinates relative to its parent
|
||||||
|
pub anchor_parent: UiPositionAnchor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpriteRect {
|
||||||
|
/*
|
||||||
|
pub fn to_centered_relative(&self, parent: SpriteRectCentered) -> SpriteRectCentered {
|
||||||
|
let dim = Vector2::new(self.dim.x * parent.dim.x, self.dim.y * parent.dim.y);
|
||||||
|
|
||||||
|
let pos = Vector2::new(self.pos.x * parent.dim.x, self.pos.y * parent.dim.y);
|
||||||
|
|
||||||
|
let mut zero = match self.anchor_parent {
|
||||||
|
PositionAnchor::Center => parent.pos,
|
||||||
|
|
||||||
|
PositionAnchor::NorthWest => Point2::new(
|
||||||
|
parent.pos.x - (parent.dim.x / 2.0),
|
||||||
|
parent.pos.y + (parent.dim.y / 2.0),
|
||||||
|
),
|
||||||
|
|
||||||
|
PositionAnchor::SouthWest => Point2::new(
|
||||||
|
parent.pos.x - (parent.dim.x / 2.0),
|
||||||
|
parent.pos.y - (parent.dim.y / 2.0),
|
||||||
|
),
|
||||||
|
|
||||||
|
PositionAnchor::NorthEast => Point2::new(
|
||||||
|
parent.pos.x + (parent.dim.x / 2.0),
|
||||||
|
parent.pos.y + (parent.dim.y / 2.0),
|
||||||
|
),
|
||||||
|
|
||||||
|
PositionAnchor::SouthEast => Point2::new(
|
||||||
|
parent.pos.x + (parent.dim.x / 2.0),
|
||||||
|
parent.pos.y - (parent.dim.y / 2.0),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.anchor_self {
|
||||||
|
PositionAnchor::Center => {}
|
||||||
|
|
||||||
|
PositionAnchor::NorthWest => {
|
||||||
|
zero += Vector2::new(dim.x, -dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PositionAnchor::NorthEast => {
|
||||||
|
zero += Vector2::new(-dim.x, -dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PositionAnchor::SouthWest => {
|
||||||
|
zero += Vector2::new(dim.x, dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PositionAnchor::SouthEast => {
|
||||||
|
zero += Vector2::new(-dim.x, dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return SpriteRectCentered {
|
||||||
|
dim,
|
||||||
|
pos: zero + pos,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Convert this rectangle to a centered rectangle.
|
||||||
|
pub fn to_centered(&self, state: &RenderState) -> CenteredSpriteRect {
|
||||||
|
let w: LogicalSize<f32> = state.window_size.to_logical(state.window.scale_factor());
|
||||||
|
let w = Vector2::new(w.width, w.height);
|
||||||
|
|
||||||
|
let mut pos = self.pos;
|
||||||
|
let dim = self.dim;
|
||||||
|
|
||||||
|
// Origin
|
||||||
|
match self.anchor_parent {
|
||||||
|
UiPositionAnchor::Center => {}
|
||||||
|
|
||||||
|
UiPositionAnchor::NorthWest => {
|
||||||
|
pos += Vector2::new(-w.x, w.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiPositionAnchor::SouthWest => {
|
||||||
|
pos += Vector2::new(-w.x, -w.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiPositionAnchor::NorthEast => {
|
||||||
|
pos += Vector2::new(w.x, w.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiPositionAnchor::SouthEast => {
|
||||||
|
pos += Vector2::new(w.x, -w.y) / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset for self dimensions
|
||||||
|
match self.anchor_self {
|
||||||
|
UiPositionAnchor::Center => {}
|
||||||
|
|
||||||
|
UiPositionAnchor::NorthWest => {
|
||||||
|
pos += Vector2::new(dim.x, -dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiPositionAnchor::NorthEast => {
|
||||||
|
pos += Vector2::new(-dim.x, -dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiPositionAnchor::SouthWest => {
|
||||||
|
pos += Vector2::new(dim.x, dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiPositionAnchor::SouthEast => {
|
||||||
|
pos += Vector2::new(dim.x, dim.y) / 2.0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return CenteredSpriteRect { pos, dim };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a rectangular region, in absolute coordinates relative to the screen center.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) struct CenteredSpriteRect {
|
||||||
|
/// The position of the top-left corner of this rectangle, in fractional units.
|
||||||
|
/// (0.0 is left edge of sprite, 1.0 is right edge)
|
||||||
|
pub pos: Point2<f32>,
|
||||||
|
|
||||||
|
/// The width and height of this rectangle, in fractional units.
|
||||||
|
/// 1.0 will be as tall as the sprite, 0.5 will be half as tall
|
||||||
|
pub dim: Vector2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CenteredSpriteRect {
|
||||||
|
pub fn contains_point(&self, pt: Point2<f32>) -> bool {
|
||||||
|
let ne = self.pos + Vector2::new(-self.dim.x, self.dim.y) / 2.0;
|
||||||
|
let sw = self.pos + Vector2::new(self.dim.x, -self.dim.y) / 2.0;
|
||||||
|
return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) trait UiElement {
|
||||||
|
fn push_to_buffer_child(
|
||||||
|
&self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
parent_pos: Point2<f32>,
|
||||||
|
parent_size: Vector2<f32>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,57 +1,118 @@
|
||||||
use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
|
use galactica_content::{Content, SectionEdge, SpriteAutomaton, SpriteHandle, UiSpriteConfig};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
|
use nalgebra::{Point2, Vector2};
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::{CenteredSpriteRect, SpriteRect};
|
||||||
use crate::{ui::manager::MouseEvent, vertexbuffer::types::UiInstance, RenderInput, RenderState};
|
use crate::{vertexbuffer::types::UiInstance, RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub struct UiSprite {
|
||||||
pub struct Sprite {
|
|
||||||
pub anim: SpriteAutomaton,
|
pub anim: SpriteAutomaton,
|
||||||
pub rect: Rect,
|
mask: Option<SpriteHandle>,
|
||||||
pub mask: Option<SpriteHandle>,
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
|
rect: SpriteRect,
|
||||||
has_mouse: bool,
|
has_mouse: bool,
|
||||||
has_click: bool,
|
|
||||||
/// If true, ignore mouse events until click is released
|
on_mouse_enter: Option<SectionEdge>,
|
||||||
waiting_for_release: bool,
|
on_mouse_leave: Option<SectionEdge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sprite {
|
impl UiSprite {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ct: &Content,
|
ct: &Content,
|
||||||
name: String,
|
sprite: SpriteHandle,
|
||||||
sprite: String,
|
mask: Option<SpriteHandle>,
|
||||||
mask: Option<String>,
|
rect: SpriteRect,
|
||||||
rect: Rect,
|
on_mouse_enter: Option<SectionEdge>,
|
||||||
|
on_mouse_leave: Option<SectionEdge>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let sprite_handle = ct.get_sprite_handle(&sprite).unwrap(); // TODO: errors
|
return Self {
|
||||||
let mask = {
|
anim: SpriteAutomaton::new(ct, sprite),
|
||||||
if mask.is_some() {
|
|
||||||
Some(ct.get_sprite_handle(&mask.unwrap()).unwrap())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
anim: SpriteAutomaton::new(&ct, sprite_handle),
|
|
||||||
rect,
|
|
||||||
mask,
|
mask,
|
||||||
|
rect,
|
||||||
has_mouse: false,
|
has_mouse: false,
|
||||||
has_click: false,
|
on_mouse_enter,
|
||||||
waiting_for_release: false,
|
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!")
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::from_with_sprite(ct, ui, ui.sprite.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_with_sprite(ct: &Content, ui: &UiSpriteConfig, sprite: SpriteHandle) -> Self {
|
||||||
|
Self::new(
|
||||||
|
ct,
|
||||||
|
sprite,
|
||||||
|
ui.mask,
|
||||||
|
SpriteRect {
|
||||||
|
pos: ui.rect.pos,
|
||||||
|
dim: ui.rect.dim,
|
||||||
|
anchor_self: ui.rect.anchor_self,
|
||||||
|
anchor_parent: ui.rect.anchor_parent,
|
||||||
|
},
|
||||||
|
ui.on_mouse_enter,
|
||||||
|
ui.on_mouse_leave,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(&mut self, input: &RenderInput, state: &RenderState) {
|
||||||
|
if self.contains_mouse(input, state) && !self.has_mouse && self.on_mouse_enter.is_some() {
|
||||||
|
self.has_mouse = true;
|
||||||
|
self.anim.jump_to(input.ct, self.on_mouse_enter.unwrap())
|
||||||
|
} else if !self.contains_mouse(input, state)
|
||||||
|
&& self.has_mouse
|
||||||
|
&& self.on_mouse_leave.is_some()
|
||||||
|
{
|
||||||
|
self.has_mouse = false;
|
||||||
|
self.anim.jump_to(input.ct, self.on_mouse_leave.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
self.anim.step(input.ct, input.time_since_last_run);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_mouse(&self, input: &RenderInput, state: &RenderState) -> bool {
|
||||||
|
let rect = self.get_rect(state);
|
||||||
|
|
||||||
|
let fac = state.window.scale_factor() as f32;
|
||||||
|
let window_size = Vector2::new(
|
||||||
|
state.window_size.width as f32 / fac,
|
||||||
|
state.window_size.height as f32 / fac,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pos = input.player.input.get_mouse_pos();
|
||||||
|
let mouse_pos = Point2::new(
|
||||||
|
pos.x / fac - window_size.x / 2.0,
|
||||||
|
window_size.y / 2.0 - pos.y / fac,
|
||||||
|
);
|
||||||
|
|
||||||
|
return rect.contains_point(mouse_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_rect(
|
||||||
|
&self,
|
||||||
|
state: &RenderState,
|
||||||
|
//parent: Option<SpriteRectCentered>,
|
||||||
|
) -> CenteredSpriteRect {
|
||||||
|
/*
|
||||||
|
if let Some(parent) = parent {
|
||||||
|
return self.rect.to_centered_relative(parent);
|
||||||
|
} else {
|
||||||
|
return self.rect.to_centered(state);
|
||||||
|
}*/
|
||||||
|
return self.rect.to_centered(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add this image to the gpu sprite buffer
|
||||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
let rect = self.get_rect(state);
|
||||||
|
|
||||||
// TODO: use both dimensions,
|
// TODO: use both dimensions,
|
||||||
// not just height
|
// not just height
|
||||||
let anim_state = self.anim.get_texture_idx();
|
let anim_state = self.anim.get_texture_idx();
|
||||||
|
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: crate::PositionAnchor::CC.to_int(),
|
anchor: crate::PositionAnchor::CC.to_int(),
|
||||||
position: rect.pos.into(),
|
position: rect.pos.into(),
|
||||||
|
@ -64,60 +125,10 @@ impl Sprite {
|
||||||
.mask
|
.mask
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
let sprite = input.ct.get_sprite(x);
|
let sprite = input.ct.get_sprite(x);
|
||||||
let texture_b = sprite.get_first_frame(); // TODO: animate?
|
let texture_b = sprite.get_first_frame(); // ANIMATE
|
||||||
[1, texture_b]
|
[1, texture_b]
|
||||||
})
|
})
|
||||||
.unwrap_or([0, 0]),
|
.unwrap_or([0, 0]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_mouse(&mut self, input: &RenderInput, state: &mut RenderState) -> MouseEvent {
|
|
||||||
let r = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
|
||||||
|
|
||||||
if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
|
|
||||||
self.waiting_for_release = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.waiting_for_release
|
|
||||||
&& self.has_mouse
|
|
||||||
&& !self.has_click
|
|
||||||
&& input.player.input.pressed_leftclick()
|
|
||||||
{
|
|
||||||
self.has_click = true;
|
|
||||||
return MouseEvent::Click;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() {
|
|
||||||
self.has_click = false;
|
|
||||||
return MouseEvent::Release;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release mouse when cursor leaves box
|
|
||||||
if self.has_click && !self.has_mouse {
|
|
||||||
self.has_click = false;
|
|
||||||
return MouseEvent::Release;
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.contains_mouse(input, state) && !self.has_mouse {
|
|
||||||
if input.player.input.pressed_leftclick() {
|
|
||||||
// If we're holding click when the cursor enters,
|
|
||||||
// don't trigger the `Click` event.
|
|
||||||
self.waiting_for_release = true;
|
|
||||||
}
|
|
||||||
self.has_mouse = true;
|
|
||||||
return MouseEvent::Enter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.contains_mouse(input, state) && self.has_mouse {
|
|
||||||
self.waiting_for_release = false;
|
|
||||||
self.has_mouse = false;
|
|
||||||
return MouseEvent::Leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MouseEvent::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
|
||||||
self.anim.step(&input.ct, input.time_since_last_run);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
use galactica_content::{Content, UiTextAlign, UiTextConfig};
|
||||||
|
use glyphon::{cosmic_text::Align, Attrs, Buffer, Color, Metrics, Shaping, TextArea, TextBounds};
|
||||||
|
use nalgebra::Vector2;
|
||||||
|
|
||||||
|
use super::SpriteRect;
|
||||||
|
use crate::RenderState;
|
||||||
|
|
||||||
|
/// Represents a text area inside a sprite.
|
||||||
|
pub(crate) struct UiTextArea {
|
||||||
|
/// Bounds of text area
|
||||||
|
rect: SpriteRect,
|
||||||
|
|
||||||
|
/// Text buffer
|
||||||
|
buffer: Buffer,
|
||||||
|
|
||||||
|
/// Text color
|
||||||
|
color: Color,
|
||||||
|
|
||||||
|
/// Text alignment
|
||||||
|
align: Align,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiTextArea {
|
||||||
|
pub fn new(
|
||||||
|
_ct: &Content,
|
||||||
|
state: &mut RenderState,
|
||||||
|
rect: SpriteRect,
|
||||||
|
text_metrics: Metrics,
|
||||||
|
color: Color,
|
||||||
|
align: Align,
|
||||||
|
) -> Self {
|
||||||
|
let mut s = Self {
|
||||||
|
buffer: Buffer::new(&mut state.text_font_system, text_metrics),
|
||||||
|
rect,
|
||||||
|
align,
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
|
||||||
|
s.buffer.set_size(
|
||||||
|
&mut state.text_font_system,
|
||||||
|
s.rect.dim.x * state.window.scale_factor() as f32,
|
||||||
|
s.rect.dim.y * state.window.scale_factor() as f32,
|
||||||
|
);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(ct: &Content, state: &mut RenderState, ui: &UiTextConfig) -> Self {
|
||||||
|
Self::new(
|
||||||
|
ct,
|
||||||
|
state,
|
||||||
|
SpriteRect {
|
||||||
|
pos: ui.rect.pos,
|
||||||
|
dim: ui.rect.dim,
|
||||||
|
anchor_self: ui.rect.anchor_self,
|
||||||
|
anchor_parent: ui.rect.anchor_parent,
|
||||||
|
},
|
||||||
|
Metrics::new(ui.font_size, ui.line_height),
|
||||||
|
Color::rgb(255, 255, 255),
|
||||||
|
match ui.align {
|
||||||
|
UiTextAlign::Center => Align::Center,
|
||||||
|
UiTextAlign::Left => Align::Left,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_text(&mut self, state: &mut RenderState, text: &str, attrs: Attrs) {
|
||||||
|
self.buffer
|
||||||
|
.set_text(&mut state.text_font_system, text, attrs, Shaping::Advanced);
|
||||||
|
|
||||||
|
for l in &mut self.buffer.lines {
|
||||||
|
l.set_align(Some(self.align));
|
||||||
|
}
|
||||||
|
self.buffer.shape_until_scroll(&mut state.text_font_system);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_textarea(&self, state: &RenderState) -> TextArea {
|
||||||
|
let rect = self.rect.to_centered(state);
|
||||||
|
|
||||||
|
// Glypon works with physical pixels, so we must do some conversion
|
||||||
|
let fac = state.window.scale_factor() as f32;
|
||||||
|
let corner_ne = Vector2::new(
|
||||||
|
(rect.pos.x - rect.dim.x / 2.0) * fac + state.window_size.width as f32 / 2.0,
|
||||||
|
state.window_size.height as f32 / 2.0 - (rect.pos.y * fac + rect.dim.y / 2.0),
|
||||||
|
);
|
||||||
|
let corner_sw = corner_ne + rect.dim * fac;
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
buffer: &self.buffer,
|
||||||
|
top: corner_ne.y,
|
||||||
|
left: corner_ne.x,
|
||||||
|
scale: 1.0,
|
||||||
|
bounds: TextBounds {
|
||||||
|
top: (corner_ne.y) as i32,
|
||||||
|
bottom: (corner_sw.y) as i32,
|
||||||
|
left: (corner_ne.x) as i32,
|
||||||
|
right: (corner_sw.x) as i32,
|
||||||
|
},
|
||||||
|
default_color: self.color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,91 +0,0 @@
|
||||||
use glyphon::{cosmic_text::Align, Attrs, Buffer, Color, Metrics, Shaping, TextArea, TextBounds};
|
|
||||||
use nalgebra::Vector2;
|
|
||||||
|
|
||||||
use super::super::api::{Rect, TextBoxFont, TextBoxJustify};
|
|
||||||
use crate::{RenderInput, RenderState};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TextBox {
|
|
||||||
pub name: String,
|
|
||||||
pub font: TextBoxFont,
|
|
||||||
pub justify: TextBoxJustify,
|
|
||||||
pub rect: Rect,
|
|
||||||
pub buffer: Buffer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextBox {
|
|
||||||
pub fn new(
|
|
||||||
state: &mut RenderState,
|
|
||||||
name: String,
|
|
||||||
font_size: f32,
|
|
||||||
line_height: f32,
|
|
||||||
font: TextBoxFont,
|
|
||||||
justify: TextBoxJustify,
|
|
||||||
rect: Rect,
|
|
||||||
) -> Self {
|
|
||||||
let mut buffer = Buffer::new(
|
|
||||||
&mut state.text_font_system,
|
|
||||||
Metrics::new(font_size, line_height),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Do NOT apply UI scale here, that's only done when we make a TextArea
|
|
||||||
buffer.set_size(&mut state.text_font_system, rect.dim.x, rect.dim.y);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
font,
|
|
||||||
justify,
|
|
||||||
rect,
|
|
||||||
buffer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_text(&mut self, state: &mut RenderState, text: &str) {
|
|
||||||
let mut attrs = Attrs::new();
|
|
||||||
attrs = match self.font {
|
|
||||||
TextBoxFont::Monospace => attrs.family(glyphon::Family::Monospace),
|
|
||||||
TextBoxFont::SansSerif => attrs.family(glyphon::Family::SansSerif),
|
|
||||||
TextBoxFont::Serif => attrs.family(glyphon::Family::Serif),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.buffer
|
|
||||||
.set_text(&mut state.text_font_system, text, attrs, Shaping::Advanced);
|
|
||||||
|
|
||||||
for l in &mut self.buffer.lines {
|
|
||||||
l.set_align(Some(match self.justify {
|
|
||||||
TextBoxJustify::Center => Align::Center,
|
|
||||||
TextBoxJustify::Left => Align::Left,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.buffer.shape_until_scroll(&mut state.text_font_system);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b: 'a> TextBox {
|
|
||||||
pub fn get_textarea(&'b self, state: &RenderState, input: &RenderInput) -> TextArea<'a> {
|
|
||||||
let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
|
||||||
|
|
||||||
// Glypon works with physical pixels, so we must do some conversion
|
|
||||||
let fac = state.window.scale_factor() as f32;
|
|
||||||
let corner_ne = Vector2::new(
|
|
||||||
(rect.pos.x - rect.dim.x / 2.0) * fac + state.window_size.width as f32 / 2.0,
|
|
||||||
state.window_size.height as f32 / 2.0 - (rect.pos.y * fac + rect.dim.y / 2.0),
|
|
||||||
);
|
|
||||||
let corner_sw = corner_ne + rect.dim * fac;
|
|
||||||
|
|
||||||
TextArea {
|
|
||||||
buffer: &self.buffer,
|
|
||||||
top: corner_ne.y,
|
|
||||||
left: corner_ne.x,
|
|
||||||
scale: input.ct.get_config().ui_scale,
|
|
||||||
bounds: TextBounds {
|
|
||||||
top: (corner_ne.y) as i32,
|
|
||||||
bottom: (corner_sw.y) as i32,
|
|
||||||
left: (corner_ne.x) as i32,
|
|
||||||
right: (corner_sw.x) as i32,
|
|
||||||
},
|
|
||||||
default_color: Color::rgb(255, 255, 255),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue