Compare commits
2 Commits
e893da01bc
...
47a73224c6
Author | SHA1 | Date |
---|---|---|
Mark | 47a73224c6 | |
Mark | 7ce169bbea |
|
@ -887,7 +887,9 @@ dependencies = [
|
|||
name = "galactica-util"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nalgebra",
|
||||
"rhai",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -78,7 +78,6 @@ rhai = { version = "1.17.0", features = [
|
|||
"f32_float",
|
||||
"only_i32",
|
||||
"metadata",
|
||||
"sync",
|
||||
"no_custom_syntax",
|
||||
"no_closure",
|
||||
] }
|
||||
|
|
9
TODO.md
9
TODO.md
|
@ -1,18 +1,17 @@
|
|||
# Specific projects
|
||||
|
||||
## Now:
|
||||
- outfitter
|
||||
- Clean up scripting & errors
|
||||
- Clean up sprite content (and content in general)
|
||||
- Flying UI
|
||||
- Fix radar
|
||||
- Mouse colliders
|
||||
- UI captures input?
|
||||
- No UI zoom scroll
|
||||
- Preserve aspect for icons
|
||||
- Check game version in config
|
||||
|
||||
- outfitter
|
||||
|
||||
## Small jobs
|
||||
- Clean up sprite content (and content in general)
|
||||
- Check game version in config
|
||||
- Fix window resizing
|
||||
- Debug: show mouse position
|
||||
- 🌟 Better planet desc formatting
|
||||
|
|
|
@ -1,65 +1,55 @@
|
|||
fn config() {
|
||||
let config = SceneConfig();
|
||||
config.show_starfield(true);
|
||||
config.show_phys(true);
|
||||
return config
|
||||
}
|
||||
|
||||
fn init(state) {
|
||||
let ring = SpriteBuilder(
|
||||
|
||||
conf_set_starfield(true);
|
||||
conf_set_phys(true);
|
||||
|
||||
add_sprite(
|
||||
"ring",
|
||||
"ui::status",
|
||||
Rect(
|
||||
-5.0, -5.0, 100.0, 100.0,
|
||||
SpriteAnchor::NorthEast,
|
||||
SpriteAnchor::NorthEast
|
||||
Anchor::NorthEast,
|
||||
Anchor::NorthEast
|
||||
)
|
||||
);
|
||||
|
||||
let shield = RadialBuilder(
|
||||
add_radialbar(
|
||||
"shield", 2.5,
|
||||
Color(0.3, 0.6, 0.8, 1.0),
|
||||
Rect(
|
||||
-9.5, -9.5, 91.0, 91.0,
|
||||
SpriteAnchor::NorthEast,
|
||||
SpriteAnchor::NorthEast
|
||||
Anchor::NorthEast,
|
||||
Anchor::NorthEast
|
||||
)
|
||||
);
|
||||
shield.set_progress(1.0);
|
||||
|
||||
let hull = RadialBuilder(
|
||||
add_radialbar(
|
||||
"hull", 2.5,
|
||||
Color(0.8, 0.7, 0.5, 1.0),
|
||||
Rect(
|
||||
-13.5, -13.5, 83.0, 83.0,
|
||||
SpriteAnchor::NorthEast,
|
||||
SpriteAnchor::NorthEast
|
||||
Anchor::NorthEast,
|
||||
Anchor::NorthEast
|
||||
)
|
||||
);
|
||||
hull.set_progress(1.0);
|
||||
|
||||
return [
|
||||
ring,
|
||||
shield,
|
||||
hull
|
||||
];
|
||||
}
|
||||
|
||||
fn event(state, event) {
|
||||
if type_of(event) == "PlayerShipStateEvent" {
|
||||
if state.player_ship().is_landed() {
|
||||
return SceneAction::GoTo("landed");
|
||||
go_to_scene("landed");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn step(state, elements) {
|
||||
elements["shield"].set_val(
|
||||
fn step(state) {
|
||||
radialbar_set_val("shield",
|
||||
state.player_ship().get_shields()
|
||||
/ state.player_ship().get_total_shields()
|
||||
);
|
||||
elements["hull"].set_val(
|
||||
radialbar_set_val("hull",
|
||||
state.player_ship().get_hull()
|
||||
/ state.player_ship().get_total_hull()
|
||||
);
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
fn config() {
|
||||
let config = SceneConfig();
|
||||
config.show_starfield(true);
|
||||
config.show_phys(false);
|
||||
return config
|
||||
}
|
||||
|
||||
fn init(state) {
|
||||
let player = state.player_ship();
|
||||
|
||||
let frame = SpriteBuilder(
|
||||
"frame",
|
||||
"ui::planet",
|
||||
conf_set_starfield(true);
|
||||
conf_set_phys(false);
|
||||
|
||||
add_sprite(
|
||||
"button",
|
||||
"ui::planet::button",
|
||||
Rect(
|
||||
0.0, 0.0, 400.0, 297.866,
|
||||
SpriteAnchor::Center,
|
||||
SpriteAnchor::Center
|
||||
99.0, 128.0, 73.898, 18.708,
|
||||
Anchor::NorthWest,
|
||||
Anchor::Center
|
||||
)
|
||||
);
|
||||
|
||||
let landscape = SpriteBuilder(
|
||||
add_sprite(
|
||||
"landscape",
|
||||
{
|
||||
if player.is_landed() {
|
||||
|
@ -29,73 +25,62 @@ fn init(state) {
|
|||
},
|
||||
Rect(
|
||||
-180.0, 142.0, 274.0, 135.0,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::Center
|
||||
Anchor::NorthWest,
|
||||
Anchor::Center
|
||||
)
|
||||
);
|
||||
landscape.set_mask("ui::landscapemask");
|
||||
sprite_set_mask("landscape", "ui::landscapemask");
|
||||
|
||||
let button = SpriteBuilder(
|
||||
"button",
|
||||
"ui::planet::button",
|
||||
add_sprite(
|
||||
"frame",
|
||||
"ui::planet",
|
||||
Rect(
|
||||
99.0, 128.0, 73.898, 18.708,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::Center
|
||||
0.0, 0.0, 400.0, 297.866,
|
||||
Anchor::Center,
|
||||
Anchor::Center
|
||||
)
|
||||
);
|
||||
|
||||
let title = TextBoxBuilder(
|
||||
|
||||
add_textbox(
|
||||
"title", 10.0, 10.0,
|
||||
Rect(
|
||||
-70.79, 138.0, 59.867, 10.0,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::Center
|
||||
Anchor::NorthWest,
|
||||
Anchor::Center
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
textbox_align_center("title");
|
||||
textbox_font_serif("title");
|
||||
textbox_weight_bold("title");
|
||||
if player.is_landed() {
|
||||
title.set_text(player.landed_on().name());
|
||||
} else {
|
||||
title.set_text("");
|
||||
textbox_set_text("title", player.landed_on().name());
|
||||
}
|
||||
title.align_center();
|
||||
title.font_serif();
|
||||
title.weight_bold();
|
||||
|
||||
let desc = TextBoxBuilder(
|
||||
add_textbox(
|
||||
"desc", 7.5, 8.0,
|
||||
Rect(
|
||||
-178.92, -20.3, 343.0, 81.467,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::Center
|
||||
Anchor::NorthWest,
|
||||
Anchor::Center
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
textbox_font_sans("desc");
|
||||
if player.is_landed() {
|
||||
desc.set_text(player.landed_on().desc());
|
||||
} else {
|
||||
desc.set_text("");
|
||||
textbox_set_text("desc", player.landed_on().desc());
|
||||
}
|
||||
desc.font_sansserif();
|
||||
|
||||
return [
|
||||
button,
|
||||
landscape,
|
||||
frame,
|
||||
title,
|
||||
desc,
|
||||
];
|
||||
}
|
||||
|
||||
fn event(state, event) {
|
||||
if type_of(event) == "MouseHoverEvent" {
|
||||
let element = event.element();
|
||||
if element.has_name("button") {
|
||||
if element == "button" {
|
||||
if event.is_enter() {
|
||||
element.take_edge("on:top", 0.1);
|
||||
sprite_take_edge("button", "on:top", 0.1);
|
||||
} else {
|
||||
element.take_edge("off:top", 0.1);
|
||||
sprite_take_edge("button", "off:top", 0.1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
@ -104,19 +89,21 @@ fn event(state, event) {
|
|||
|
||||
if type_of(event) == "MouseClickEvent" {
|
||||
if !event.is_down() {
|
||||
return SceneAction::None;
|
||||
return;
|
||||
}
|
||||
|
||||
let element = event.element();
|
||||
if element.has_name("button") {
|
||||
return SceneAction::GoTo("outfitter");
|
||||
if element == "button" {
|
||||
go_to_scene("outfitter");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if type_of(event) == "PlayerShipStateEvent" {
|
||||
if !state.player_ship().is_landed() {
|
||||
return SceneAction::GoTo("flying");
|
||||
go_to_scene("flying");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,196 +1,174 @@
|
|||
fn config() {
|
||||
let config = SceneConfig();
|
||||
config.show_starfield(true);
|
||||
config.show_phys(false);
|
||||
return config
|
||||
}
|
||||
|
||||
fn init(state) {
|
||||
let se_box = SpriteBuilder(
|
||||
|
||||
conf_set_starfield(true);
|
||||
conf_set_phys(false);
|
||||
|
||||
add_sprite(
|
||||
"se_box",
|
||||
"ui::outfitterbox",
|
||||
Rect(
|
||||
-1.0, -1.0, 202.345, 133.409,
|
||||
SpriteAnchor::SouthWest,
|
||||
SpriteAnchor::SouthWest
|
||||
Anchor::SouthWest,
|
||||
Anchor::SouthWest
|
||||
)
|
||||
);
|
||||
|
||||
let exit_text = TextBoxBuilder(
|
||||
add_textbox(
|
||||
"exit_text", 10.0, 10.0,
|
||||
Rect(
|
||||
122.71, 48.0, 51.0, 12.0,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::SouthWest
|
||||
Anchor::NorthWest,
|
||||
Anchor::SouthWest
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
exit_text.set_text("Exit");
|
||||
exit_text.font_serif();
|
||||
exit_text.align_center();
|
||||
textbox_font_serif("exit_text");
|
||||
textbox_align_center("exit_text");
|
||||
textbox_set_text("exit_text", "Exit");
|
||||
|
||||
let exit_button = SpriteBuilder(
|
||||
add_sprite(
|
||||
"exit_button",
|
||||
"ui::button",
|
||||
Rect(
|
||||
113.35, 52.0, 69.8, 18.924,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::SouthWest
|
||||
Anchor::NorthWest,
|
||||
Anchor::SouthWest
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let ship_bg = SpriteBuilder(
|
||||
add_sprite(
|
||||
"ship_bg",
|
||||
"ui::outfitter-ship-bg",
|
||||
Rect(
|
||||
16.0, -16.0, 190.0, 353.0,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::NorthWest
|
||||
Anchor::NorthWest,
|
||||
Anchor::NorthWest
|
||||
)
|
||||
);
|
||||
|
||||
let ship_thumb = SpriteBuilder(
|
||||
add_sprite(
|
||||
"ship_thumb",
|
||||
"icon::gypsum",
|
||||
Rect(
|
||||
111.0, -95.45, 90.0, 90.0,
|
||||
SpriteAnchor::Center,
|
||||
SpriteAnchor::NorthWest
|
||||
Anchor::Center,
|
||||
Anchor::NorthWest
|
||||
)
|
||||
);
|
||||
|
||||
let ship_name = TextBoxBuilder(
|
||||
add_textbox(
|
||||
"ship_name", 10.0, 10.0,
|
||||
Rect(
|
||||
111.0, -167.27, 145.0, 10.0,
|
||||
SpriteAnchor::Center,
|
||||
SpriteAnchor::NorthWest
|
||||
Anchor::Center,
|
||||
Anchor::NorthWest
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
ship_name.set_text("Hyperion");
|
||||
ship_name.font_serif();
|
||||
ship_name.align_center();
|
||||
textbox_font_serif("ship_name");
|
||||
textbox_align_center("ship_name");
|
||||
textbox_set_text("ship_name", "Hyperion");
|
||||
|
||||
let ship_type = TextBoxBuilder(
|
||||
add_textbox(
|
||||
"ship_type", 7.0, 8.5,
|
||||
Rect(
|
||||
111.0, -178.0, 145.0, 8.5,
|
||||
SpriteAnchor::Center,
|
||||
SpriteAnchor::NorthWest
|
||||
Anchor::Center,
|
||||
Anchor::NorthWest
|
||||
),
|
||||
Color(0.7, 0.7, 0.7, 1.0)
|
||||
);
|
||||
textbox_font_sans("ship_type");
|
||||
textbox_align_center("ship_type");
|
||||
if state.player_ship().is_some() {
|
||||
ship_type.set_text(state.player_ship().name());
|
||||
} else {
|
||||
ship_type.set_text("ERR: SHIP IS NONE");
|
||||
textbox_set_text("ship_type", state.player_ship().name());
|
||||
}
|
||||
ship_type.font_sansserif();
|
||||
ship_type.align_center();
|
||||
|
||||
let ship_stats = TextBoxBuilder(
|
||||
|
||||
add_textbox(
|
||||
"ship_stats", 7.0, 8.5,
|
||||
Rect(
|
||||
38.526, -192.332, 144.948, 154.5,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::NorthWest,
|
||||
Anchor::NorthWest,
|
||||
Anchor::NorthWest,
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
ship_stats.set_text("Earth");
|
||||
ship_stats.font_monospace();
|
||||
textbox_font_mono("ship_stats");
|
||||
textbox_set_text("ship_stats", "Earth");
|
||||
|
||||
|
||||
let outfit_bg = SpriteBuilder(
|
||||
add_sprite(
|
||||
"outfit_bg",
|
||||
"ui::outfitter-outfit-bg",
|
||||
Rect(
|
||||
-16.0, -16.0, 300.0, 480.0,
|
||||
SpriteAnchor::NorthEast,
|
||||
SpriteAnchor::NorthEast
|
||||
Anchor::NorthEast,
|
||||
Anchor::NorthEast
|
||||
)
|
||||
);
|
||||
|
||||
let outfit_thumb = SpriteBuilder(
|
||||
add_sprite(
|
||||
"outfit_thumb",
|
||||
"icon::engine",
|
||||
Rect(
|
||||
-166.0, -109.0, 90.0, 90.0,
|
||||
SpriteAnchor::Center,
|
||||
SpriteAnchor::NorthEast
|
||||
Anchor::Center,
|
||||
Anchor::NorthEast
|
||||
)
|
||||
);
|
||||
|
||||
let outfit_name = TextBoxBuilder(
|
||||
add_textbox(
|
||||
"outfit_name", 16.0, 16.0,
|
||||
Rect(
|
||||
-312.0, -20.0, 200.0, 16.0,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::NorthEast,
|
||||
Anchor::NorthWest,
|
||||
Anchor::NorthEast,
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
outfit_name.set_text("Earth");
|
||||
outfit_name.font_serif();
|
||||
outfit_name.weight_bold();
|
||||
textbox_font_serif("outfit_name");
|
||||
textbox_weight_bold("outfit_name");
|
||||
textbox_set_text("outfit_name", "Earth");
|
||||
|
||||
let outfit_desc = TextBoxBuilder(
|
||||
add_textbox(
|
||||
"outfit_desc", 7.0, 8.5,
|
||||
Rect(
|
||||
-166.0, -219.0, 260.0, 78.0,
|
||||
SpriteAnchor::Center,
|
||||
SpriteAnchor::NorthEast,
|
||||
Anchor::Center,
|
||||
Anchor::NorthEast,
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
outfit_desc.set_text("Earth");
|
||||
outfit_desc.font_serif();
|
||||
textbox_font_serif("outfit_desc");
|
||||
textbox_set_text("outfit_desc", "Earth");
|
||||
|
||||
let outfit_stats = TextBoxBuilder(
|
||||
|
||||
add_textbox(
|
||||
"outfit_stats", 7.0, 8.5,
|
||||
Rect(
|
||||
-295.0, -271.0, 164.0, 216.0,
|
||||
SpriteAnchor::NorthWest,
|
||||
SpriteAnchor::NorthEast,
|
||||
Anchor::NorthWest,
|
||||
Anchor::NorthEast,
|
||||
),
|
||||
Color(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
outfit_stats.set_text("Earth");
|
||||
ship_stats.font_monospace();
|
||||
textbox_font_mono("outfit_stats");
|
||||
textbox_set_text("outfit_stats", "Earth");
|
||||
|
||||
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 event(state, event) {
|
||||
if type_of(event) == "MouseHoverEvent" {
|
||||
let element = event.element();
|
||||
if element.has_name("exit_button") {
|
||||
if element == "exit_button" {
|
||||
if event.is_enter() {
|
||||
element.take_edge("on:top", 0.1);
|
||||
sprite_take_edge("exit_button", "on:top", 0.1);
|
||||
} else {
|
||||
element.take_edge("off:top", 0.1);
|
||||
sprite_take_edge("exit_button", "off:top", 0.1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
@ -199,19 +177,21 @@ fn event(state, event) {
|
|||
|
||||
if type_of(event) == "MouseClickEvent" {
|
||||
if !event.is_down() {
|
||||
return SceneAction::None;
|
||||
return;
|
||||
}
|
||||
|
||||
let element = event.element();
|
||||
if element.has_name("exit_button") {
|
||||
return SceneAction::GoTo("landed");
|
||||
if element == "exit_button" {
|
||||
go_to_scene("landed");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if type_of(event) == "PlayerShipStateEvent" {
|
||||
if !state.player_ship().is_landed() {
|
||||
return SceneAction::GoTo("flying");
|
||||
go_to_scene("flying");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use rhai::AST;
|
|||
pub(crate) mod syntax {
|
||||
use anyhow::{bail, Context, Result};
|
||||
use galactica_packer::SpriteAtlas;
|
||||
use galactica_util::rhai_error_to_anyhow;
|
||||
use rhai::Engine;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
|
@ -65,9 +66,8 @@ pub(crate) mod syntax {
|
|||
for (n, p) in self.ui_scene {
|
||||
ui_scenes.insert(
|
||||
n.clone(),
|
||||
engine
|
||||
.compile_file(content_root.join(p))
|
||||
.with_context(|| format!("while loading scene script `{}`", n))?,
|
||||
rhai_error_to_anyhow(engine.compile_file(content_root.join(p)))
|
||||
.with_context(|| format!("while loading scene script `{n}`"))?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ use galactica_system::data::ShipPersonality;
|
|||
use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepResources};
|
||||
use galactica_util::timing::Timing;
|
||||
use nalgebra::Point2;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct Game {
|
||||
// Core game data
|
||||
ct: Rc<Content>,
|
||||
ct: Arc<Content>,
|
||||
phys_sim: PhysSim,
|
||||
timing: Timing,
|
||||
start_instant: Instant,
|
||||
|
@ -44,7 +44,7 @@ impl<'a> Game {
|
|||
return player;
|
||||
}
|
||||
|
||||
pub fn new(ct: Rc<Content>) -> Self {
|
||||
pub fn new(ct: Arc<Content>) -> Self {
|
||||
let mut phys_sim = PhysSim::new(&ct, SystemHandle { index: 0 });
|
||||
|
||||
let a = phys_sim.add_ship(
|
||||
|
|
|
@ -21,7 +21,7 @@ use nalgebra::Vector2;
|
|||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use winit::{
|
||||
|
@ -113,7 +113,7 @@ fn try_main() -> Result<()> {
|
|||
}
|
||||
|
||||
// TODO: pretty error if missing (also in cli)
|
||||
let content = Rc::new(Content::load_dir(
|
||||
let content = Arc::new(Content::load_dir(
|
||||
PathBuf::from("./content"),
|
||||
PathBuf::from("./assets"),
|
||||
atlas_index,
|
||||
|
@ -128,12 +128,12 @@ fn try_main() -> Result<()> {
|
|||
let mut game = game::Game::new(content.clone());
|
||||
let p = game.make_player();
|
||||
|
||||
let mut player = Rc::new(PlayerAgent::new(p.0));
|
||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
let mut player = Arc::new(PlayerAgent::new(p.0));
|
||||
Arc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
||||
);
|
||||
|
||||
let mut phys_img = Rc::new(PhysImage::new());
|
||||
let mut phys_img = Arc::new(PhysImage::new());
|
||||
let mut last_run = Instant::now();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
|
@ -162,9 +162,9 @@ fn try_main() -> Result<()> {
|
|||
}
|
||||
|
||||
Event::MainEventsCleared => {
|
||||
game.update_player_controls(Rc::get_mut(&mut player).unwrap());
|
||||
game.update_player_controls(Arc::get_mut(&mut player).unwrap());
|
||||
game.step(&phys_img);
|
||||
game.update_image(Rc::get_mut(&mut phys_img).unwrap());
|
||||
game.update_image(Arc::get_mut(&mut phys_img).unwrap());
|
||||
|
||||
// TODO: clean up
|
||||
let player_status = {
|
||||
|
@ -193,10 +193,10 @@ fn try_main() -> Result<()> {
|
|||
};
|
||||
|
||||
// This must be updated BEFORE rendering!
|
||||
Rc::get_mut(&mut player)
|
||||
Arc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.step(&content, player_status);
|
||||
Rc::get_mut(&mut player).unwrap().input.clear_inputs();
|
||||
Arc::get_mut(&mut player).unwrap().input.clear_inputs();
|
||||
gpu.window().request_redraw();
|
||||
}
|
||||
|
||||
|
@ -217,39 +217,39 @@ fn try_main() -> Result<()> {
|
|||
},
|
||||
..
|
||||
} => {
|
||||
Rc::get_mut(&mut player)
|
||||
Arc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_key(state, key);
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
Rc::get_mut(&mut player)
|
||||
Arc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_mouse(position);
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
Rc::get_mut(&mut player)
|
||||
Arc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_click(state, button);
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||
Rc::get_mut(&mut player)
|
||||
Arc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_scroll(delta, phase);
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
gpu.resize(&content);
|
||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
Arc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32
|
||||
/ gpu.window().inner_size().height as f32,
|
||||
);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { .. } => {
|
||||
gpu.resize(&content);
|
||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
Arc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32
|
||||
/ gpu.window().inner_size().height as f32,
|
||||
);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::{iter, rc::Rc};
|
||||
|
||||
use anyhow::Result;
|
||||
use bytemuck;
|
||||
use galactica_content::Content;
|
||||
|
@ -7,6 +5,7 @@ use galactica_system::data::ShipState;
|
|||
use galactica_util::to_radians;
|
||||
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
||||
use nalgebra::{Point2, Point3};
|
||||
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
||||
use wgpu;
|
||||
use winit;
|
||||
|
||||
|
@ -16,7 +15,7 @@ use crate::{
|
|||
shaderprocessor::preprocess_shader,
|
||||
starfield::Starfield,
|
||||
texturearray::TextureArray,
|
||||
ui::UiManager,
|
||||
ui::UiScriptExecutor,
|
||||
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
||||
RenderInput, RenderState, VertexBuffers,
|
||||
};
|
||||
|
@ -33,12 +32,12 @@ pub struct GPUState {
|
|||
pub(crate) starfield: Starfield,
|
||||
pub(crate) texture_array: TextureArray,
|
||||
pub(crate) state: RenderState,
|
||||
pub(crate) ui: UiManager,
|
||||
pub(crate) ui: UiScriptExecutor,
|
||||
}
|
||||
|
||||
impl GPUState {
|
||||
/// Make a new GPUState that draws on `window`
|
||||
pub async fn new(window: winit::window::Window, ct: Rc<Content>) -> Result<Self> {
|
||||
pub async fn new(window: winit::window::Window, ct: Arc<Content>) -> Result<Self> {
|
||||
let window_size = window.inner_size();
|
||||
let window_aspect = window_size.width as f32 / window_size.height as f32;
|
||||
|
||||
|
@ -222,14 +221,14 @@ impl GPUState {
|
|||
window_aspect,
|
||||
global_uniform,
|
||||
vertex_buffers,
|
||||
text_renderer,
|
||||
text_atlas,
|
||||
text_cache,
|
||||
text_font_system,
|
||||
text_renderer,
|
||||
text_font_system: Rc::new(RefCell::new(text_font_system)),
|
||||
};
|
||||
|
||||
return Ok(Self {
|
||||
ui: UiManager::new(ct, &mut state),
|
||||
ui: UiScriptExecutor::new(ct, &mut state),
|
||||
device,
|
||||
config,
|
||||
surface,
|
||||
|
@ -278,7 +277,7 @@ impl GPUState {
|
|||
|
||||
/// Main render function. Draws sprites on a window.
|
||||
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
||||
let input = Rc::new(input);
|
||||
let input = Arc::new(input);
|
||||
|
||||
// Update global values
|
||||
self.state.queue.write_buffer(
|
||||
|
@ -333,7 +332,9 @@ impl GPUState {
|
|||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
if self.ui.get_config().show_phys {
|
||||
let config = self.ui.get_config();
|
||||
|
||||
if config.show_phys {
|
||||
// Create sprite instances
|
||||
|
||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||
|
@ -350,14 +351,14 @@ impl GPUState {
|
|||
self.push_effects(&input, (clip_ne, clip_sw));
|
||||
}
|
||||
|
||||
self.ui.draw(input.clone(), &mut self.state).unwrap();
|
||||
self.ui.draw(&mut self.state, input.clone()).unwrap();
|
||||
|
||||
// These should match the indices in each shader,
|
||||
// and should each have a corresponding bind group layout.
|
||||
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
||||
render_pass.set_bind_group(1, &self.state.global_uniform.bind_group, &[]);
|
||||
|
||||
if self.ui.get_config().show_starfield {
|
||||
if config.show_starfield {
|
||||
// Starfield pipeline
|
||||
self.state
|
||||
.vertex_buffers
|
||||
|
@ -371,7 +372,7 @@ impl GPUState {
|
|||
);
|
||||
}
|
||||
|
||||
if self.ui.get_config().show_phys {
|
||||
if config.show_phys {
|
||||
// Sprite pipeline
|
||||
self.state
|
||||
.vertex_buffers
|
||||
|
@ -410,27 +411,30 @@ impl GPUState {
|
|||
0..self.state.get_radialbar_counter(),
|
||||
);
|
||||
|
||||
let textareas = self.ui.get_textareas(&input, &self.state);
|
||||
self.state
|
||||
.text_renderer
|
||||
.prepare(
|
||||
&self.device,
|
||||
&self.state.queue,
|
||||
&mut self.state.text_font_system,
|
||||
&mut self.state.text_atlas,
|
||||
Resolution {
|
||||
width: self.state.window_size.width,
|
||||
height: self.state.window_size.height,
|
||||
},
|
||||
textareas,
|
||||
&mut self.state.text_cache,
|
||||
)
|
||||
.unwrap();
|
||||
{
|
||||
self.state
|
||||
.text_renderer
|
||||
.prepare(
|
||||
&self.device,
|
||||
&self.state.queue,
|
||||
&mut self.state.text_font_system.borrow_mut(),
|
||||
&mut self.state.text_atlas,
|
||||
Resolution {
|
||||
width: self.state.window_size.width,
|
||||
height: self.state.window_size.height,
|
||||
},
|
||||
(*self.ui.state)
|
||||
.borrow_mut()
|
||||
.get_textareas(&input, &self.state.window),
|
||||
&mut self.state.text_cache,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.state
|
||||
.text_renderer
|
||||
.render(&self.state.text_atlas, &mut render_pass)
|
||||
.unwrap();
|
||||
self.state
|
||||
.text_renderer
|
||||
.render(&mut self.state.text_atlas, &mut render_pass)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// begin_render_pass borrows encoder mutably,
|
||||
// so we need to drop it before calling finish.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::rc::Rc;
|
||||
use wgpu;
|
||||
|
||||
use crate::vertexbuffer::VertexBuffer;
|
||||
|
@ -16,7 +15,7 @@ pub struct PipelineBuilder<'a> {
|
|||
// These must be provided
|
||||
shader: Option<&'a str>,
|
||||
format: Option<wgpu::TextureFormat>,
|
||||
vertex_buffer: Option<&'a Rc<VertexBuffer>>,
|
||||
vertex_buffer: Option<&'a VertexBuffer>,
|
||||
}
|
||||
|
||||
impl<'a> PipelineBuilder<'a> {
|
||||
|
@ -49,7 +48,7 @@ impl<'a> PipelineBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn set_vertex_buffer(mut self, vertex_buffer: &'a Rc<VertexBuffer>) -> Self {
|
||||
pub fn set_vertex_buffer(mut self, vertex_buffer: &'a VertexBuffer) -> Self {
|
||||
self.vertex_buffer = Some(vertex_buffer);
|
||||
self
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use galactica_content::{Content, SystemHandle};
|
||||
use galactica_playeragent::PlayerAgent;
|
||||
|
@ -13,7 +13,7 @@ pub struct RenderInput {
|
|||
pub camera_pos: Vector2<f32>,
|
||||
|
||||
/// Player ship data
|
||||
pub player: Rc<PlayerAgent>,
|
||||
pub player: Arc<PlayerAgent>,
|
||||
|
||||
/// The system we're currently in
|
||||
pub current_system: SystemHandle,
|
||||
|
@ -22,7 +22,7 @@ pub struct RenderInput {
|
|||
pub camera_zoom: f32,
|
||||
|
||||
/// The world state to render
|
||||
pub phys_img: Rc<PhysImage>,
|
||||
pub phys_img: Arc<PhysImage>,
|
||||
|
||||
// TODO: handle overflow. is it a problem?
|
||||
/// The current time, in seconds
|
||||
|
@ -32,7 +32,7 @@ pub struct RenderInput {
|
|||
pub time_since_last_run: f32,
|
||||
|
||||
/// Game content
|
||||
pub ct: Rc<Content>,
|
||||
pub ct: Arc<Content>,
|
||||
|
||||
/// Time we spent in each part of the game loop
|
||||
pub timing: Timing,
|
||||
|
|
|
@ -3,7 +3,7 @@ use galactica_util::constants::{
|
|||
OBJECT_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT,
|
||||
};
|
||||
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use wgpu::BufferAddress;
|
||||
use winit::window::Window;
|
||||
|
||||
|
@ -26,10 +26,10 @@ pub(crate) struct VertexBuffers {
|
|||
starfield_counter: BufferAddress,
|
||||
starfield_limit: BufferAddress,
|
||||
|
||||
object: Rc<VertexBuffer>,
|
||||
starfield: Rc<VertexBuffer>,
|
||||
ui: Rc<VertexBuffer>,
|
||||
radialbar: Rc<VertexBuffer>,
|
||||
object: VertexBuffer,
|
||||
starfield: VertexBuffer,
|
||||
ui: VertexBuffer,
|
||||
radialbar: VertexBuffer,
|
||||
}
|
||||
|
||||
impl<'a> VertexBuffers {
|
||||
|
@ -41,53 +41,53 @@ impl<'a> VertexBuffers {
|
|||
starfield_counter: 0,
|
||||
starfield_limit: ct.get_config().starfield_instance_limit,
|
||||
|
||||
object: Rc::new(VertexBuffer::new::<TexturedVertex, ObjectInstance>(
|
||||
object: VertexBuffer::new::<TexturedVertex, ObjectInstance>(
|
||||
"object",
|
||||
&device,
|
||||
Some(SPRITE_VERTICES),
|
||||
Some(SPRITE_INDICES),
|
||||
OBJECT_SPRITE_INSTANCE_LIMIT,
|
||||
)),
|
||||
),
|
||||
|
||||
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
|
||||
starfield: VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
|
||||
"starfield",
|
||||
&device,
|
||||
Some(SPRITE_VERTICES),
|
||||
Some(SPRITE_INDICES),
|
||||
ct.get_config().starfield_instance_limit,
|
||||
)),
|
||||
),
|
||||
|
||||
ui: Rc::new(VertexBuffer::new::<TexturedVertex, UiInstance>(
|
||||
ui: VertexBuffer::new::<TexturedVertex, UiInstance>(
|
||||
"ui",
|
||||
&device,
|
||||
Some(SPRITE_VERTICES),
|
||||
Some(SPRITE_INDICES),
|
||||
UI_SPRITE_INSTANCE_LIMIT,
|
||||
)),
|
||||
),
|
||||
|
||||
radialbar: Rc::new(VertexBuffer::new::<TexturedVertex, RadialBarInstance>(
|
||||
radialbar: VertexBuffer::new::<TexturedVertex, RadialBarInstance>(
|
||||
"radial bar",
|
||||
&device,
|
||||
Some(SPRITE_VERTICES),
|
||||
Some(SPRITE_INDICES),
|
||||
10,
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ui(&'a self) -> &'a Rc<VertexBuffer> {
|
||||
pub fn get_ui(&'a self) -> &'a VertexBuffer {
|
||||
&self.ui
|
||||
}
|
||||
|
||||
pub fn get_object(&'a self) -> &'a Rc<VertexBuffer> {
|
||||
pub fn get_object(&'a self) -> &'a VertexBuffer {
|
||||
&self.object
|
||||
}
|
||||
|
||||
pub fn get_radialbar(&'a self) -> &'a Rc<VertexBuffer> {
|
||||
pub fn get_radialbar(&'a self) -> &'a VertexBuffer {
|
||||
&self.radialbar
|
||||
}
|
||||
|
||||
pub fn get_starfield(&'a self) -> &'a Rc<VertexBuffer> {
|
||||
pub fn get_starfield(&'a self) -> &'a VertexBuffer {
|
||||
&self.starfield
|
||||
}
|
||||
}
|
||||
|
@ -102,10 +102,10 @@ pub(crate) struct RenderState {
|
|||
pub global_uniform: GlobalUniform,
|
||||
pub vertex_buffers: VertexBuffers,
|
||||
|
||||
pub text_font_system: FontSystem,
|
||||
pub text_font_system: Rc<RefCell<FontSystem>>,
|
||||
pub text_renderer: TextRenderer,
|
||||
pub text_cache: SwashCache,
|
||||
pub text_atlas: TextAtlas,
|
||||
pub text_renderer: TextRenderer,
|
||||
}
|
||||
|
||||
impl RenderState {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
use rhai::plugin::*;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Anchor {
|
||||
Center,
|
||||
NorthWest,
|
||||
SouthWest,
|
||||
NorthEast,
|
||||
SouthEast,
|
||||
}
|
||||
|
||||
#[export_module]
|
||||
pub mod anchor_mod {
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const Center: Anchor = Anchor::Center;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const NorthWest: Anchor = Anchor::NorthWest;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const NorthEast: Anchor = Anchor::NorthEast;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const SouthWest: Anchor = Anchor::SouthWest;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const SouthEast: Anchor = Anchor::SouthEast;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
use rhai::{CustomType, TypeBuilder};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SceneConfig {
|
||||
pub show_phys: bool,
|
||||
pub show_starfield: bool,
|
||||
}
|
||||
|
||||
impl SceneConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
show_phys: false,
|
||||
show_starfield: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomType for SceneConfig {
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("SceneConfig")
|
||||
.with_fn("SceneConfig", Self::new)
|
||||
.with_fn("show_phys", |s: &mut Self, x: bool| s.show_phys = x)
|
||||
.with_fn("show_starfield", |s: &mut Self, x: bool| {
|
||||
s.show_starfield = x
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
use rhai::{CustomType, TypeBuilder};
|
||||
|
||||
use super::SpriteElement;
|
||||
use rhai::{CustomType, ImmutableString, TypeBuilder};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MouseClickEvent {
|
||||
pub down: bool,
|
||||
pub element: SpriteElement,
|
||||
pub element: ImmutableString,
|
||||
}
|
||||
|
||||
impl CustomType for MouseClickEvent {
|
||||
|
@ -20,7 +18,7 @@ impl CustomType for MouseClickEvent {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct MouseHoverEvent {
|
||||
pub enter: bool,
|
||||
pub element: SpriteElement,
|
||||
pub element: ImmutableString,
|
||||
}
|
||||
|
||||
impl CustomType for MouseHoverEvent {
|
||||
|
|
|
@ -1,28 +1,14 @@
|
|||
mod anchor;
|
||||
mod color;
|
||||
mod config;
|
||||
mod event;
|
||||
mod radialbuilder;
|
||||
mod radialelement;
|
||||
mod rect;
|
||||
mod sceneaction;
|
||||
mod spritebuilder;
|
||||
mod spriteelement;
|
||||
mod state;
|
||||
mod textboxbuilder;
|
||||
mod util;
|
||||
|
||||
pub use anchor::*;
|
||||
pub use color::*;
|
||||
pub use config::*;
|
||||
pub use event::*;
|
||||
pub use radialbuilder::*;
|
||||
pub use radialelement::*;
|
||||
pub use rect::*;
|
||||
pub use sceneaction::*;
|
||||
pub use spritebuilder::*;
|
||||
pub use spriteelement::*;
|
||||
pub use state::*;
|
||||
pub use textboxbuilder::*;
|
||||
pub use util::*;
|
||||
|
||||
use rhai::{exported_module, Engine};
|
||||
|
||||
|
@ -31,25 +17,15 @@ pub fn register_into_engine(engine: &mut Engine) {
|
|||
// Helpers
|
||||
.build_type::<Rect>()
|
||||
.build_type::<Color>()
|
||||
.build_type::<SceneConfig>()
|
||||
// State
|
||||
.build_type::<State>()
|
||||
.build_type::<ShipState>()
|
||||
.build_type::<SystemObjectState>()
|
||||
// Builders
|
||||
.build_type::<RadialBuilder>()
|
||||
.build_type::<SpriteBuilder>()
|
||||
.build_type::<TextBoxBuilder>()
|
||||
// Elements
|
||||
.build_type::<SpriteElement>()
|
||||
.build_type::<RadialElement>()
|
||||
// Events
|
||||
.build_type::<MouseClickEvent>()
|
||||
.build_type::<MouseHoverEvent>()
|
||||
.build_type::<PlayerShipStateEvent>()
|
||||
// Larger modules
|
||||
.register_type_with_name::<SpriteAnchor>("SpriteAnchor")
|
||||
.register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into())
|
||||
.register_type_with_name::<SceneAction>("SceneAction")
|
||||
.register_static_module("SceneAction", exported_module!(sceneaction_mod).into());
|
||||
// Bigger modules
|
||||
.register_type_with_name::<Anchor>("Anchor")
|
||||
.register_static_module("Anchor", exported_module!(anchor_mod).into());
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
use nalgebra::clamp;
|
||||
use rhai::{CustomType, ImmutableString, TypeBuilder};
|
||||
|
||||
use super::{color::Color, Rect};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RadialBuilder {
|
||||
pub name: ImmutableString,
|
||||
pub rect: Rect,
|
||||
|
||||
pub stroke: f32,
|
||||
pub color: Color,
|
||||
pub progress: f32,
|
||||
}
|
||||
|
||||
impl RadialBuilder {
|
||||
pub fn new(name: ImmutableString, stroke: f32, color: Color, rect: Rect) -> Self {
|
||||
Self {
|
||||
name,
|
||||
rect,
|
||||
stroke,
|
||||
color,
|
||||
progress: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_progress(&mut self, progress: f32) {
|
||||
self.progress = clamp(progress, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomType for RadialBuilder {
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("RadialBuilder")
|
||||
.with_fn("RadialBuilder", Self::new)
|
||||
.with_fn("set_progress", Self::set_progress);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
use galactica_content::Content;
|
||||
use rhai::{CustomType, TypeBuilder};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::ui::util::RadialBar;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RadialElement {
|
||||
pub bar: Rc<RefCell<RadialBar>>,
|
||||
pub ct: Rc<Content>,
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
unsafe impl Send for RadialElement {}
|
||||
unsafe impl Sync for RadialElement {}
|
||||
|
||||
impl RadialElement {
|
||||
pub fn new(ct: Rc<Content>, bar: Rc<RefCell<RadialBar>>) -> Self {
|
||||
Self { ct, bar }
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomType for RadialElement {
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("RadialElement")
|
||||
.with_fn("set_val", |s: &mut Self, val: f32| {
|
||||
s.bar.borrow_mut().set_val(val)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,27 +1,20 @@
|
|||
use nalgebra::{Point2, Vector2};
|
||||
use rhai::{CustomType, TypeBuilder};
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::{dpi::LogicalSize, window::Window};
|
||||
|
||||
use super::SpriteAnchor;
|
||||
use super::Anchor;
|
||||
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,
|
||||
pub anchor_self: Anchor,
|
||||
pub anchor_parent: Anchor,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn new(
|
||||
x: f32,
|
||||
y: f32,
|
||||
w: f32,
|
||||
h: f32,
|
||||
anchor_self: SpriteAnchor,
|
||||
anchor_parent: SpriteAnchor,
|
||||
) -> Self {
|
||||
pub fn new(x: f32, y: f32, w: f32, h: f32, anchor_self: Anchor, anchor_parent: Anchor) -> Self {
|
||||
Self {
|
||||
pos: Point2::new(x, y),
|
||||
dim: Vector2::new(w, h),
|
||||
|
@ -30,8 +23,8 @@ impl Rect {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_centered(&self, state: &RenderState, ui_scale: f32) -> CenteredRect {
|
||||
let w: LogicalSize<f32> = state.window_size.to_logical(state.window.scale_factor());
|
||||
pub fn to_centered(&self, window: &Window, ui_scale: f32) -> CenteredRect {
|
||||
let w: LogicalSize<f32> = window.inner_size().to_logical(window.scale_factor());
|
||||
let w = Vector2::new(w.width, w.height);
|
||||
|
||||
let mut pos = self.pos * ui_scale;
|
||||
|
@ -39,42 +32,42 @@ impl Rect {
|
|||
|
||||
// Origin
|
||||
match self.anchor_parent {
|
||||
SpriteAnchor::Center => {}
|
||||
Anchor::Center => {}
|
||||
|
||||
SpriteAnchor::NorthWest => {
|
||||
Anchor::NorthWest => {
|
||||
pos += Vector2::new(-w.x, w.y) / 2.0;
|
||||
}
|
||||
|
||||
SpriteAnchor::SouthWest => {
|
||||
Anchor::SouthWest => {
|
||||
pos += Vector2::new(-w.x, -w.y) / 2.0;
|
||||
}
|
||||
|
||||
SpriteAnchor::NorthEast => {
|
||||
Anchor::NorthEast => {
|
||||
pos += Vector2::new(w.x, w.y) / 2.0;
|
||||
}
|
||||
|
||||
SpriteAnchor::SouthEast => {
|
||||
Anchor::SouthEast => {
|
||||
pos += Vector2::new(w.x, -w.y) / 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Offset for self dimensions
|
||||
match self.anchor_self {
|
||||
SpriteAnchor::Center => {}
|
||||
Anchor::Center => {}
|
||||
|
||||
SpriteAnchor::NorthWest => {
|
||||
Anchor::NorthWest => {
|
||||
pos += Vector2::new(dim.x, -dim.y) / 2.0;
|
||||
}
|
||||
|
||||
SpriteAnchor::NorthEast => {
|
||||
Anchor::NorthEast => {
|
||||
pos += Vector2::new(-dim.x, -dim.y) / 2.0;
|
||||
}
|
||||
|
||||
SpriteAnchor::SouthWest => {
|
||||
Anchor::SouthWest => {
|
||||
pos += Vector2::new(dim.x, dim.y) / 2.0;
|
||||
}
|
||||
|
||||
SpriteAnchor::SouthEast => {
|
||||
Anchor::SouthEast => {
|
||||
pos += Vector2::new(-dim.x, dim.y) / 2.0;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
use rhai::plugin::*;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SceneAction {
|
||||
None,
|
||||
GoTo(String),
|
||||
}
|
||||
|
||||
#[export_module]
|
||||
pub mod sceneaction_mod {
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const None: SceneAction = SceneAction::None;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GoTo(scene: String) -> SceneAction {
|
||||
SceneAction::GoTo(scene)
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
use rhai::{CustomType, ImmutableString, TypeBuilder};
|
||||
|
||||
use super::Rect;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpriteBuilder {
|
||||
pub name: ImmutableString,
|
||||
pub rect: Rect,
|
||||
pub sprite: ImmutableString,
|
||||
pub mask: Option<ImmutableString>,
|
||||
}
|
||||
|
||||
impl SpriteBuilder {
|
||||
pub fn new(name: ImmutableString, sprite: ImmutableString, rect: Rect) -> Self {
|
||||
Self {
|
||||
name,
|
||||
rect,
|
||||
sprite,
|
||||
mask: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mask(&mut self, mask: ImmutableString) {
|
||||
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,75 +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>,
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
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) {
|
||||
if self.sprite.as_ref().borrow().anim.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sprite_handle = self
|
||||
.sprite
|
||||
.as_ref()
|
||||
.borrow()
|
||||
.anim
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.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
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.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);
|
||||
}
|
||||
}
|
|
@ -5,14 +5,14 @@ use galactica_system::{
|
|||
};
|
||||
use log::error;
|
||||
use rhai::{CustomType, TypeBuilder};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::RenderInput;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShipState {
|
||||
ship: Option<PhysSimShipHandle>,
|
||||
input: Rc<RenderInput>,
|
||||
input: Arc<RenderInput>,
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
|
@ -94,7 +94,7 @@ impl CustomType for ShipState {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct SystemObjectState {
|
||||
object: Option<SystemObjectHandle>,
|
||||
input: Rc<RenderInput>,
|
||||
input: Arc<RenderInput>,
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
|
@ -152,7 +152,7 @@ impl CustomType for SystemObjectState {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State {
|
||||
input: Rc<RenderInput>,
|
||||
input: Arc<RenderInput>,
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
|
@ -160,7 +160,7 @@ unsafe impl Send for State {}
|
|||
unsafe impl Sync for State {}
|
||||
|
||||
impl State {
|
||||
pub fn new(input: Rc<RenderInput>) -> Self {
|
||||
pub fn new(input: Arc<RenderInput>) -> Self {
|
||||
Self { input }
|
||||
}
|
||||
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
use glyphon::{cosmic_text::Align, Attrs, AttrsOwned, FamilyOwned, Style, Weight};
|
||||
use rhai::{CustomType, ImmutableString, TypeBuilder};
|
||||
|
||||
use super::{Color, Rect};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextBoxBuilder {
|
||||
pub name: ImmutableString,
|
||||
pub font_size: f32,
|
||||
pub line_height: f32,
|
||||
pub rect: Rect,
|
||||
pub text: ImmutableString,
|
||||
pub color: Color,
|
||||
|
||||
pub attrs: AttrsOwned,
|
||||
pub justify: Align,
|
||||
}
|
||||
|
||||
impl TextBoxBuilder {
|
||||
pub fn new(
|
||||
name: ImmutableString,
|
||||
font_size: f32,
|
||||
line_height: f32,
|
||||
rect: Rect,
|
||||
color: Color,
|
||||
) -> Self {
|
||||
Self {
|
||||
color,
|
||||
name,
|
||||
font_size,
|
||||
line_height,
|
||||
rect,
|
||||
text: ImmutableString::new(),
|
||||
attrs: AttrsOwned::new(Attrs::new()),
|
||||
justify: Align::Left,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: ImmutableString) {
|
||||
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)
|
||||
.with_fn("align_left", |s: &mut Self| s.justify = Align::Left)
|
||||
.with_fn("align_right", |s: &mut Self| s.justify = Align::Right)
|
||||
.with_fn("align_justify", |s: &mut Self| s.justify = Align::Justified)
|
||||
.with_fn("align_center", |s: &mut Self| s.justify = Align::Center)
|
||||
.with_fn("weight_bold", |s: &mut Self| s.attrs.weight = Weight::BOLD)
|
||||
.with_fn("weight_normal", |s: &mut Self| {
|
||||
s.attrs.weight = Weight::NORMAL
|
||||
})
|
||||
.with_fn("font_serif", |s: &mut Self| {
|
||||
s.attrs.family_owned = FamilyOwned::Serif
|
||||
})
|
||||
.with_fn("font_sansserif", |s: &mut Self| {
|
||||
s.attrs.family_owned = FamilyOwned::SansSerif
|
||||
})
|
||||
.with_fn("font_monospace", |s: &mut Self| {
|
||||
s.attrs.family_owned = FamilyOwned::Monospace
|
||||
})
|
||||
.with_fn("style_normal", |s: &mut Self| s.attrs.style = Style::Normal)
|
||||
.with_fn("style_italic", |s: &mut Self| s.attrs.style = Style::Italic);
|
||||
}
|
||||
}
|
|
@ -1,28 +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;
|
||||
}
|
|
@ -0,0 +1,567 @@
|
|||
use anyhow::{Context, Result};
|
||||
use galactica_content::{resolve_edge_as_edge, Content};
|
||||
use galactica_system::phys::PhysSimShipHandle;
|
||||
use galactica_util::rhai_error_to_anyhow;
|
||||
use glyphon::{cosmic_text::Align, FamilyOwned, FontSystem, Style, Weight};
|
||||
use log::{debug, error};
|
||||
use rhai::{Dynamic, Engine, ImmutableString, Scope};
|
||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
|
||||
|
||||
use super::{
|
||||
api::{self, Color, MouseClickEvent, MouseHoverEvent, PlayerShipStateEvent, Rect},
|
||||
event::Event,
|
||||
util::{RadialBar, Sprite, TextBox},
|
||||
UiConfig, UiElement, UiState,
|
||||
};
|
||||
use crate::{ui::api::State, RenderInput, RenderState};
|
||||
|
||||
pub(crate) struct UiScriptExecutor {
|
||||
engine: Engine,
|
||||
scope: Scope<'static>,
|
||||
|
||||
pub state: Rc<RefCell<UiState>>,
|
||||
|
||||
last_player_state: u32,
|
||||
last_scene: Option<ImmutableString>,
|
||||
}
|
||||
|
||||
impl UiScriptExecutor {
|
||||
pub fn new(ct: Arc<Content>, state: &mut RenderState) -> Self {
|
||||
let scope = Scope::new();
|
||||
let elements = Rc::new(RefCell::new(UiState::new(ct.clone(), state)));
|
||||
|
||||
let mut engine = Engine::new_raw();
|
||||
api::register_into_engine(&mut engine);
|
||||
Self::register_api(
|
||||
ct.clone(),
|
||||
state.text_font_system.clone(),
|
||||
elements.clone(),
|
||||
&mut engine,
|
||||
);
|
||||
|
||||
Self {
|
||||
engine,
|
||||
scope,
|
||||
state: elements,
|
||||
last_scene: None,
|
||||
last_player_state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> UiConfig {
|
||||
(*self.state).borrow().config.clone()
|
||||
}
|
||||
|
||||
/// Change the current scene
|
||||
pub fn set_scene(&mut self, input: Arc<RenderInput>) -> Result<()> {
|
||||
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||
if self.last_scene == current_scene {
|
||||
return Ok(());
|
||||
}
|
||||
self.last_scene = current_scene.clone();
|
||||
|
||||
if current_scene.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!(
|
||||
"switched to {}, running `init()`",
|
||||
current_scene.as_ref().unwrap()
|
||||
);
|
||||
|
||||
self.scope.clear();
|
||||
|
||||
// Drop this right away, since all script calls borrow elm mutably.
|
||||
let mut elm = self.state.borrow_mut();
|
||||
elm.clear();
|
||||
drop(elm);
|
||||
let ct = (*self.state).borrow().ct.clone();
|
||||
|
||||
rhai_error_to_anyhow(
|
||||
self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
ct.get_config()
|
||||
.ui_scenes
|
||||
.get(current_scene.as_ref().unwrap().as_str())
|
||||
.unwrap(),
|
||||
"init",
|
||||
(State::new(input.clone()),),
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while running `init()`"))
|
||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Draw all ui elements
|
||||
pub fn draw(&mut self, state: &mut RenderState, input: Arc<RenderInput>) -> Result<()> {
|
||||
let ct = (*self.state).borrow().ct.clone();
|
||||
|
||||
// Initialize start scene if we haven't yet
|
||||
if (*self.state).borrow().get_scene().is_none() {
|
||||
(*self.state)
|
||||
.borrow_mut()
|
||||
.set_scene(ImmutableString::from(&ct.get_config().start_ui_scene));
|
||||
}
|
||||
self.set_scene(input.clone())?;
|
||||
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||
|
||||
(*self.state).borrow_mut().step(state, input.clone());
|
||||
|
||||
// Run step() (if it is defined)
|
||||
|
||||
let ast = ct
|
||||
.get_config()
|
||||
.ui_scenes
|
||||
.get(current_scene.as_ref().unwrap().as_str())
|
||||
.unwrap();
|
||||
if ast.iter_functions().any(|x| x.name == "step") {
|
||||
rhai_error_to_anyhow(self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
ast,
|
||||
"step",
|
||||
(State::new(input.clone()),),
|
||||
))
|
||||
.with_context(|| format!("while calling `step()`"))
|
||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
||||
}
|
||||
|
||||
// Send player state change events
|
||||
if {
|
||||
let player = input.player.ship;
|
||||
if let Some(player) = player {
|
||||
let ship = input.phys_img.get_ship(&PhysSimShipHandle(player)).unwrap();
|
||||
if self.last_player_state == 0
|
||||
|| NonZeroU32::new(self.last_player_state).unwrap()
|
||||
!= ship.ship.get_data().get_state().as_int()
|
||||
{
|
||||
self.last_player_state = ship.ship.get_data().get_state().as_int().into();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.last_player_state = 0;
|
||||
true
|
||||
}
|
||||
} {
|
||||
rhai_error_to_anyhow(
|
||||
self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
ct.get_config()
|
||||
.ui_scenes
|
||||
.get(current_scene.as_ref().unwrap().as_str())
|
||||
.unwrap(),
|
||||
"event",
|
||||
(State::new(input.clone()), PlayerShipStateEvent {}),
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while handling player state change event"))
|
||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
||||
}
|
||||
|
||||
let len = (*self.state).borrow().len();
|
||||
for i in 0..len {
|
||||
let event_arg = match (*self.state).borrow_mut().get_mut_by_idx(i).unwrap() {
|
||||
UiElement::Sprite(sprite) => {
|
||||
// Draw and update sprites
|
||||
sprite.step(&input, state);
|
||||
sprite.push_to_buffer(&input, state);
|
||||
let event = sprite.check_events(&input, state);
|
||||
|
||||
match event {
|
||||
Event::None => None,
|
||||
|
||||
Event::MouseClick => Some(Dynamic::from(MouseClickEvent {
|
||||
down: true,
|
||||
element: sprite.name.clone(),
|
||||
})),
|
||||
|
||||
Event::MouseRelease => Some(Dynamic::from(MouseClickEvent {
|
||||
down: false,
|
||||
element: sprite.name.clone(),
|
||||
})),
|
||||
|
||||
Event::MouseHover => Some(Dynamic::from(MouseHoverEvent {
|
||||
enter: true,
|
||||
element: sprite.name.clone(),
|
||||
})),
|
||||
|
||||
Event::MouseUnhover => Some(Dynamic::from(MouseHoverEvent {
|
||||
enter: false,
|
||||
element: sprite.name.clone(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
UiElement::RadialBar(x) => {
|
||||
// Draw and update radialbar
|
||||
x.step(&input, state);
|
||||
x.push_to_buffer(&input, state);
|
||||
None
|
||||
}
|
||||
|
||||
UiElement::Text(..) => None,
|
||||
};
|
||||
|
||||
if let Some(event_arg) = event_arg {
|
||||
rhai_error_to_anyhow(
|
||||
self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
ct.get_config()
|
||||
.ui_scenes
|
||||
.get(current_scene.as_ref().unwrap().as_str())
|
||||
.unwrap(),
|
||||
"event",
|
||||
(State::new(input.clone()), event_arg.clone()),
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while handling event `{:?}`", event_arg))
|
||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
impl UiScriptExecutor {
|
||||
pub fn register_api(
|
||||
ct_src: Arc<Content>,
|
||||
font_src: Rc<RefCell<FontSystem>>,
|
||||
s: Rc<RefCell<UiState>>,
|
||||
engine: &mut Engine,
|
||||
) {
|
||||
// Utilities
|
||||
{
|
||||
let c = s.clone();
|
||||
engine.register_fn("go_to_scene", move |scene: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
ui_state.set_scene(scene);
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
engine.register_fn("conf_set_phys", move |b: bool| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
ui_state.config.show_phys = b;
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
engine.register_fn("conf_set_starfield", move |b: bool| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
ui_state.config.show_starfield = b;
|
||||
});
|
||||
}
|
||||
|
||||
// Sprites
|
||||
{
|
||||
let c = s.clone();
|
||||
let ct = ct_src.clone();
|
||||
engine.register_fn(
|
||||
"add_sprite",
|
||||
move |name: ImmutableString, sprite: ImmutableString, rect: Rect| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
let len = ui_state.len();
|
||||
|
||||
let sprite_handle = ct.get_sprite_handle(sprite.as_str());
|
||||
if sprite_handle.is_none() {
|
||||
error!("made a sprite using an invalid source `{sprite}`");
|
||||
return;
|
||||
}
|
||||
|
||||
ui_state.names.insert(name.clone(), len);
|
||||
ui_state.elements.push(UiElement::Sprite(Sprite::new(
|
||||
&ct,
|
||||
name.clone(),
|
||||
sprite_handle.unwrap(),
|
||||
rect,
|
||||
)));
|
||||
},
|
||||
);
|
||||
|
||||
let c = s.clone();
|
||||
let ct = ct_src.clone();
|
||||
engine.register_fn(
|
||||
"sprite_set_mask",
|
||||
move |name: ImmutableString, mask: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Sprite(x)) => {
|
||||
let m = ct.get_sprite_handle(mask.as_str());
|
||||
if m.is_none() {
|
||||
error!("called `set_sprite_mask` with an invalid mask `{mask}`");
|
||||
return;
|
||||
}
|
||||
x.set_mask(m)
|
||||
}
|
||||
|
||||
_ => {
|
||||
error!("called `set_sprite_mask` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let c = s.clone();
|
||||
let ct = ct_src.clone();
|
||||
engine.register_fn(
|
||||
"sprite_take_edge",
|
||||
move |name: ImmutableString, edge_name: ImmutableString, duration: f32| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Sprite(x)) => {
|
||||
let sprite_handle = x.anim.get_sprite();
|
||||
let sprite = &ct.get_sprite(sprite_handle);
|
||||
|
||||
let edge = resolve_edge_as_edge(edge_name.as_str(), duration, |x| {
|
||||
sprite.get_section_handle_by_name(x)
|
||||
});
|
||||
let edge = match edge {
|
||||
Err(_) => {
|
||||
error!(
|
||||
"called `sprite_take_edge` on an invalid edge `{}` on sprite `{}`",
|
||||
edge_name, sprite.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
|
||||
x.anim.jump_to(&ct, edge);
|
||||
}
|
||||
_ => {
|
||||
error!("called `sprite_take_edge` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Textboxes
|
||||
{
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn(
|
||||
"add_textbox",
|
||||
// TODO: fix ugly spaces
|
||||
move |name: ImmutableString,
|
||||
font_size: f32,
|
||||
line_height: f32,
|
||||
rect: Rect,
|
||||
color: Color| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
let len = ui_state.len();
|
||||
|
||||
ui_state.names.insert(name.clone(), len);
|
||||
ui_state.elements.push(UiElement::Text(TextBox::new(
|
||||
&mut font.borrow_mut(),
|
||||
name.clone(),
|
||||
font_size,
|
||||
line_height,
|
||||
rect,
|
||||
color,
|
||||
)));
|
||||
},
|
||||
);
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn(
|
||||
"textbox_set_text",
|
||||
move |name: ImmutableString, text: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => {
|
||||
x.set_text(&mut font.borrow_mut(), text.as_str())
|
||||
}
|
||||
_ => {
|
||||
error!("called `textbox_set_text` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_align_left", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => x.set_align(&mut font.borrow_mut(), Align::Left),
|
||||
_ => {
|
||||
error!("called `textbox_align_left` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_align_right", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => x.set_align(&mut font.borrow_mut(), Align::Right),
|
||||
_ => {
|
||||
error!("called `textbox_align_right` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_align_justify", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => {
|
||||
x.set_align(&mut font.borrow_mut(), Align::Justified)
|
||||
}
|
||||
_ => {
|
||||
error!("called `textbox_align_justify` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_align_center", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => x.set_align(&mut font.borrow_mut(), Align::Center),
|
||||
_ => {
|
||||
error!("called `textbox_align_center` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_weight_bold", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => x.set_weight(&mut font.borrow_mut(), Weight::BOLD),
|
||||
_ => {
|
||||
error!("called `textbox_weight_bold` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_weight_normal", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => {
|
||||
x.set_weight(&mut font.borrow_mut(), Weight::NORMAL)
|
||||
}
|
||||
_ => {
|
||||
error!("called `textbox_weight_normal` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_font_serif", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => {
|
||||
x.set_font(&mut font.borrow_mut(), FamilyOwned::Serif)
|
||||
}
|
||||
_ => {
|
||||
error!("called `textbox_font_serif` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_font_sans", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => {
|
||||
x.set_font(&mut font.borrow_mut(), FamilyOwned::SansSerif)
|
||||
}
|
||||
_ => {
|
||||
error!("called `textbox_font_sans` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_font_mono", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => {
|
||||
x.set_font(&mut font.borrow_mut(), FamilyOwned::Monospace)
|
||||
}
|
||||
_ => {
|
||||
error!("called `textbox_font_mono` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_style_normal", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => x.set_style(&mut font.borrow_mut(), Style::Normal),
|
||||
_ => {
|
||||
error!("called `textbox_style_normal` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c = s.clone();
|
||||
let font = font_src.clone();
|
||||
engine.register_fn("textbox_style_italic", move |name: ImmutableString| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::Text(x)) => x.set_style(&mut font.borrow_mut(), Style::Italic),
|
||||
_ => {
|
||||
error!("called `textbox_style_italic` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Radialbars
|
||||
{
|
||||
let c = s.clone();
|
||||
engine.register_fn(
|
||||
"add_radialbar",
|
||||
// TODO: fix ugly spaces
|
||||
move |name: ImmutableString, stroke: f32, color: Color, rect: Rect| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
let len = ui_state.len();
|
||||
|
||||
ui_state.names.insert(name.clone(), len);
|
||||
ui_state.elements.push(UiElement::RadialBar(RadialBar::new(
|
||||
name.clone(),
|
||||
stroke,
|
||||
color,
|
||||
rect,
|
||||
1.0,
|
||||
)));
|
||||
},
|
||||
);
|
||||
|
||||
let c = s.clone();
|
||||
engine.register_fn(
|
||||
"radialbar_set_val",
|
||||
move |name: ImmutableString, val: f32| {
|
||||
let mut ui_state = c.borrow_mut();
|
||||
match ui_state.get_mut_by_name(&name) {
|
||||
Some(UiElement::RadialBar(x)) => x.set_val(val),
|
||||
_ => {
|
||||
error!("called `radialbar_set_val` on an invalid name `{name}`")
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,418 +0,0 @@
|
|||
use anyhow::{Context, Result};
|
||||
use galactica_content::Content;
|
||||
use galactica_system::phys::PhysSimShipHandle;
|
||||
use glyphon::TextArea;
|
||||
use log::{debug, error, trace};
|
||||
use rhai::{Array, Dynamic, Engine, Map, Scope};
|
||||
use std::{collections::HashSet, num::NonZeroU32, rc::Rc};
|
||||
|
||||
use super::{
|
||||
api::{
|
||||
self, MouseClickEvent, MouseHoverEvent, PlayerShipStateEvent, SceneAction, SceneConfig,
|
||||
SpriteElement, TextBoxBuilder,
|
||||
},
|
||||
event::Event,
|
||||
util::{FpsIndicator, RadialBar, TextBox},
|
||||
UiElement,
|
||||
};
|
||||
use crate::{
|
||||
ui::{
|
||||
api::{RadialBuilder, RadialElement, SpriteBuilder, State},
|
||||
util::Sprite,
|
||||
},
|
||||
RenderInput, RenderState,
|
||||
};
|
||||
|
||||
pub(crate) struct UiManager {
|
||||
current_scene: Option<String>,
|
||||
current_scene_config: SceneConfig,
|
||||
engine: Engine,
|
||||
scope: Scope<'static>,
|
||||
ct: Rc<Content>,
|
||||
|
||||
/// UI elements
|
||||
elements: Vec<UiElement>,
|
||||
/// Map of ui element name -> api handle.
|
||||
/// Used for step() function.
|
||||
element_index: Map,
|
||||
|
||||
last_player_state: u32,
|
||||
show_timings: bool,
|
||||
fps_indicator: FpsIndicator,
|
||||
}
|
||||
|
||||
impl UiManager {
|
||||
pub fn new(ct: Rc<Content>, state: &mut RenderState) -> Self {
|
||||
let scope = Scope::new();
|
||||
|
||||
let mut engine = Engine::new_raw();
|
||||
api::register_into_engine(&mut engine);
|
||||
|
||||
Self {
|
||||
ct,
|
||||
current_scene: None,
|
||||
current_scene_config: SceneConfig::new(),
|
||||
engine,
|
||||
scope,
|
||||
elements: Vec::new(),
|
||||
element_index: Map::new(),
|
||||
show_timings: true,
|
||||
fps_indicator: FpsIndicator::new(state),
|
||||
last_player_state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> &SceneConfig {
|
||||
&self.current_scene_config
|
||||
}
|
||||
|
||||
/// Change the current scene
|
||||
pub fn set_scene(
|
||||
&mut self,
|
||||
state: &mut RenderState,
|
||||
input: Rc<RenderInput>,
|
||||
scene: String,
|
||||
) -> Result<()> {
|
||||
if !self.ct.get_config().ui_scenes.contains_key(&scene) {
|
||||
error!("tried to switch to ui scene `{scene}`, which doesn't exist");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("switching to {:?}", scene);
|
||||
self.current_scene = Some(scene);
|
||||
self.current_scene_config = self
|
||||
.engine
|
||||
.call_fn(
|
||||
&mut self.scope,
|
||||
&self
|
||||
.ct
|
||||
.get_config()
|
||||
.ui_scenes
|
||||
.get(self.current_scene.as_ref().unwrap())
|
||||
.unwrap(),
|
||||
"config",
|
||||
(),
|
||||
)
|
||||
.with_context(|| format!("while handling `config()`"))
|
||||
.with_context(|| format!("in ui scene `{}`", self.current_scene.as_ref().unwrap()))?;
|
||||
|
||||
self.scope.clear();
|
||||
self.elements.clear();
|
||||
let mut used_names = HashSet::new();
|
||||
|
||||
let builders: Array = self
|
||||
.engine
|
||||
.call_fn(
|
||||
&mut self.scope,
|
||||
&self
|
||||
.ct
|
||||
.get_config()
|
||||
.ui_scenes
|
||||
.get(self.current_scene.as_ref().unwrap())
|
||||
.unwrap(),
|
||||
"init",
|
||||
(State::new(input.clone()),),
|
||||
)
|
||||
.with_context(|| format!("while running `init()`"))
|
||||
.with_context(|| format!("in ui scene `{}`", self.current_scene.as_ref().unwrap()))?;
|
||||
|
||||
trace!("found {:?} builders", builders.len());
|
||||
|
||||
for v in builders {
|
||||
if v.is::<SpriteBuilder>() {
|
||||
let s = v.cast::<SpriteBuilder>();
|
||||
if used_names.contains(s.name.as_str()) {
|
||||
error!(
|
||||
"UI scene `{}` re-uses element name `{}`",
|
||||
self.current_scene.as_ref().unwrap(),
|
||||
s.name
|
||||
);
|
||||
} else {
|
||||
used_names.insert(s.name.to_string());
|
||||
}
|
||||
self.elements.push(UiElement::new_sprite(Sprite::new(
|
||||
&self.ct,
|
||||
s.name.to_string(),
|
||||
s.sprite.to_string(),
|
||||
s.mask.map(|x| x.to_string()),
|
||||
s.rect,
|
||||
)));
|
||||
} else if v.is::<RadialBuilder>() {
|
||||
let r = v.cast::<RadialBuilder>();
|
||||
if used_names.contains(r.name.as_str()) {
|
||||
error!(
|
||||
"UI scene `{}` re-uses element name `{}`",
|
||||
self.current_scene.as_ref().unwrap(),
|
||||
r.name
|
||||
);
|
||||
} else {
|
||||
used_names.insert(r.name.to_string());
|
||||
}
|
||||
self.elements.push(UiElement::new_radialbar(RadialBar::new(
|
||||
&self.ct,
|
||||
r.name.to_string(),
|
||||
r.stroke,
|
||||
r.color,
|
||||
r.rect,
|
||||
r.progress,
|
||||
)));
|
||||
} else if v.is::<TextBoxBuilder>() {
|
||||
let t = v.cast::<TextBoxBuilder>();
|
||||
if used_names.contains(t.name.as_str()) {
|
||||
error!(
|
||||
"UI scene `{}` re-uses element name `{}`",
|
||||
self.current_scene.as_ref().unwrap(),
|
||||
t.name
|
||||
);
|
||||
} else {
|
||||
used_names.insert(t.name.to_string());
|
||||
}
|
||||
let mut b = TextBox::new(
|
||||
state,
|
||||
t.name.to_string(),
|
||||
t.font_size,
|
||||
t.line_height,
|
||||
t.justify,
|
||||
t.attrs,
|
||||
t.rect,
|
||||
t.color,
|
||||
);
|
||||
b.set_text(state, &t.text);
|
||||
self.elements.push(UiElement::new_text(b));
|
||||
} else {
|
||||
// TODO: better message
|
||||
error!("bad type in builder array")
|
||||
}
|
||||
|
||||
self.element_index.clear();
|
||||
for e in &self.elements {
|
||||
match e {
|
||||
UiElement::Text(_) => {}
|
||||
UiElement::RadialBar(r) => {
|
||||
self.element_index.insert(
|
||||
(&r).borrow().name.clone().into(),
|
||||
Dynamic::from(RadialElement::new(self.ct.clone(), r.clone())),
|
||||
);
|
||||
}
|
||||
UiElement::Sprite(s) => {
|
||||
self.element_index.insert(
|
||||
(&s).borrow().name.clone().into(),
|
||||
Dynamic::from(SpriteElement::new(self.ct.clone(), s.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Draw all ui elements
|
||||
pub fn draw(&mut self, input: Rc<RenderInput>, state: &mut RenderState) -> Result<()> {
|
||||
// Initialize start scene if we haven't yet
|
||||
if self.current_scene.is_none() {
|
||||
self.set_scene(
|
||||
state,
|
||||
input.clone(),
|
||||
self.ct.get_config().start_ui_scene.clone(),
|
||||
)?;
|
||||
}
|
||||
|
||||
// Run step() (if it is defined)
|
||||
let ast = &self
|
||||
.ct
|
||||
.get_config()
|
||||
.ui_scenes
|
||||
.get(self.current_scene.as_ref().unwrap())
|
||||
.unwrap();
|
||||
if ast.iter_functions().any(|x| x.name == "step") {
|
||||
self.engine
|
||||
.call_fn(
|
||||
&mut self.scope,
|
||||
ast,
|
||||
"step",
|
||||
(State::new(input.clone()), self.element_index.clone()),
|
||||
)
|
||||
.with_context(|| format!("while handling player state change event"))
|
||||
.with_context(|| {
|
||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Update timings if they're being displayed
|
||||
if self.show_timings {
|
||||
self.fps_indicator.step(&input, state);
|
||||
}
|
||||
|
||||
// Send player state change events
|
||||
if {
|
||||
let player = input.player.ship;
|
||||
if let Some(player) = player {
|
||||
let ship = input.phys_img.get_ship(&PhysSimShipHandle(player)).unwrap();
|
||||
if self.last_player_state == 0
|
||||
|| NonZeroU32::new(self.last_player_state).unwrap()
|
||||
!= ship.ship.get_data().get_state().as_int()
|
||||
{
|
||||
self.last_player_state = ship.ship.get_data().get_state().as_int().into();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.last_player_state = 0;
|
||||
true
|
||||
}
|
||||
} {
|
||||
let action: Dynamic = self
|
||||
.engine
|
||||
.call_fn(
|
||||
&mut self.scope,
|
||||
&self
|
||||
.ct
|
||||
.get_config()
|
||||
.ui_scenes
|
||||
.get(self.current_scene.as_ref().unwrap())
|
||||
.unwrap(),
|
||||
"event",
|
||||
(State::new(input.clone()), PlayerShipStateEvent {}),
|
||||
)
|
||||
.with_context(|| format!("while handling player state change event"))
|
||||
.with_context(|| {
|
||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
||||
})?;
|
||||
|
||||
if let Some(action) = action.try_cast::<SceneAction>() {
|
||||
if self.handle_action(state, input.clone(), action)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..self.elements.len() {
|
||||
match &self.elements[i] {
|
||||
UiElement::Sprite(e) => {
|
||||
// Draw and update sprites
|
||||
let mut sprite = (*e).borrow_mut();
|
||||
sprite.step(&input, state);
|
||||
sprite.push_to_buffer(&input, state);
|
||||
|
||||
let event = sprite.check_events(&input, state);
|
||||
// we MUST drop here, since script calls mutate the sprite RefCell
|
||||
drop(sprite);
|
||||
|
||||
match event {
|
||||
Event::None => continue,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let event_arg = match event {
|
||||
Event::None => unreachable!("this shouldn't happen"),
|
||||
|
||||
Event::MouseClick => Dynamic::from(MouseClickEvent {
|
||||
down: true,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
}),
|
||||
|
||||
Event::MouseRelease => Dynamic::from(MouseClickEvent {
|
||||
down: false,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
}),
|
||||
|
||||
Event::MouseHover => Dynamic::from(MouseHoverEvent {
|
||||
enter: true,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
}),
|
||||
|
||||
Event::MouseUnhover => Dynamic::from(MouseHoverEvent {
|
||||
enter: false,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
}),
|
||||
};
|
||||
|
||||
let action: Dynamic = self
|
||||
.engine
|
||||
.call_fn(
|
||||
&mut self.scope,
|
||||
&self
|
||||
.ct
|
||||
.get_config()
|
||||
.ui_scenes
|
||||
.get(self.current_scene.as_ref().unwrap())
|
||||
.unwrap(),
|
||||
"event",
|
||||
(State::new(input.clone()), event_arg),
|
||||
)
|
||||
.with_context(|| format!("while handling event `{:?}`", event))
|
||||
.with_context(|| {
|
||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
||||
})?;
|
||||
|
||||
if let Some(action) = action.try_cast::<SceneAction>() {
|
||||
if self.handle_action(state, input.clone(), action)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UiElement::RadialBar(e) => {
|
||||
// Draw and update radialbar
|
||||
let mut x = (*e).borrow_mut();
|
||||
x.step(&input, state);
|
||||
x.push_to_buffer(&input, state);
|
||||
}
|
||||
|
||||
UiElement::Text(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Do a SceneAction.
|
||||
/// If this returns true, all evaluation stops immediately.
|
||||
fn handle_action(
|
||||
&mut self,
|
||||
state: &mut RenderState,
|
||||
input: Rc<RenderInput>,
|
||||
action: SceneAction,
|
||||
) -> Result<bool> {
|
||||
Ok(match action {
|
||||
SceneAction::None => false,
|
||||
SceneAction::GoTo(s) => {
|
||||
self.set_scene(state, input.clone(), s.clone())?;
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> UiManager {
|
||||
/// Get textareas
|
||||
pub fn get_textareas(
|
||||
&'a mut self,
|
||||
input: &RenderInput,
|
||||
state: &RenderState,
|
||||
) -> Vec<TextArea<'a>> {
|
||||
let mut v = Vec::with_capacity(32);
|
||||
|
||||
if self.current_scene.is_none() {
|
||||
return v;
|
||||
}
|
||||
|
||||
if self.show_timings {
|
||||
v.push(self.fps_indicator.get_textarea(state, input))
|
||||
}
|
||||
|
||||
for t in self
|
||||
.elements
|
||||
.iter()
|
||||
.map(|x| x.text())
|
||||
.filter(|x| x.is_some())
|
||||
.map(|x| x.unwrap())
|
||||
.map(|x| x.get_textarea(state, input))
|
||||
{
|
||||
v.push(t)
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
mod api;
|
||||
mod event;
|
||||
mod manager;
|
||||
mod uielement;
|
||||
mod executor;
|
||||
mod state;
|
||||
|
||||
mod util;
|
||||
|
||||
pub(crate) use manager::UiManager;
|
||||
pub(crate) use uielement::UiElement;
|
||||
pub(crate) use executor::UiScriptExecutor;
|
||||
pub(crate) use state::*;
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
use galactica_content::Content;
|
||||
use glyphon::TextArea;
|
||||
use log::{debug, error};
|
||||
use rhai::ImmutableString;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use winit::window::Window;
|
||||
|
||||
use super::util::{FpsIndicator, RadialBar, Sprite, TextBox};
|
||||
use crate::{RenderInput, RenderState};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UiElement {
|
||||
Sprite(Sprite),
|
||||
RadialBar(RadialBar),
|
||||
Text(TextBox),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct UiConfig {
|
||||
pub show_phys: bool,
|
||||
pub show_starfield: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct UiState {
|
||||
pub names: HashMap<ImmutableString, usize>,
|
||||
pub elements: Vec<UiElement>,
|
||||
|
||||
pub ct: Arc<Content>,
|
||||
current_scene: Option<ImmutableString>,
|
||||
|
||||
show_timings: bool,
|
||||
fps_indicator: FpsIndicator,
|
||||
|
||||
pub config: UiConfig,
|
||||
}
|
||||
// TODO: remove this
|
||||
unsafe impl Send for UiState {}
|
||||
unsafe impl Sync for UiState {}
|
||||
|
||||
impl UiState {
|
||||
pub fn new(ct: Arc<Content>, state: &mut RenderState) -> Self {
|
||||
Self {
|
||||
ct,
|
||||
names: HashMap::new(),
|
||||
elements: Vec::new(),
|
||||
|
||||
current_scene: None,
|
||||
show_timings: true,
|
||||
fps_indicator: FpsIndicator::new(state),
|
||||
config: UiConfig {
|
||||
show_phys: false,
|
||||
show_starfield: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.elements.clear();
|
||||
self.names.clear();
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.elements.len()
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn get_by_idx(&self, idx: usize) -> Option<&UiElement> {
|
||||
self.elements.get(idx)
|
||||
}
|
||||
|
||||
pub fn get_by_name(&self, name: &ImmutableString) -> Option<&UiElement> {
|
||||
let idx = self.names.get(name);
|
||||
if idx.is_none() {
|
||||
return None;
|
||||
}
|
||||
self.get_by_idx(*idx.unwrap())
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn get_mut_by_idx(&mut self, idx: usize) -> Option<&mut UiElement> {
|
||||
self.elements.get_mut(idx)
|
||||
}
|
||||
|
||||
pub fn get_mut_by_name(&mut self, name: &ImmutableString) -> Option<&mut UiElement> {
|
||||
let idx = self.names.get(name);
|
||||
if idx.is_none() {
|
||||
return None;
|
||||
}
|
||||
self.get_mut_by_idx(*idx.unwrap())
|
||||
}
|
||||
|
||||
pub fn get_scene(&self) -> &Option<ImmutableString> {
|
||||
&self.current_scene
|
||||
}
|
||||
|
||||
pub fn set_scene(&mut self, scene: ImmutableString) {
|
||||
if !self.ct.get_config().ui_scenes.contains_key(scene.as_str()) {
|
||||
error!("tried to switch to ui scene `{scene}`, which doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("switching to {:?}", scene);
|
||||
self.current_scene = Some(scene);
|
||||
}
|
||||
|
||||
pub fn step(&mut self, state: &mut RenderState, input: Arc<RenderInput>) {
|
||||
if self.show_timings {
|
||||
self.fps_indicator
|
||||
.step(&input, &mut state.text_font_system.borrow_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> UiState {
|
||||
pub fn get_textareas(&'a mut self, input: &RenderInput, window: &Window) -> Vec<TextArea<'a>> {
|
||||
let mut v = Vec::with_capacity(32);
|
||||
|
||||
if self.current_scene.is_none() {
|
||||
return v;
|
||||
}
|
||||
|
||||
if self.show_timings {
|
||||
v.push(self.fps_indicator.get_textarea(input, window))
|
||||
}
|
||||
|
||||
for t in self.elements.iter() {
|
||||
match &t {
|
||||
UiElement::Text(x) => v.push(x.get_textarea(input, window)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use super::util::{RadialBar, Sprite, TextBox};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UiElement {
|
||||
Sprite(Rc<RefCell<Sprite>>),
|
||||
RadialBar(Rc<RefCell<RadialBar>>),
|
||||
Text(TextBox),
|
||||
}
|
||||
|
||||
impl UiElement {
|
||||
pub fn new_sprite(sprite: Sprite) -> Self {
|
||||
Self::Sprite(Rc::new(RefCell::new(sprite)))
|
||||
}
|
||||
|
||||
pub fn new_radialbar(bar: RadialBar) -> Self {
|
||||
Self::RadialBar(Rc::new(RefCell::new(bar)))
|
||||
}
|
||||
|
||||
pub fn new_text(text: TextBox) -> Self {
|
||||
Self::Text(text)
|
||||
}
|
||||
|
||||
pub fn text(&self) -> Option<&TextBox> {
|
||||
match self {
|
||||
Self::Text(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use glyphon::{Attrs, Buffer, Color, Family, Metrics, Shaping, TextArea, TextBounds};
|
||||
use glyphon::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, TextArea, TextBounds};
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::{RenderInput, RenderState};
|
||||
|
||||
|
@ -9,9 +10,12 @@ pub(crate) struct FpsIndicator {
|
|||
|
||||
impl FpsIndicator {
|
||||
pub fn new(state: &mut RenderState) -> Self {
|
||||
let mut buffer = Buffer::new(&mut state.text_font_system, Metrics::new(7.0, 8.0));
|
||||
let mut buffer = Buffer::new(
|
||||
&mut state.text_font_system.borrow_mut(),
|
||||
Metrics::new(7.0, 8.0),
|
||||
);
|
||||
buffer.set_size(
|
||||
&mut state.text_font_system,
|
||||
&mut state.text_font_system.borrow_mut(),
|
||||
state.window_size.width as f32,
|
||||
state.window_size.height as f32,
|
||||
);
|
||||
|
@ -24,7 +28,7 @@ impl FpsIndicator {
|
|||
}
|
||||
|
||||
impl FpsIndicator {
|
||||
pub fn step(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||
pub fn step(&mut self, input: &RenderInput, font: &mut FontSystem) {
|
||||
if self.update_counter > 0 {
|
||||
self.update_counter -= 1;
|
||||
return;
|
||||
|
@ -32,17 +36,17 @@ impl FpsIndicator {
|
|||
self.update_counter = 100;
|
||||
|
||||
self.buffer.set_text(
|
||||
&mut state.text_font_system,
|
||||
font,
|
||||
&input.timing.get_string(),
|
||||
Attrs::new().family(Family::Monospace),
|
||||
Shaping::Basic,
|
||||
);
|
||||
self.buffer.shape_until_scroll(&mut state.text_font_system);
|
||||
self.buffer.shape_until_scroll(font);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> FpsIndicator {
|
||||
pub fn get_textarea(&'b self, _state: &RenderState, input: &RenderInput) -> TextArea<'a> {
|
||||
pub fn get_textarea(&'b self, input: &RenderInput, _window: &Window) -> TextArea<'a> {
|
||||
TextArea {
|
||||
buffer: &self.buffer,
|
||||
left: 10.0,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use galactica_content::Content;
|
||||
use std::f32::consts::TAU;
|
||||
|
||||
use rhai::ImmutableString;
|
||||
|
||||
use super::super::api::Rect;
|
||||
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RadialBar {
|
||||
pub name: String,
|
||||
pub name: ImmutableString,
|
||||
rect: Rect,
|
||||
stroke: f32,
|
||||
color: Color,
|
||||
|
@ -15,8 +16,7 @@ pub struct RadialBar {
|
|||
|
||||
impl RadialBar {
|
||||
pub fn new(
|
||||
_ct: &Content,
|
||||
name: String,
|
||||
name: ImmutableString,
|
||||
stroke: f32,
|
||||
color: Color,
|
||||
rect: Rect,
|
||||
|
@ -36,7 +36,9 @@ impl RadialBar {
|
|||
}
|
||||
|
||||
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
|
||||
.rect
|
||||
.to_centered(&state.window, input.ct.get_config().ui_scale);
|
||||
|
||||
state.push_radialbar_buffer(RadialBarInstance {
|
||||
position: [rect.pos.x, rect.pos.y],
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
|
||||
use galactica_util::to_radians;
|
||||
use log::error;
|
||||
|
||||
use super::super::api::Rect;
|
||||
use crate::{ui::event::Event, vertexbuffer::types::UiInstance, RenderInput, RenderState};
|
||||
use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
|
||||
use galactica_util::to_radians;
|
||||
use rhai::ImmutableString;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Sprite {
|
||||
pub name: String,
|
||||
|
||||
/// If this is none, this was constructed with an invalid sprite
|
||||
pub anim: Option<SpriteAutomaton>,
|
||||
pub anim: SpriteAutomaton,
|
||||
pub name: ImmutableString,
|
||||
|
||||
rect: Rect,
|
||||
mask: Option<SpriteHandle>,
|
||||
|
@ -22,57 +19,30 @@ pub struct Sprite {
|
|||
}
|
||||
|
||||
impl Sprite {
|
||||
pub fn new(
|
||||
ct: &Content,
|
||||
name: String,
|
||||
sprite: String,
|
||||
mask: Option<String>,
|
||||
rect: Rect,
|
||||
) -> Self {
|
||||
let sprite_handle = ct.get_sprite_handle(&sprite);
|
||||
|
||||
if sprite_handle.is_none() {
|
||||
error!("UI constructed a sprite named `{name}` using an invalid source `{sprite}`")
|
||||
}
|
||||
|
||||
let mask = {
|
||||
if mask.is_some() {
|
||||
let m = ct.get_sprite_handle(mask.as_ref().unwrap());
|
||||
if m.is_none() {
|
||||
error!(
|
||||
"UI constructed a sprite named `{name}` using an invalid mask` {}`",
|
||||
mask.unwrap()
|
||||
);
|
||||
None
|
||||
} else {
|
||||
m
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
pub fn new(ct: &Content, name: ImmutableString, sprite: SpriteHandle, rect: Rect) -> Self {
|
||||
Self {
|
||||
name,
|
||||
anim: sprite_handle.map(|x| SpriteAutomaton::new(&ct, x)),
|
||||
anim: SpriteAutomaton::new(&ct, sprite),
|
||||
rect,
|
||||
mask,
|
||||
mask: None,
|
||||
has_mouse: false,
|
||||
has_click: false,
|
||||
waiting_for_release: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||
if self.anim.is_none() {
|
||||
return;
|
||||
}
|
||||
pub fn set_mask(&mut self, mask: Option<SpriteHandle>) {
|
||||
self.mask = mask;
|
||||
}
|
||||
|
||||
let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||
let rect = self
|
||||
.rect
|
||||
.to_centered(&state.window, input.ct.get_config().ui_scale);
|
||||
|
||||
// TODO: use both dimensions,
|
||||
// not just height
|
||||
let anim_state = self.anim.as_ref().unwrap().get_texture_idx();
|
||||
let anim_state = self.anim.get_texture_idx();
|
||||
|
||||
state.push_ui_buffer(UiInstance {
|
||||
position: rect.pos.into(),
|
||||
|
@ -93,7 +63,9 @@ impl Sprite {
|
|||
}
|
||||
|
||||
pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
|
||||
let r = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
||||
let r = self
|
||||
.rect
|
||||
.to_centered(&state.window, input.ct.get_config().ui_scale);
|
||||
|
||||
if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
|
||||
self.waiting_for_release = false;
|
||||
|
@ -139,13 +111,6 @@ impl Sprite {
|
|||
}
|
||||
|
||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
||||
if self.anim.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.anim
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.step(&input.ct, input.time_since_last_run);
|
||||
self.anim.step(&input.ct, input.time_since_last_run);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
use glyphon::{
|
||||
cosmic_text::Align, AttrsOwned, Buffer, Color, Metrics, Shaping, TextArea, TextBounds,
|
||||
cosmic_text::Align, Attrs, AttrsOwned, Buffer, Color, FamilyOwned, FontSystem, Metrics,
|
||||
Shaping, Style, TextArea, TextBounds, Weight,
|
||||
};
|
||||
use nalgebra::Vector2;
|
||||
use rhai::ImmutableString;
|
||||
use winit::window::Window;
|
||||
|
||||
use super::super::api::Rect;
|
||||
use crate::{ui::api, RenderInput, RenderState};
|
||||
use crate::{ui::api, RenderInput};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextBox {
|
||||
pub name: String,
|
||||
pub name: ImmutableString,
|
||||
|
||||
text: String,
|
||||
justify: Align,
|
||||
rect: Rect,
|
||||
buffer: Buffer,
|
||||
|
@ -18,58 +23,78 @@ pub struct TextBox {
|
|||
|
||||
impl TextBox {
|
||||
pub fn new(
|
||||
state: &mut RenderState,
|
||||
name: String,
|
||||
font: &mut FontSystem,
|
||||
name: ImmutableString,
|
||||
font_size: f32,
|
||||
line_height: f32,
|
||||
justify: Align,
|
||||
attrs: AttrsOwned,
|
||||
rect: Rect,
|
||||
color: api::Color,
|
||||
) -> Self {
|
||||
let mut buffer = Buffer::new(
|
||||
&mut state.text_font_system,
|
||||
Metrics::new(font_size, line_height),
|
||||
);
|
||||
let mut buffer = Buffer::new(font, 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);
|
||||
buffer.set_size(font, rect.dim.x, rect.dim.y);
|
||||
|
||||
Self {
|
||||
name,
|
||||
justify,
|
||||
rect,
|
||||
buffer,
|
||||
color,
|
||||
attrs,
|
||||
justify: Align::Left,
|
||||
attrs: AttrsOwned::new(Attrs::new()),
|
||||
text: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, state: &mut RenderState, text: &str) {
|
||||
self.buffer.set_text(
|
||||
&mut state.text_font_system,
|
||||
text,
|
||||
self.attrs.as_attrs(),
|
||||
Shaping::Advanced,
|
||||
);
|
||||
fn reflow(&mut self, font: &mut FontSystem) {
|
||||
self.buffer
|
||||
.set_text(font, &self.text, self.attrs.as_attrs(), Shaping::Advanced);
|
||||
|
||||
for l in &mut self.buffer.lines {
|
||||
l.set_align(Some(self.justify));
|
||||
}
|
||||
|
||||
self.buffer.shape_until_scroll(&mut state.text_font_system);
|
||||
self.buffer.shape_until_scroll(font);
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, font: &mut FontSystem, text: &str) {
|
||||
self.text.clear();
|
||||
self.text.push_str(text);
|
||||
self.reflow(font);
|
||||
}
|
||||
|
||||
pub fn set_align(&mut self, font: &mut FontSystem, align: Align) {
|
||||
self.justify = align;
|
||||
self.reflow(font);
|
||||
}
|
||||
|
||||
pub fn set_weight(&mut self, font: &mut FontSystem, weight: Weight) {
|
||||
self.attrs.weight = weight;
|
||||
self.reflow(font);
|
||||
}
|
||||
|
||||
pub fn set_font(&mut self, font: &mut FontSystem, family: FamilyOwned) {
|
||||
self.attrs.family_owned = family;
|
||||
self.reflow(font);
|
||||
}
|
||||
|
||||
pub fn set_style(&mut self, font: &mut FontSystem, style: Style) {
|
||||
self.attrs.style = style;
|
||||
self.reflow(font);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> {
|
||||
let rect = self
|
||||
.rect
|
||||
.to_centered(window, 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 fac = 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),
|
||||
(rect.pos.x - rect.dim.x / 2.0) * fac + window.inner_size().width as f32 / 2.0,
|
||||
window.inner_size().height as f32 / 2.0 - (rect.pos.y * fac + rect.dim.y / 2.0),
|
||||
);
|
||||
let corner_sw = corner_ne + rect.dim * fac;
|
||||
let c = self.color.as_array_u8();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use galactica_content::Content;
|
||||
use galactica_util::timing::Timing;
|
||||
|
@ -6,7 +6,7 @@ use galactica_util::timing::Timing;
|
|||
/// External resources we need to compute time steps
|
||||
pub struct PhysStepResources<'a> {
|
||||
/// Game content
|
||||
pub ct: Rc<Content>,
|
||||
pub ct: Arc<Content>,
|
||||
|
||||
/// Length of time step
|
||||
pub t: f32,
|
||||
|
|
|
@ -18,3 +18,5 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
nalgebra = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
rhai = { workspace = true }
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
//! Various utilities
|
||||
|
||||
use anyhow::bail;
|
||||
use nalgebra::Vector2;
|
||||
|
||||
pub mod constants;
|
||||
|
@ -17,3 +18,18 @@ pub fn to_radians(degrees: f32) -> f32 {
|
|||
pub fn clockwise_angle(a: &Vector2<f32>, b: &Vector2<f32>) -> f32 {
|
||||
(a.x * b.y - b.x * a.y).atan2(a.dot(&b))
|
||||
}
|
||||
|
||||
/// Convert a rhai error to an anyhow error.
|
||||
/// We can't do this directly, since anyhow requires send + sync,
|
||||
/// and we don't need send+sync on rhai.
|
||||
pub fn rhai_error_to_anyhow<T, E>(r: Result<T, E>) -> anyhow::Result<T>
|
||||
where
|
||||
E: ToString,
|
||||
{
|
||||
match r {
|
||||
Ok(x) => Ok(x),
|
||||
Err(e) => {
|
||||
bail!("{}", e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue