Reworked ui scripting
parent
7ce169bbea
commit
47a73224c6
|
@ -887,7 +887,9 @@ dependencies = [
|
||||||
name = "galactica-util"
|
name = "galactica-util"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
|
"rhai",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -78,7 +78,6 @@ rhai = { version = "1.17.0", features = [
|
||||||
"f32_float",
|
"f32_float",
|
||||||
"only_i32",
|
"only_i32",
|
||||||
"metadata",
|
"metadata",
|
||||||
"sync",
|
|
||||||
"no_custom_syntax",
|
"no_custom_syntax",
|
||||||
"no_closure",
|
"no_closure",
|
||||||
] }
|
] }
|
||||||
|
|
9
TODO.md
9
TODO.md
|
@ -1,18 +1,17 @@
|
||||||
# Specific projects
|
# Specific projects
|
||||||
|
|
||||||
## Now:
|
## Now:
|
||||||
- outfitter
|
|
||||||
- Clean up scripting & errors
|
- Clean up scripting & errors
|
||||||
- Clean up sprite content (and content in general)
|
- Fix radar
|
||||||
- Flying UI
|
|
||||||
- Mouse colliders
|
- Mouse colliders
|
||||||
- UI captures input?
|
- UI captures input?
|
||||||
- No UI zoom scroll
|
- No UI zoom scroll
|
||||||
- Preserve aspect for icons
|
- Preserve aspect for icons
|
||||||
- Check game version in config
|
- outfitter
|
||||||
|
|
||||||
|
|
||||||
## Small jobs
|
## Small jobs
|
||||||
|
- Clean up sprite content (and content in general)
|
||||||
|
- Check game version in config
|
||||||
- Fix window resizing
|
- Fix window resizing
|
||||||
- Debug: show mouse position
|
- Debug: show mouse position
|
||||||
- 🌟 Better planet desc formatting
|
- 🌟 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) {
|
fn init(state) {
|
||||||
let ring = SpriteBuilder(
|
|
||||||
|
conf_set_starfield(true);
|
||||||
|
conf_set_phys(true);
|
||||||
|
|
||||||
|
add_sprite(
|
||||||
"ring",
|
"ring",
|
||||||
"ui::status",
|
"ui::status",
|
||||||
Rect(
|
Rect(
|
||||||
-5.0, -5.0, 100.0, 100.0,
|
-5.0, -5.0, 100.0, 100.0,
|
||||||
SpriteAnchor::NorthEast,
|
Anchor::NorthEast,
|
||||||
SpriteAnchor::NorthEast
|
Anchor::NorthEast
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let shield = RadialBuilder(
|
add_radialbar(
|
||||||
"shield", 2.5,
|
"shield", 2.5,
|
||||||
Color(0.3, 0.6, 0.8, 1.0),
|
Color(0.3, 0.6, 0.8, 1.0),
|
||||||
Rect(
|
Rect(
|
||||||
-9.5, -9.5, 91.0, 91.0,
|
-9.5, -9.5, 91.0, 91.0,
|
||||||
SpriteAnchor::NorthEast,
|
Anchor::NorthEast,
|
||||||
SpriteAnchor::NorthEast
|
Anchor::NorthEast
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
shield.set_progress(1.0);
|
|
||||||
|
|
||||||
let hull = RadialBuilder(
|
add_radialbar(
|
||||||
"hull", 2.5,
|
"hull", 2.5,
|
||||||
Color(0.8, 0.7, 0.5, 1.0),
|
Color(0.8, 0.7, 0.5, 1.0),
|
||||||
Rect(
|
Rect(
|
||||||
-13.5, -13.5, 83.0, 83.0,
|
-13.5, -13.5, 83.0, 83.0,
|
||||||
SpriteAnchor::NorthEast,
|
Anchor::NorthEast,
|
||||||
SpriteAnchor::NorthEast
|
Anchor::NorthEast
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
hull.set_progress(1.0);
|
|
||||||
|
|
||||||
return [
|
|
||||||
ring,
|
|
||||||
shield,
|
|
||||||
hull
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(state, event) {
|
fn event(state, event) {
|
||||||
if type_of(event) == "PlayerShipStateEvent" {
|
if type_of(event) == "PlayerShipStateEvent" {
|
||||||
if state.player_ship().is_landed() {
|
if state.player_ship().is_landed() {
|
||||||
return SceneAction::GoTo("landed");
|
go_to_scene("landed");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(state, elements) {
|
fn step(state) {
|
||||||
elements["shield"].set_val(
|
radialbar_set_val("shield",
|
||||||
state.player_ship().get_shields()
|
state.player_ship().get_shields()
|
||||||
/ state.player_ship().get_total_shields()
|
/ state.player_ship().get_total_shields()
|
||||||
);
|
);
|
||||||
elements["hull"].set_val(
|
radialbar_set_val("hull",
|
||||||
state.player_ship().get_hull()
|
state.player_ship().get_hull()
|
||||||
/ state.player_ship().get_total_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) {
|
fn init(state) {
|
||||||
let player = state.player_ship();
|
let player = state.player_ship();
|
||||||
|
|
||||||
let frame = SpriteBuilder(
|
conf_set_starfield(true);
|
||||||
"frame",
|
conf_set_phys(false);
|
||||||
"ui::planet",
|
|
||||||
|
add_sprite(
|
||||||
|
"button",
|
||||||
|
"ui::planet::button",
|
||||||
Rect(
|
Rect(
|
||||||
0.0, 0.0, 400.0, 297.866,
|
99.0, 128.0, 73.898, 18.708,
|
||||||
SpriteAnchor::Center,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::Center
|
Anchor::Center
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let landscape = SpriteBuilder(
|
add_sprite(
|
||||||
"landscape",
|
"landscape",
|
||||||
{
|
{
|
||||||
if player.is_landed() {
|
if player.is_landed() {
|
||||||
|
@ -29,73 +25,62 @@ fn init(state) {
|
||||||
},
|
},
|
||||||
Rect(
|
Rect(
|
||||||
-180.0, 142.0, 274.0, 135.0,
|
-180.0, 142.0, 274.0, 135.0,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::Center
|
Anchor::Center
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
landscape.set_mask("ui::landscapemask");
|
sprite_set_mask("landscape", "ui::landscapemask");
|
||||||
|
|
||||||
let button = SpriteBuilder(
|
add_sprite(
|
||||||
"button",
|
"frame",
|
||||||
"ui::planet::button",
|
"ui::planet",
|
||||||
Rect(
|
Rect(
|
||||||
99.0, 128.0, 73.898, 18.708,
|
0.0, 0.0, 400.0, 297.866,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::Center,
|
||||||
SpriteAnchor::Center
|
Anchor::Center
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let title = TextBoxBuilder(
|
|
||||||
|
add_textbox(
|
||||||
"title", 10.0, 10.0,
|
"title", 10.0, 10.0,
|
||||||
Rect(
|
Rect(
|
||||||
-70.79, 138.0, 59.867, 10.0,
|
-70.79, 138.0, 59.867, 10.0,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::Center
|
Anchor::Center
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
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() {
|
if player.is_landed() {
|
||||||
title.set_text(player.landed_on().name());
|
textbox_set_text("title", player.landed_on().name());
|
||||||
} else {
|
|
||||||
title.set_text("");
|
|
||||||
}
|
}
|
||||||
title.align_center();
|
|
||||||
title.font_serif();
|
|
||||||
title.weight_bold();
|
|
||||||
|
|
||||||
let desc = TextBoxBuilder(
|
add_textbox(
|
||||||
"desc", 7.5, 8.0,
|
"desc", 7.5, 8.0,
|
||||||
Rect(
|
Rect(
|
||||||
-178.92, -20.3, 343.0, 81.467,
|
-178.92, -20.3, 343.0, 81.467,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::Center
|
Anchor::Center
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
);
|
);
|
||||||
|
textbox_font_sans("desc");
|
||||||
if player.is_landed() {
|
if player.is_landed() {
|
||||||
desc.set_text(player.landed_on().desc());
|
textbox_set_text("desc", player.landed_on().desc());
|
||||||
} else {
|
|
||||||
desc.set_text("");
|
|
||||||
}
|
}
|
||||||
desc.font_sansserif();
|
|
||||||
|
|
||||||
return [
|
|
||||||
button,
|
|
||||||
landscape,
|
|
||||||
frame,
|
|
||||||
title,
|
|
||||||
desc,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(state, event) {
|
fn event(state, event) {
|
||||||
if type_of(event) == "MouseHoverEvent" {
|
if type_of(event) == "MouseHoverEvent" {
|
||||||
let element = event.element();
|
let element = event.element();
|
||||||
if element.has_name("button") {
|
if element == "button" {
|
||||||
if event.is_enter() {
|
if event.is_enter() {
|
||||||
element.take_edge("on:top", 0.1);
|
sprite_take_edge("button", "on:top", 0.1);
|
||||||
} else {
|
} else {
|
||||||
element.take_edge("off:top", 0.1);
|
sprite_take_edge("button", "off:top", 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -104,19 +89,21 @@ fn event(state, event) {
|
||||||
|
|
||||||
if type_of(event) == "MouseClickEvent" {
|
if type_of(event) == "MouseClickEvent" {
|
||||||
if !event.is_down() {
|
if !event.is_down() {
|
||||||
return SceneAction::None;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let element = event.element();
|
let element = event.element();
|
||||||
if element.has_name("button") {
|
if element == "button" {
|
||||||
return SceneAction::GoTo("outfitter");
|
go_to_scene("outfitter");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if type_of(event) == "PlayerShipStateEvent" {
|
if type_of(event) == "PlayerShipStateEvent" {
|
||||||
if !state.player_ship().is_landed() {
|
if !state.player_ship().is_landed() {
|
||||||
return SceneAction::GoTo("flying");
|
go_to_scene("flying");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,196 +1,174 @@
|
||||||
fn config() {
|
|
||||||
let config = SceneConfig();
|
|
||||||
config.show_starfield(true);
|
|
||||||
config.show_phys(false);
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(state) {
|
fn init(state) {
|
||||||
let se_box = SpriteBuilder(
|
|
||||||
|
conf_set_starfield(true);
|
||||||
|
conf_set_phys(false);
|
||||||
|
|
||||||
|
add_sprite(
|
||||||
"se_box",
|
"se_box",
|
||||||
"ui::outfitterbox",
|
"ui::outfitterbox",
|
||||||
Rect(
|
Rect(
|
||||||
-1.0, -1.0, 202.345, 133.409,
|
-1.0, -1.0, 202.345, 133.409,
|
||||||
SpriteAnchor::SouthWest,
|
Anchor::SouthWest,
|
||||||
SpriteAnchor::SouthWest
|
Anchor::SouthWest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let exit_text = TextBoxBuilder(
|
add_textbox(
|
||||||
"exit_text", 10.0, 10.0,
|
"exit_text", 10.0, 10.0,
|
||||||
Rect(
|
Rect(
|
||||||
122.71, 48.0, 51.0, 12.0,
|
122.71, 48.0, 51.0, 12.0,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::SouthWest
|
Anchor::SouthWest
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
);
|
);
|
||||||
exit_text.set_text("Exit");
|
textbox_font_serif("exit_text");
|
||||||
exit_text.font_serif();
|
textbox_align_center("exit_text");
|
||||||
exit_text.align_center();
|
textbox_set_text("exit_text", "Exit");
|
||||||
|
|
||||||
let exit_button = SpriteBuilder(
|
add_sprite(
|
||||||
"exit_button",
|
"exit_button",
|
||||||
"ui::button",
|
"ui::button",
|
||||||
Rect(
|
Rect(
|
||||||
113.35, 52.0, 69.8, 18.924,
|
113.35, 52.0, 69.8, 18.924,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::SouthWest
|
Anchor::SouthWest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
add_sprite(
|
||||||
|
|
||||||
let ship_bg = SpriteBuilder(
|
|
||||||
"ship_bg",
|
"ship_bg",
|
||||||
"ui::outfitter-ship-bg",
|
"ui::outfitter-ship-bg",
|
||||||
Rect(
|
Rect(
|
||||||
16.0, -16.0, 190.0, 353.0,
|
16.0, -16.0, 190.0, 353.0,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::NorthWest
|
Anchor::NorthWest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let ship_thumb = SpriteBuilder(
|
add_sprite(
|
||||||
"ship_thumb",
|
"ship_thumb",
|
||||||
"icon::gypsum",
|
"icon::gypsum",
|
||||||
Rect(
|
Rect(
|
||||||
111.0, -95.45, 90.0, 90.0,
|
111.0, -95.45, 90.0, 90.0,
|
||||||
SpriteAnchor::Center,
|
Anchor::Center,
|
||||||
SpriteAnchor::NorthWest
|
Anchor::NorthWest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let ship_name = TextBoxBuilder(
|
add_textbox(
|
||||||
"ship_name", 10.0, 10.0,
|
"ship_name", 10.0, 10.0,
|
||||||
Rect(
|
Rect(
|
||||||
111.0, -167.27, 145.0, 10.0,
|
111.0, -167.27, 145.0, 10.0,
|
||||||
SpriteAnchor::Center,
|
Anchor::Center,
|
||||||
SpriteAnchor::NorthWest
|
Anchor::NorthWest
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
);
|
);
|
||||||
ship_name.set_text("Hyperion");
|
textbox_font_serif("ship_name");
|
||||||
ship_name.font_serif();
|
textbox_align_center("ship_name");
|
||||||
ship_name.align_center();
|
textbox_set_text("ship_name", "Hyperion");
|
||||||
|
|
||||||
let ship_type = TextBoxBuilder(
|
add_textbox(
|
||||||
"ship_type", 7.0, 8.5,
|
"ship_type", 7.0, 8.5,
|
||||||
Rect(
|
Rect(
|
||||||
111.0, -178.0, 145.0, 8.5,
|
111.0, -178.0, 145.0, 8.5,
|
||||||
SpriteAnchor::Center,
|
Anchor::Center,
|
||||||
SpriteAnchor::NorthWest
|
Anchor::NorthWest
|
||||||
),
|
),
|
||||||
Color(0.7, 0.7, 0.7, 1.0)
|
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() {
|
if state.player_ship().is_some() {
|
||||||
ship_type.set_text(state.player_ship().name());
|
textbox_set_text("ship_type", state.player_ship().name());
|
||||||
} else {
|
|
||||||
ship_type.set_text("ERR: SHIP IS NONE");
|
|
||||||
}
|
}
|
||||||
ship_type.font_sansserif();
|
|
||||||
ship_type.align_center();
|
|
||||||
|
|
||||||
let ship_stats = TextBoxBuilder(
|
|
||||||
|
add_textbox(
|
||||||
"ship_stats", 7.0, 8.5,
|
"ship_stats", 7.0, 8.5,
|
||||||
Rect(
|
Rect(
|
||||||
38.526, -192.332, 144.948, 154.5,
|
38.526, -192.332, 144.948, 154.5,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
);
|
);
|
||||||
ship_stats.set_text("Earth");
|
textbox_font_mono("ship_stats");
|
||||||
ship_stats.font_monospace();
|
textbox_set_text("ship_stats", "Earth");
|
||||||
|
|
||||||
|
|
||||||
let outfit_bg = SpriteBuilder(
|
add_sprite(
|
||||||
"outfit_bg",
|
"outfit_bg",
|
||||||
"ui::outfitter-outfit-bg",
|
"ui::outfitter-outfit-bg",
|
||||||
Rect(
|
Rect(
|
||||||
-16.0, -16.0, 300.0, 480.0,
|
-16.0, -16.0, 300.0, 480.0,
|
||||||
SpriteAnchor::NorthEast,
|
Anchor::NorthEast,
|
||||||
SpriteAnchor::NorthEast
|
Anchor::NorthEast
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let outfit_thumb = SpriteBuilder(
|
add_sprite(
|
||||||
"outfit_thumb",
|
"outfit_thumb",
|
||||||
"icon::engine",
|
"icon::engine",
|
||||||
Rect(
|
Rect(
|
||||||
-166.0, -109.0, 90.0, 90.0,
|
-166.0, -109.0, 90.0, 90.0,
|
||||||
SpriteAnchor::Center,
|
Anchor::Center,
|
||||||
SpriteAnchor::NorthEast
|
Anchor::NorthEast
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let outfit_name = TextBoxBuilder(
|
add_textbox(
|
||||||
"outfit_name", 16.0, 16.0,
|
"outfit_name", 16.0, 16.0,
|
||||||
Rect(
|
Rect(
|
||||||
-312.0, -20.0, 200.0, 16.0,
|
-312.0, -20.0, 200.0, 16.0,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::NorthEast,
|
Anchor::NorthEast,
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
);
|
);
|
||||||
outfit_name.set_text("Earth");
|
textbox_font_serif("outfit_name");
|
||||||
outfit_name.font_serif();
|
textbox_weight_bold("outfit_name");
|
||||||
outfit_name.weight_bold();
|
textbox_set_text("outfit_name", "Earth");
|
||||||
|
|
||||||
let outfit_desc = TextBoxBuilder(
|
add_textbox(
|
||||||
"outfit_desc", 7.0, 8.5,
|
"outfit_desc", 7.0, 8.5,
|
||||||
Rect(
|
Rect(
|
||||||
-166.0, -219.0, 260.0, 78.0,
|
-166.0, -219.0, 260.0, 78.0,
|
||||||
SpriteAnchor::Center,
|
Anchor::Center,
|
||||||
SpriteAnchor::NorthEast,
|
Anchor::NorthEast,
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
);
|
);
|
||||||
outfit_desc.set_text("Earth");
|
textbox_font_serif("outfit_desc");
|
||||||
outfit_desc.font_serif();
|
textbox_set_text("outfit_desc", "Earth");
|
||||||
|
|
||||||
let outfit_stats = TextBoxBuilder(
|
|
||||||
|
add_textbox(
|
||||||
"outfit_stats", 7.0, 8.5,
|
"outfit_stats", 7.0, 8.5,
|
||||||
Rect(
|
Rect(
|
||||||
-295.0, -271.0, 164.0, 216.0,
|
-295.0, -271.0, 164.0, 216.0,
|
||||||
SpriteAnchor::NorthWest,
|
Anchor::NorthWest,
|
||||||
SpriteAnchor::NorthEast,
|
Anchor::NorthEast,
|
||||||
),
|
),
|
||||||
Color(1.0, 1.0, 1.0, 1.0)
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
);
|
);
|
||||||
outfit_stats.set_text("Earth");
|
textbox_font_mono("outfit_stats");
|
||||||
ship_stats.font_monospace();
|
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) {
|
fn event(state, event) {
|
||||||
if type_of(event) == "MouseHoverEvent" {
|
if type_of(event) == "MouseHoverEvent" {
|
||||||
let element = event.element();
|
let element = event.element();
|
||||||
if element.has_name("exit_button") {
|
if element == "exit_button" {
|
||||||
if event.is_enter() {
|
if event.is_enter() {
|
||||||
element.take_edge("on:top", 0.1);
|
sprite_take_edge("exit_button", "on:top", 0.1);
|
||||||
} else {
|
} else {
|
||||||
element.take_edge("off:top", 0.1);
|
sprite_take_edge("exit_button", "off:top", 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -199,19 +177,21 @@ fn event(state, event) {
|
||||||
|
|
||||||
if type_of(event) == "MouseClickEvent" {
|
if type_of(event) == "MouseClickEvent" {
|
||||||
if !event.is_down() {
|
if !event.is_down() {
|
||||||
return SceneAction::None;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let element = event.element();
|
let element = event.element();
|
||||||
if element.has_name("exit_button") {
|
if element == "exit_button" {
|
||||||
return SceneAction::GoTo("landed");
|
go_to_scene("landed");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if type_of(event) == "PlayerShipStateEvent" {
|
if type_of(event) == "PlayerShipStateEvent" {
|
||||||
if !state.player_ship().is_landed() {
|
if !state.player_ship().is_landed() {
|
||||||
return SceneAction::GoTo("flying");
|
go_to_scene("flying");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use rhai::AST;
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use galactica_packer::SpriteAtlas;
|
use galactica_packer::SpriteAtlas;
|
||||||
|
use galactica_util::rhai_error_to_anyhow;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -65,9 +66,8 @@ pub(crate) mod syntax {
|
||||||
for (n, p) in self.ui_scene {
|
for (n, p) in self.ui_scene {
|
||||||
ui_scenes.insert(
|
ui_scenes.insert(
|
||||||
n.clone(),
|
n.clone(),
|
||||||
engine
|
rhai_error_to_anyhow(engine.compile_file(content_root.join(p)))
|
||||||
.compile_file(content_root.join(p))
|
.with_context(|| format!("while loading scene script `{n}`"))?,
|
||||||
.with_context(|| format!("while loading scene script `{}`", n))?,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use galactica_system::data::ShipState;
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
||||||
use nalgebra::{Point2, Point3};
|
use nalgebra::{Point2, Point3};
|
||||||
use std::{iter, sync::Arc};
|
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit;
|
use winit;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
shaderprocessor::preprocess_shader,
|
shaderprocessor::preprocess_shader,
|
||||||
starfield::Starfield,
|
starfield::Starfield,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
ui::UiManager,
|
ui::UiScriptExecutor,
|
||||||
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
||||||
RenderInput, RenderState, VertexBuffers,
|
RenderInput, RenderState, VertexBuffers,
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,7 @@ pub struct GPUState {
|
||||||
pub(crate) starfield: Starfield,
|
pub(crate) starfield: Starfield,
|
||||||
pub(crate) texture_array: TextureArray,
|
pub(crate) texture_array: TextureArray,
|
||||||
pub(crate) state: RenderState,
|
pub(crate) state: RenderState,
|
||||||
pub(crate) ui: UiManager,
|
pub(crate) ui: UiScriptExecutor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GPUState {
|
impl GPUState {
|
||||||
|
@ -221,14 +221,14 @@ impl GPUState {
|
||||||
window_aspect,
|
window_aspect,
|
||||||
global_uniform,
|
global_uniform,
|
||||||
vertex_buffers,
|
vertex_buffers,
|
||||||
|
text_renderer,
|
||||||
text_atlas,
|
text_atlas,
|
||||||
text_cache,
|
text_cache,
|
||||||
text_font_system,
|
text_font_system: Rc::new(RefCell::new(text_font_system)),
|
||||||
text_renderer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
ui: UiManager::new(ct, &mut state),
|
ui: UiScriptExecutor::new(ct, &mut state),
|
||||||
device,
|
device,
|
||||||
config,
|
config,
|
||||||
surface,
|
surface,
|
||||||
|
@ -332,7 +332,9 @@ impl GPUState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.ui.get_config().show_phys {
|
let config = self.ui.get_config();
|
||||||
|
|
||||||
|
if config.show_phys {
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
|
|
||||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||||
|
@ -349,14 +351,14 @@ impl GPUState {
|
||||||
self.push_effects(&input, (clip_ne, clip_sw));
|
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,
|
// 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.
|
||||||
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
||||||
render_pass.set_bind_group(1, &self.state.global_uniform.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
|
// Starfield pipeline
|
||||||
self.state
|
self.state
|
||||||
.vertex_buffers
|
.vertex_buffers
|
||||||
|
@ -370,7 +372,7 @@ impl GPUState {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ui.get_config().show_phys {
|
if config.show_phys {
|
||||||
// Sprite pipeline
|
// Sprite pipeline
|
||||||
self.state
|
self.state
|
||||||
.vertex_buffers
|
.vertex_buffers
|
||||||
|
@ -409,27 +411,30 @@ impl GPUState {
|
||||||
0..self.state.get_radialbar_counter(),
|
0..self.state.get_radialbar_counter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let textareas = self.ui.get_textareas(&input, &self.state);
|
{
|
||||||
self.state
|
self.state
|
||||||
.text_renderer
|
.text_renderer
|
||||||
.prepare(
|
.prepare(
|
||||||
&self.device,
|
&self.device,
|
||||||
&self.state.queue,
|
&self.state.queue,
|
||||||
&mut self.state.text_font_system,
|
&mut self.state.text_font_system.borrow_mut(),
|
||||||
&mut self.state.text_atlas,
|
&mut self.state.text_atlas,
|
||||||
Resolution {
|
Resolution {
|
||||||
width: self.state.window_size.width,
|
width: self.state.window_size.width,
|
||||||
height: self.state.window_size.height,
|
height: self.state.window_size.height,
|
||||||
},
|
},
|
||||||
textareas,
|
(*self.ui.state)
|
||||||
&mut self.state.text_cache,
|
.borrow_mut()
|
||||||
)
|
.get_textareas(&input, &self.state.window),
|
||||||
.unwrap();
|
&mut self.state.text_cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
self.state
|
self.state
|
||||||
.text_renderer
|
.text_renderer
|
||||||
.render(&self.state.text_atlas, &mut render_pass)
|
.render(&mut self.state.text_atlas, &mut render_pass)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// begin_render_pass borrows encoder mutably,
|
// begin_render_pass borrows encoder mutably,
|
||||||
// so we need to drop it before calling finish.
|
// so we need to drop it before calling finish.
|
||||||
|
|
|
@ -3,6 +3,7 @@ use galactica_util::constants::{
|
||||||
OBJECT_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT,
|
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::{cell::RefCell, rc::Rc};
|
||||||
use wgpu::BufferAddress;
|
use wgpu::BufferAddress;
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
|
@ -101,10 +102,10 @@ pub(crate) struct RenderState {
|
||||||
pub global_uniform: GlobalUniform,
|
pub global_uniform: GlobalUniform,
|
||||||
pub vertex_buffers: VertexBuffers,
|
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_cache: SwashCache,
|
||||||
pub text_atlas: TextAtlas,
|
pub text_atlas: TextAtlas,
|
||||||
pub text_renderer: TextRenderer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderState {
|
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 rhai::{CustomType, ImmutableString, TypeBuilder};
|
||||||
|
|
||||||
use super::SpriteElement;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MouseClickEvent {
|
pub struct MouseClickEvent {
|
||||||
pub down: bool,
|
pub down: bool,
|
||||||
pub element: SpriteElement,
|
pub element: ImmutableString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomType for MouseClickEvent {
|
impl CustomType for MouseClickEvent {
|
||||||
|
@ -20,7 +18,7 @@ impl CustomType for MouseClickEvent {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MouseHoverEvent {
|
pub struct MouseHoverEvent {
|
||||||
pub enter: bool,
|
pub enter: bool,
|
||||||
pub element: SpriteElement,
|
pub element: ImmutableString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomType for MouseHoverEvent {
|
impl CustomType for MouseHoverEvent {
|
||||||
|
|
|
@ -1,28 +1,14 @@
|
||||||
|
mod anchor;
|
||||||
mod color;
|
mod color;
|
||||||
mod config;
|
|
||||||
mod event;
|
mod event;
|
||||||
mod radialbuilder;
|
|
||||||
mod radialelement;
|
|
||||||
mod rect;
|
mod rect;
|
||||||
mod sceneaction;
|
|
||||||
mod spritebuilder;
|
|
||||||
mod spriteelement;
|
|
||||||
mod state;
|
mod state;
|
||||||
mod textboxbuilder;
|
|
||||||
mod util;
|
|
||||||
|
|
||||||
|
pub use anchor::*;
|
||||||
pub use color::*;
|
pub use color::*;
|
||||||
pub use config::*;
|
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use radialbuilder::*;
|
|
||||||
pub use radialelement::*;
|
|
||||||
pub use rect::*;
|
pub use rect::*;
|
||||||
pub use sceneaction::*;
|
|
||||||
pub use spritebuilder::*;
|
|
||||||
pub use spriteelement::*;
|
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
pub use textboxbuilder::*;
|
|
||||||
pub use util::*;
|
|
||||||
|
|
||||||
use rhai::{exported_module, Engine};
|
use rhai::{exported_module, Engine};
|
||||||
|
|
||||||
|
@ -31,25 +17,15 @@ pub fn register_into_engine(engine: &mut Engine) {
|
||||||
// Helpers
|
// Helpers
|
||||||
.build_type::<Rect>()
|
.build_type::<Rect>()
|
||||||
.build_type::<Color>()
|
.build_type::<Color>()
|
||||||
.build_type::<SceneConfig>()
|
|
||||||
// State
|
// State
|
||||||
.build_type::<State>()
|
.build_type::<State>()
|
||||||
.build_type::<ShipState>()
|
.build_type::<ShipState>()
|
||||||
.build_type::<SystemObjectState>()
|
.build_type::<SystemObjectState>()
|
||||||
// Builders
|
|
||||||
.build_type::<RadialBuilder>()
|
|
||||||
.build_type::<SpriteBuilder>()
|
|
||||||
.build_type::<TextBoxBuilder>()
|
|
||||||
// Elements
|
|
||||||
.build_type::<SpriteElement>()
|
|
||||||
.build_type::<RadialElement>()
|
|
||||||
// Events
|
// Events
|
||||||
.build_type::<MouseClickEvent>()
|
.build_type::<MouseClickEvent>()
|
||||||
.build_type::<MouseHoverEvent>()
|
.build_type::<MouseHoverEvent>()
|
||||||
.build_type::<PlayerShipStateEvent>()
|
.build_type::<PlayerShipStateEvent>()
|
||||||
// Larger modules
|
// Bigger modules
|
||||||
.register_type_with_name::<SpriteAnchor>("SpriteAnchor")
|
.register_type_with_name::<Anchor>("Anchor")
|
||||||
.register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into())
|
.register_static_module("Anchor", exported_module!(anchor_mod).into());
|
||||||
.register_type_with_name::<SceneAction>("SceneAction")
|
|
||||||
.register_static_module("SceneAction", exported_module!(sceneaction_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, sync::Arc};
|
|
||||||
|
|
||||||
use crate::ui::util::RadialBar;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RadialElement {
|
|
||||||
pub bar: Rc<RefCell<RadialBar>>,
|
|
||||||
pub ct: Arc<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this
|
|
||||||
unsafe impl Send for RadialElement {}
|
|
||||||
unsafe impl Sync for RadialElement {}
|
|
||||||
|
|
||||||
impl RadialElement {
|
|
||||||
pub fn new(ct: Arc<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 nalgebra::{Point2, Vector2};
|
||||||
use rhai::{CustomType, TypeBuilder};
|
use rhai::{CustomType, TypeBuilder};
|
||||||
use winit::dpi::LogicalSize;
|
use winit::{dpi::LogicalSize, window::Window};
|
||||||
|
|
||||||
use super::SpriteAnchor;
|
use super::Anchor;
|
||||||
use crate::{RenderInput, RenderState};
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub pos: Point2<f32>,
|
pub pos: Point2<f32>,
|
||||||
pub dim: Vector2<f32>,
|
pub dim: Vector2<f32>,
|
||||||
pub anchor_self: SpriteAnchor,
|
pub anchor_self: Anchor,
|
||||||
pub anchor_parent: SpriteAnchor,
|
pub anchor_parent: Anchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rect {
|
impl Rect {
|
||||||
pub fn new(
|
pub fn new(x: f32, y: f32, w: f32, h: f32, anchor_self: Anchor, anchor_parent: Anchor) -> Self {
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
w: f32,
|
|
||||||
h: f32,
|
|
||||||
anchor_self: SpriteAnchor,
|
|
||||||
anchor_parent: SpriteAnchor,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
pos: Point2::new(x, y),
|
pos: Point2::new(x, y),
|
||||||
dim: Vector2::new(w, h),
|
dim: Vector2::new(w, h),
|
||||||
|
@ -30,8 +23,8 @@ impl Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_centered(&self, state: &RenderState, ui_scale: f32) -> CenteredRect {
|
pub fn to_centered(&self, window: &Window, ui_scale: f32) -> CenteredRect {
|
||||||
let w: LogicalSize<f32> = state.window_size.to_logical(state.window.scale_factor());
|
let w: LogicalSize<f32> = window.inner_size().to_logical(window.scale_factor());
|
||||||
let w = Vector2::new(w.width, w.height);
|
let w = Vector2::new(w.width, w.height);
|
||||||
|
|
||||||
let mut pos = self.pos * ui_scale;
|
let mut pos = self.pos * ui_scale;
|
||||||
|
@ -39,42 +32,42 @@ impl Rect {
|
||||||
|
|
||||||
// Origin
|
// Origin
|
||||||
match self.anchor_parent {
|
match self.anchor_parent {
|
||||||
SpriteAnchor::Center => {}
|
Anchor::Center => {}
|
||||||
|
|
||||||
SpriteAnchor::NorthWest => {
|
Anchor::NorthWest => {
|
||||||
pos += Vector2::new(-w.x, w.y) / 2.0;
|
pos += Vector2::new(-w.x, w.y) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpriteAnchor::SouthWest => {
|
Anchor::SouthWest => {
|
||||||
pos += Vector2::new(-w.x, -w.y) / 2.0;
|
pos += Vector2::new(-w.x, -w.y) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpriteAnchor::NorthEast => {
|
Anchor::NorthEast => {
|
||||||
pos += Vector2::new(w.x, w.y) / 2.0;
|
pos += Vector2::new(w.x, w.y) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpriteAnchor::SouthEast => {
|
Anchor::SouthEast => {
|
||||||
pos += Vector2::new(w.x, -w.y) / 2.0;
|
pos += Vector2::new(w.x, -w.y) / 2.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offset for self dimensions
|
// Offset for self dimensions
|
||||||
match self.anchor_self {
|
match self.anchor_self {
|
||||||
SpriteAnchor::Center => {}
|
Anchor::Center => {}
|
||||||
|
|
||||||
SpriteAnchor::NorthWest => {
|
Anchor::NorthWest => {
|
||||||
pos += Vector2::new(dim.x, -dim.y) / 2.0;
|
pos += Vector2::new(dim.x, -dim.y) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpriteAnchor::NorthEast => {
|
Anchor::NorthEast => {
|
||||||
pos += Vector2::new(-dim.x, -dim.y) / 2.0;
|
pos += Vector2::new(-dim.x, -dim.y) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpriteAnchor::SouthWest => {
|
Anchor::SouthWest => {
|
||||||
pos += Vector2::new(dim.x, dim.y) / 2.0;
|
pos += Vector2::new(dim.x, dim.y) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpriteAnchor::SouthEast => {
|
Anchor::SouthEast => {
|
||||||
pos += Vector2::new(-dim.x, dim.y) / 2.0;
|
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, sync::Arc};
|
|
||||||
|
|
||||||
use crate::ui::util::Sprite;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct SpriteElement {
|
|
||||||
pub sprite: Rc<RefCell<Sprite>>,
|
|
||||||
pub ct: Arc<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this
|
|
||||||
unsafe impl Send for SpriteElement {}
|
|
||||||
unsafe impl Sync for SpriteElement {}
|
|
||||||
|
|
||||||
impl SpriteElement {
|
|
||||||
pub fn new(ct: Arc<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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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, sync::Arc};
|
|
||||||
|
|
||||||
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: Arc<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: Arc<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: Arc<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: Arc<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: Arc<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 api;
|
||||||
mod event;
|
mod event;
|
||||||
mod manager;
|
mod executor;
|
||||||
mod uielement;
|
mod state;
|
||||||
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub(crate) use manager::UiManager;
|
pub(crate) use executor::UiScriptExecutor;
|
||||||
pub(crate) use uielement::UiElement;
|
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};
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
|
@ -9,9 +10,12 @@ pub(crate) struct FpsIndicator {
|
||||||
|
|
||||||
impl FpsIndicator {
|
impl FpsIndicator {
|
||||||
pub fn new(state: &mut RenderState) -> Self {
|
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(
|
buffer.set_size(
|
||||||
&mut state.text_font_system,
|
&mut state.text_font_system.borrow_mut(),
|
||||||
state.window_size.width as f32,
|
state.window_size.width as f32,
|
||||||
state.window_size.height as f32,
|
state.window_size.height as f32,
|
||||||
);
|
);
|
||||||
|
@ -24,7 +28,7 @@ impl FpsIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if self.update_counter > 0 {
|
||||||
self.update_counter -= 1;
|
self.update_counter -= 1;
|
||||||
return;
|
return;
|
||||||
|
@ -32,17 +36,17 @@ impl FpsIndicator {
|
||||||
self.update_counter = 100;
|
self.update_counter = 100;
|
||||||
|
|
||||||
self.buffer.set_text(
|
self.buffer.set_text(
|
||||||
&mut state.text_font_system,
|
font,
|
||||||
&input.timing.get_string(),
|
&input.timing.get_string(),
|
||||||
Attrs::new().family(Family::Monospace),
|
Attrs::new().family(Family::Monospace),
|
||||||
Shaping::Basic,
|
Shaping::Basic,
|
||||||
);
|
);
|
||||||
self.buffer.shape_until_scroll(&mut state.text_font_system);
|
self.buffer.shape_until_scroll(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> FpsIndicator {
|
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 {
|
TextArea {
|
||||||
buffer: &self.buffer,
|
buffer: &self.buffer,
|
||||||
left: 10.0,
|
left: 10.0,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use galactica_content::Content;
|
|
||||||
use std::f32::consts::TAU;
|
use std::f32::consts::TAU;
|
||||||
|
|
||||||
|
use rhai::ImmutableString;
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::super::api::Rect;
|
||||||
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RadialBar {
|
pub struct RadialBar {
|
||||||
pub name: String,
|
pub name: ImmutableString,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
stroke: f32,
|
stroke: f32,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
@ -15,8 +16,7 @@ pub struct RadialBar {
|
||||||
|
|
||||||
impl RadialBar {
|
impl RadialBar {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
_ct: &Content,
|
name: ImmutableString,
|
||||||
name: String,
|
|
||||||
stroke: f32,
|
stroke: f32,
|
||||||
color: Color,
|
color: Color,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
|
@ -36,7 +36,9 @@ impl RadialBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
.rect
|
||||||
|
.to_centered(&state.window, input.ct.get_config().ui_scale);
|
||||||
|
|
||||||
state.push_radialbar_buffer(RadialBarInstance {
|
state.push_radialbar_buffer(RadialBarInstance {
|
||||||
position: [rect.pos.x, rect.pos.y],
|
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 super::super::api::Rect;
|
||||||
use crate::{ui::event::Event, vertexbuffer::types::UiInstance, RenderInput, RenderState};
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sprite {
|
pub struct Sprite {
|
||||||
pub name: String,
|
pub anim: SpriteAutomaton,
|
||||||
|
pub name: ImmutableString,
|
||||||
/// If this is none, this was constructed with an invalid sprite
|
|
||||||
pub anim: Option<SpriteAutomaton>,
|
|
||||||
|
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
mask: Option<SpriteHandle>,
|
mask: Option<SpriteHandle>,
|
||||||
|
@ -22,57 +19,30 @@ pub struct Sprite {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sprite {
|
impl Sprite {
|
||||||
pub fn new(
|
pub fn new(ct: &Content, name: ImmutableString, sprite: SpriteHandle, rect: Rect) -> Self {
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
anim: sprite_handle.map(|x| SpriteAutomaton::new(&ct, x)),
|
anim: SpriteAutomaton::new(&ct, sprite),
|
||||||
rect,
|
rect,
|
||||||
mask,
|
mask: None,
|
||||||
has_mouse: false,
|
has_mouse: false,
|
||||||
has_click: false,
|
has_click: false,
|
||||||
waiting_for_release: false,
|
waiting_for_release: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
pub fn set_mask(&mut self, mask: Option<SpriteHandle>) {
|
||||||
if self.anim.is_none() {
|
self.mask = mask;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
// TODO: use both dimensions,
|
||||||
// not just height
|
// 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 {
|
state.push_ui_buffer(UiInstance {
|
||||||
position: rect.pos.into(),
|
position: rect.pos.into(),
|
||||||
|
@ -93,7 +63,9 @@ impl Sprite {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
|
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() {
|
if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
|
||||||
self.waiting_for_release = false;
|
self.waiting_for_release = false;
|
||||||
|
@ -139,13 +111,6 @@ impl Sprite {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
||||||
if self.anim.is_none() {
|
self.anim.step(&input.ct, input.time_since_last_run);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.anim
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.step(&input.ct, input.time_since_last_run);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
use glyphon::{
|
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 nalgebra::Vector2;
|
||||||
|
use rhai::ImmutableString;
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::super::api::Rect;
|
||||||
use crate::{ui::api, RenderInput, RenderState};
|
use crate::{ui::api, RenderInput};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextBox {
|
pub struct TextBox {
|
||||||
pub name: String,
|
pub name: ImmutableString,
|
||||||
|
|
||||||
|
text: String,
|
||||||
justify: Align,
|
justify: Align,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
|
@ -18,58 +23,78 @@ pub struct TextBox {
|
||||||
|
|
||||||
impl TextBox {
|
impl TextBox {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
state: &mut RenderState,
|
font: &mut FontSystem,
|
||||||
name: String,
|
name: ImmutableString,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
justify: Align,
|
|
||||||
attrs: AttrsOwned,
|
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
color: api::Color,
|
color: api::Color,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut buffer = Buffer::new(
|
let mut buffer = Buffer::new(font, Metrics::new(font_size, line_height));
|
||||||
&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
|
// 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 {
|
Self {
|
||||||
name,
|
name,
|
||||||
justify,
|
|
||||||
rect,
|
rect,
|
||||||
buffer,
|
buffer,
|
||||||
color,
|
color,
|
||||||
attrs,
|
justify: Align::Left,
|
||||||
|
attrs: AttrsOwned::new(Attrs::new()),
|
||||||
|
text: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, state: &mut RenderState, text: &str) {
|
fn reflow(&mut self, font: &mut FontSystem) {
|
||||||
self.buffer.set_text(
|
self.buffer
|
||||||
&mut state.text_font_system,
|
.set_text(font, &self.text, self.attrs.as_attrs(), Shaping::Advanced);
|
||||||
text,
|
|
||||||
self.attrs.as_attrs(),
|
|
||||||
Shaping::Advanced,
|
|
||||||
);
|
|
||||||
|
|
||||||
for l in &mut self.buffer.lines {
|
for l in &mut self.buffer.lines {
|
||||||
l.set_align(Some(self.justify));
|
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 {
|
impl<'a, 'b: 'a> TextBox {
|
||||||
pub fn get_textarea(&'b self, state: &RenderState, input: &RenderInput) -> TextArea<'a> {
|
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> {
|
||||||
let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
let rect = self
|
||||||
|
.rect
|
||||||
|
.to_centered(window, input.ct.get_config().ui_scale);
|
||||||
|
|
||||||
// Glypon works with physical pixels, so we must do some conversion
|
// 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(
|
let corner_ne = Vector2::new(
|
||||||
(rect.pos.x - rect.dim.x / 2.0) * fac + state.window_size.width as f32 / 2.0,
|
(rect.pos.x - rect.dim.x / 2.0) * fac + window.inner_size().width as f32 / 2.0,
|
||||||
state.window_size.height as f32 / 2.0 - (rect.pos.y * fac + rect.dim.y / 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 corner_sw = corner_ne + rect.dim * fac;
|
||||||
let c = self.color.as_array_u8();
|
let c = self.color.as_array_u8();
|
||||||
|
|
|
@ -18,3 +18,5 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra = { workspace = true }
|
nalgebra = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
rhai = { workspace = true }
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
//! Various utilities
|
//! Various utilities
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
|
|
||||||
pub mod constants;
|
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 {
|
pub fn clockwise_angle(a: &Vector2<f32>, b: &Vector2<f32>) -> f32 {
|
||||||
(a.x * b.y - b.x * a.y).atan2(a.dot(&b))
|
(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