Compare commits

..

No commits in common. "a86b57d85cf33e1ab03817432b124609cd575ac0" and "23451eeb4b35c405ce31358212d4d082b112fc4f" have entirely different histories.

21 changed files with 531 additions and 778 deletions

View File

@ -1,11 +1,11 @@
# Specific projects # Specific projects
## Now: ## Now:
- hide ui element
- zoom limits
- clean up content - clean up content
- Clean up state api - Clean up state api
- Clean up & document UI api - Clean up & document UI api
- Persistent variables in ui scripts
- Better planet icons
- Clean up scripting errors - Clean up scripting errors
- Mouse colliders - Mouse colliders
- Fade sprites and text in scrollbox - Fade sprites and text in scrollbox
@ -14,7 +14,6 @@
- fps textbox positioning - fps textbox positioning
## Small jobs ## Small jobs
- Better planet icons in radar
- Clean up sprite content (and content in general) - Clean up sprite content (and content in general)
- Check game version in config - Check game version in config
- Fix window resizing - Fix window resizing

2
assets

@ -1 +1 @@
Subproject commit b8ea8c5a04c0216304a81ebf396b7320a709d6ed Subproject commit 1400f7bb89f1190a11a7371bb23778881073a49f

View File

@ -92,23 +92,7 @@ file = "ui/landscape/test.png"
file = "ui/landscape-mask.png" file = "ui/landscape-mask.png"
[sprite."ui::outfitbg"] [sprite."ui::outfitbg"]
start_at = "off:top" file = "ui/outfit-bg.png"
section.off.top = "stop"
section.off.bot = "stop"
section.off.timing.duration = 0.5
section.off.frames = ["ui/outfitbg.png"]
section.hover.top = "stop"
section.hover.bot = "stop"
section.hover.timing.duration = 0.5
section.hover.frames = ["ui/outfitbg-hover.png"]
section.selected.top = "stop"
section.selected.bot = "stop"
section.selected.timing.duration = 0.5
section.selected.frames = ["ui/outfitbg-selected.png"]
section.hoverselected.top = "stop"
section.hoverselected.bot = "stop"
section.hoverselected.timing.duration = 0.5
section.hoverselected.frames = ["ui/outfitbg-hover-selected.png"]
[sprite."ui::outfitterbox"] [sprite."ui::outfitterbox"]
file = "ui/outfitter-box.png" file = "ui/outfitter-box.png"

View File

@ -39,8 +39,6 @@ fn init(state) {
let radar_size = 150.0; let radar_size = 150.0;
let radar_range = 4000.0; let radar_range = 4000.0;
let hide_range = 0.85;
let shrink_distance = 20.0;
sprite::add( sprite::add(
"radar", "radar",
@ -80,9 +78,9 @@ 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() {
ui::go_to_scene("landed"); ui::go_to_scene("landed");
return PlayerDirective::None; return;
} }
return PlayerDirective::None; return;
} }
if type_of(event) == "ScrollEvent" { if type_of(event) == "ScrollEvent" {
@ -108,7 +106,6 @@ fn event(state, event) {
if event.key() == "L" && event.is_down() { if event.key() == "L" && event.is_down() {
return PlayerDirective::Land; return PlayerDirective::Land;
} }
return PlayerDirective::None;
} }
} }
@ -124,6 +121,10 @@ fn step(state) {
// TODO: share variables with init(); // TODO: share variables with init();
let radar_size = 150.0;
let radar_range = 4000.0;
let hide_range = 0.85;
let shrink_distance = 20.0;
let p_pos = state.player_ship().get_pos(); let p_pos = state.player_ship().get_pos();
// Radar arrow // Radar arrow
@ -284,8 +285,8 @@ fn step(state) {
// Window frame // Window frame
{ {
let dx = (((ui::get_camera_zoom() / 2.0) * state.window_aspect()) / radar_range) * (radar_size / 2.0); let dx = (((state.camera_zoom() / 2.0) * state.window_aspect()) / radar_range) * (radar_size / 2.0);
let dy = ((ui::get_camera_zoom() / 2.0) / radar_range) * (radar_size / 2.0); let dy = ((state.camera_zoom() / 2.0) / radar_range) * (radar_size / 2.0);
sprite::set_rect("radar.frame.ne", sprite::set_rect("radar.frame.ne",
Rect( Rect(
(radar_size / 2.0 + 5) - dx, (radar_size / 2.0 + 5) - dx,

View File

@ -1,4 +1,6 @@
fn init(state) { fn init(state) {
let player = state.player_ship();
conf::show_starfield(true); conf::show_starfield(true);
conf::show_phys(false); conf::show_phys(false);
@ -15,8 +17,8 @@ fn init(state) {
sprite::add( sprite::add(
"landscape", "landscape",
{ {
if state.player_ship().is_landed() { if player.is_landed() {
state.player_ship().landed_on().image(); player.landed_on().image();
} else { } else {
""; "";
} }
@ -38,9 +40,6 @@ fn init(state) {
Anchor::Center Anchor::Center
) )
); );
// If this is not set, the button will
// not receive events
sprite::set_disable_events("frame", true);
textbox::add( textbox::add(
@ -55,8 +54,8 @@ fn init(state) {
textbox::align_center("title"); textbox::align_center("title");
textbox::font_serif("title"); textbox::font_serif("title");
textbox::weight_bold("title"); textbox::weight_bold("title");
if state.player_ship().is_landed() { if player.is_landed() {
textbox::set_text("title", state.player_ship().landed_on().display_name()); textbox::set_text("title", player.landed_on().display_name());
} }
textbox::add( textbox::add(
@ -69,8 +68,8 @@ fn init(state) {
Color(1.0, 1.0, 1.0, 1.0) Color(1.0, 1.0, 1.0, 1.0)
); );
textbox::font_sans("desc"); textbox::font_sans("desc");
if state.player_ship().is_landed() { if player.is_landed() {
textbox::set_text("desc", state.player_ship().landed_on().desc()); textbox::set_text("desc", player.landed_on().desc());
} }
} }
@ -84,19 +83,19 @@ fn event(state, event) {
sprite::jump_to("button", "off:top", 0.1); sprite::jump_to("button", "off:top", 0.1);
} }
} }
return PlayerDirective::None; return;
} }
if type_of(event) == "MouseClickEvent" { if type_of(event) == "MouseClickEvent" {
if !event.is_down() { if !event.is_down() {
return PlayerDirective::None; return;
} }
let element = event.element(); let element = event.element();
if element == "button" { if element == "button" {
ui::go_to_scene("outfitter"); ui::go_to_scene("outfitter");
return PlayerDirective::None; return;
} }
return; return;
} }
@ -104,7 +103,7 @@ fn event(state, event) {
if type_of(event) == "KeyboardEvent" { if type_of(event) == "KeyboardEvent" {
if !event.is_down() { if !event.is_down() {
return PlayerDirective::None; return;
} }
if event.key() == "L" { if event.key() == "L" {
@ -113,15 +112,15 @@ fn event(state, event) {
if event.key() == "O" { if event.key() == "O" {
ui::go_to_scene("outfitter"); ui::go_to_scene("outfitter");
return PlayerDirective::None; return;
} }
} }
if type_of(event) == "PlayerShipStateEvent" { if type_of(event) == "PlayerShipStateEvent" {
if !state.player_ship().is_landed() { if !state.player_ship().is_landed() {
ui::go_to_scene("flying"); ui::go_to_scene("flying");
return PlayerDirective::None; return;
} }
return PlayerDirective::None; return;
} }
} }

View File

@ -163,6 +163,7 @@ fn init(state) {
// width should be calculated as a fraction of screen width // width should be calculated as a fraction of screen width
let scrollbox_rect = Rect( let scrollbox_rect = Rect(
222.0, -16.0, 470.0, 480.0, 222.0, -16.0, 470.0, 480.0,
Anchor::NorthWest, Anchor::NorthWest,
@ -171,73 +172,68 @@ fn init(state) {
scrollbox::add("outfit_list", scrollbox_rect); scrollbox::add("outfit_list", scrollbox_rect);
let selected_outfit = false; let p = state.player_ship();
if p.is_landed() {
let s = "";
let x = scrollbox_rect.pos().x() + 45.0;
let y = scrollbox_rect.pos().y() - 45.0;
for xxx in ["1","2","3"] {
for i in p.landed_on().outfitter() {
s = s + i.display_name() + "\n";
{ let thumb_name = "outfit.thumb." + i.index() + xxx;
// p cannot be saved in the global scope. let backg_name = "outfit.backg." + i.index() + xxx;
let p = state.player_ship(); let title_name = "outfit.title." + i.index() + xxx;
if p.is_landed() {
let s = "";
let x = scrollbox_rect.pos().x() + 45.0;
let y = scrollbox_rect.pos().y() - 45.0;
for xxx in ["1","2","3"] {
for i in p.landed_on().outfitter() {
s = s + i.display_name() + "\n";
let thumb_name = "outfit.thumb." + i.index() + xxx; sprite::add(
let backg_name = "outfit.backg." + i.index() + xxx; backg_name,
let title_name = "outfit.title." + i.index() + xxx; "ui::outfitbg",
Rect(
x, y, 90.0, 90.0,
Anchor::Center,
Anchor::NorthWest
)
);
sprite::preserve_aspect(backg_name, true);
scrollbox::add_element("outfit_list", backg_name);
sprite::add( sprite::add(
backg_name, thumb_name,
"ui::outfitbg", i.thumbnail(),
Rect( Rect(
x, y, 90.0, 90.0, x, y, 75.0, 75.0,
Anchor::Center, Anchor::Center,
Anchor::NorthWest Anchor::NorthWest
) )
); );
sprite::preserve_aspect(backg_name, true); sprite::preserve_aspect(thumb_name, true);
scrollbox::add_element("outfit_list", backg_name); scrollbox::add_element("outfit_list", thumb_name);
sprite::add( textbox::add(
thumb_name, title_name,
i.thumbnail(), 10.0, 10.0,
Rect( Rect(
x, y, 75.0, 75.0, x, y - 50.0, 90.0, 10.0,
Anchor::Center, Anchor::Center,
Anchor::NorthWest Anchor::NorthWest,
) ),
); Color(1.0, 1.0, 1.0, 1.0)
sprite::preserve_aspect(thumb_name, true); );
scrollbox::add_element("outfit_list", thumb_name); textbox::font_sans(title_name);
textbox::align_center(title_name);
textbox::set_text(title_name, i.display_name());
scrollbox::add_element("outfit_list", title_name);
textbox::add( x = x + 120.0;
title_name, if x > (
10.0, 10.0, scrollbox_rect.pos().x() + scrollbox_rect.dim().x() - 45.0
Rect( ) {
x, y - 50.0, 90.0, 10.0, x = scrollbox_rect.pos().x() + 45.0;
Anchor::Center, y = y - 120.0;
Anchor::NorthWest,
),
Color(1.0, 1.0, 1.0, 1.0)
);
textbox::font_sans(title_name);
textbox::align_center(title_name);
textbox::set_text(title_name, i.display_name());
scrollbox::add_element("outfit_list", title_name);
x = x + 120.0;
if x > (
scrollbox_rect.pos().x() + scrollbox_rect.dim().x() - 45.0
) {
x = scrollbox_rect.pos().x() + 45.0;
y = y - 120.0;
}
} }
}
textbox::set_text("outfit_stats", s);
} }
}
textbox::set_text("outfit_stats", s);
} }
} }
@ -252,51 +248,28 @@ fn event(state, event) {
sprite::jump_to("exit_button", "off:top", 0.1); sprite::jump_to("exit_button", "off:top", 0.1);
} }
} }
return;
if element.starts_with("outfit.backg.") && element != selected_outfit {
if event.is_enter() {
sprite::jump_to(element, "hover:top", 0.1);
} else {
sprite::jump_to(element, "off:top", 0.1);
}
}
return PlayerDirective::None;
} }
// TODO: this occasionally breaks because of sprite ordering.
// Clicks go to se_box instead!
if type_of(event) == "MouseClickEvent" { if type_of(event) == "MouseClickEvent" {
if !event.is_down() { if !event.is_down() {
return PlayerDirective::None; return;
} }
print(event.element());
let element = event.element(); let element = event.element();
if element == "exit_button" { if element == "exit_button" {
ui::go_to_scene("landed"); ui::go_to_scene("landed");
return PlayerDirective::None; return;
} }
if element.starts_with("outfit.backg.") && element != selected_outfit {
if selected_outfit != false {
sprite::jump_to(selected_outfit, "off:top", 0.1);
}
sprite::jump_to(element, "selected:top", 0.1);
selected_outfit = element;
return PlayerDirective::None;
}
return; return;
} }
if type_of(event) == "PlayerShipStateEvent" { if type_of(event) == "PlayerShipStateEvent" {
if !state.player_ship().is_landed() { if !state.player_ship().is_landed() {
ui::go_to_scene("flying"); ui::go_to_scene("flying");
return;
} }
return PlayerDirective::None; return;
} }
} }

View File

@ -12,7 +12,6 @@ use rhai::ImmutableString;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use smartstring::{LazyCompact, SmartString}; use smartstring::{LazyCompact, SmartString};
use std::{ use std::{
borrow::Borrow,
collections::HashMap, collections::HashMap,
fmt::Display, fmt::Display,
fs::File, fs::File,
@ -131,20 +130,7 @@ pub struct ContentIndex(Arc<SmartString<LazyCompact>>);
impl From<ImmutableString> for ContentIndex { impl From<ImmutableString> for ContentIndex {
fn from(value: ImmutableString) -> Self { fn from(value: ImmutableString) -> Self {
Self(Arc::new(value.into())) Self::new(&value)
}
}
impl From<ContentIndex> for ImmutableString {
fn from(value: ContentIndex) -> Self {
ImmutableString::from(Arc::into_inner(value.0).unwrap())
}
}
impl From<&ContentIndex> for ImmutableString {
fn from(value: &ContentIndex) -> Self {
let x: &SmartString<LazyCompact> = value.0.borrow();
ImmutableString::from(x.clone())
} }
} }

View File

@ -123,26 +123,21 @@ fn try_main() -> Result<()> {
let mut game = game::Game::new(content.clone()); let mut game = game::Game::new(content.clone());
let p = game.make_player(); let p = game.make_player();
let mut input = Arc::new(RenderInput { let player = Arc::new(PlayerAgent::new(&content, p.0));
current_time: game.get_current_time(), let mut phys_img = Arc::new(PhysImage::new());
ct: content.clone(),
phys_img: PhysImage::new(),
player: PlayerAgent::new(&content, p.0),
// TODO: this is a hack for testing.
current_system: content.systems.values().next().unwrap().clone(),
timing: game.get_timing().clone(),
});
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
{
let i = Arc::get_mut(&mut input).unwrap();
i.current_time = game.get_current_time();
i.timing = game.get_timing().clone();
}
match event { match event {
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => { Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
match gpu.render(&input) { match gpu.render(RenderInput {
current_time: game.get_current_time(),
ct: content.clone(),
phys_img: phys_img.clone(),
player: player.clone(),
// TODO: this is a hack for testing.
current_system: content.systems.values().next().unwrap().clone(),
timing: game.get_timing().clone(),
}) {
Ok(_) => {} Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => gpu.resize(&content), Err(wgpu::SurfaceError::Lost) => gpu.resize(&content),
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
@ -152,9 +147,8 @@ fn try_main() -> Result<()> {
} }
Event::MainEventsCleared => { Event::MainEventsCleared => {
let i = Arc::get_mut(&mut input).unwrap(); game.step(&phys_img);
game.step(&i.phys_img); game.update_image(Arc::get_mut(&mut phys_img).unwrap());
game.update_image(&mut i.phys_img);
gpu.window().request_redraw(); gpu.window().request_redraw();
} }
@ -177,20 +171,37 @@ fn try_main() -> Result<()> {
} => { } => {
let directive = gpu let directive = gpu
.process_input( .process_input(
&input, RenderInput {
current_time: game.get_current_time(),
ct: content.clone(),
phys_img: phys_img.clone(),
player: player.clone(),
current_system: content.systems.values().next().unwrap().clone(),
timing: game.get_timing().clone(),
},
InputEvent::Keyboard { InputEvent::Keyboard {
down: state == &ElementState::Pressed, down: state == &ElementState::Pressed,
key: *key, key: *key,
}, },
) )
.unwrap(); .unwrap();
game.apply_directive(directive, &input.player); game.apply_directive(directive, &player);
} }
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
let directive = gpu let directive = gpu
.process_input(&input, InputEvent::MouseMove(position.cast())) .process_input(
RenderInput {
current_time: game.get_current_time(),
ct: content.clone(),
phys_img: phys_img.clone(),
player: player.clone(),
current_system: content.systems.values().next().unwrap().clone(),
timing: game.get_timing().clone(),
},
InputEvent::MouseMove(position.cast()),
)
.unwrap(); .unwrap();
game.apply_directive(directive, &input.player); game.apply_directive(directive, &player);
} }
WindowEvent::MouseInput { state, button, .. } => { WindowEvent::MouseInput { state, button, .. } => {
let down = state == &ElementState::Pressed; let down = state == &ElementState::Pressed;
@ -200,21 +211,45 @@ fn try_main() -> Result<()> {
_ => None, _ => None,
}; };
if let Some(event) = event { if let Some(event) = event {
let directive = gpu.process_input(&input, event).unwrap(); let directive = gpu
game.apply_directive(directive, &input.player); .process_input(
RenderInput {
current_time: game.get_current_time(),
ct: content.clone(),
phys_img: phys_img.clone(),
player: player.clone(),
current_system: content
.systems
.values()
.next()
.unwrap()
.clone(),
timing: game.get_timing().clone(),
},
event,
)
.unwrap();
game.apply_directive(directive, &player);
} }
} }
WindowEvent::MouseWheel { delta, .. } => { WindowEvent::MouseWheel { delta, .. } => {
let directive = gpu let directive = gpu
.process_input( .process_input(
&input, RenderInput {
current_time: game.get_current_time(),
ct: content.clone(),
phys_img: phys_img.clone(),
player: player.clone(),
current_system: content.systems.values().next().unwrap().clone(),
timing: game.get_timing().clone(),
},
InputEvent::Scroll(match delta { InputEvent::Scroll(match delta {
MouseScrollDelta::LineDelta(_h, v) => *v, MouseScrollDelta::LineDelta(_h, v) => *v,
MouseScrollDelta::PixelDelta(v) => v.x as f32, MouseScrollDelta::PixelDelta(v) => v.x as f32,
}), }),
) )
.unwrap(); .unwrap();
game.apply_directive(directive, &input.player); game.apply_directive(directive, &player);
} }
WindowEvent::Resized(_) => { WindowEvent::Resized(_) => {
gpu.resize(&content); gpu.resize(&content);

View File

@ -279,14 +279,17 @@ impl GPUState {
/// Handle user input /// Handle user input
pub fn process_input( pub fn process_input(
&mut self, &mut self,
input: &Arc<RenderInput>, input: RenderInput,
event: InputEvent, event: InputEvent,
) -> Result<PlayerDirective> { ) -> Result<PlayerDirective> {
let input = Arc::new(input);
self.ui.process_input(&mut self.state, input, event) self.ui.process_input(&mut self.state, input, event)
} }
/// Main render function. Draws sprites on a window. /// Main render function. Draws sprites on a window.
pub fn render(&mut self, input: &Arc<RenderInput>) -> Result<(), wgpu::SurfaceError> { pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
let input = Arc::new(input);
if let Some(ship) = input.player.ship { if let Some(ship) = input.player.ship {
let o = input.phys_img.get_ship(&PhysSimShipHandle(ship)); let o = input.phys_img.get_ship(&PhysSimShipHandle(ship));
if let Some(o) = o { if let Some(o) = o {
@ -387,7 +390,7 @@ impl GPUState {
self.push_effects(&input, (clip_ne, clip_sw)); self.push_effects(&input, (clip_ne, clip_sw));
} }
self.ui.draw(&mut self.state, input).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.

View File

@ -9,13 +9,13 @@ use galactica_util::timing::Timing;
#[derive(Debug)] #[derive(Debug)]
pub struct RenderInput { pub struct RenderInput {
/// Player ship data /// Player ship data
pub player: PlayerAgent, pub player: Arc<PlayerAgent>,
/// The system we're currently in /// The system we're currently in
pub current_system: Arc<System>, pub current_system: Arc<System>,
/// The world state to render /// The world state to render
pub phys_img: PhysImage, pub phys_img: Arc<PhysImage>,
// TODO: handle overflow. is it a problem? // TODO: handle overflow. is it a problem?
/// The current time, in seconds /// The current time, in seconds

View File

@ -14,10 +14,11 @@ pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module( .set_into_module(
&mut module, &mut module,
// TODO: fix ugly spaces
move |name: ImmutableString, stroke: f32, color: Color, rect: Rect| { move |name: ImmutableString, stroke: f32, color: Color, rect: Rect| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
if ui_state.contains_name(&name) { if ui_state.elements.contains_key(&name) {
error!("tried to make a radialbar using an existing name `{name}`"); error!("tried to make a radialbar using an existing name `{name}`");
return; return;
} }
@ -37,23 +38,12 @@ pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, val: f32| { .set_into_module(&mut module, move |name: ImmutableString, val: f32| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::RadialBar(x)) => x.set_val(val),
UiElement::RadialBar(x) => x,
_ => {
error!("called `radialbar::set_val` on an invalid name `{name}`");
return;
}
},
Some(UiElement::RadialBar(x)) => x,
_ => { _ => {
error!("called `radialbar::set_val` on an invalid name `{name}`"); error!("called `radialbar_set_val` on an invalid name `{name}`")
return;
} }
}; }
e.set_val(val);
}); });
return module; return module;

View File

@ -15,7 +15,7 @@ pub fn build_scrollbox_module(state_src: Rc<RefCell<UiState>>) -> Module {
.set_into_module(&mut module, move |name: ImmutableString, rect: Rect| { .set_into_module(&mut module, move |name: ImmutableString, rect: Rect| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
if ui_state.contains_name(&name) { if ui_state.elements.contains_key(&name) {
error!("tried to make a scrollbox using an existing name `{name}`"); error!("tried to make a scrollbox using an existing name `{name}`");
return; return;
} }
@ -30,41 +30,44 @@ pub fn build_scrollbox_module(state_src: Rc<RefCell<UiState>>) -> Module {
&mut module, &mut module,
move |name: ImmutableString, target: ImmutableString| { move |name: ImmutableString, target: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
// Make sure `name` is a scrollbox
match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Scrollbox(_)) => {
UiElement::Scrollbox(_) => {} match ui_state.get_mut_by_name(&target) {
_ => { Some(UiElement::Text(_)) | Some(UiElement::Sprite(_)) => {
error!("called `scrollbox::add_element` on an invalid name `{name}`"); let e = match ui_state.remove_element_incomplete(&target) {
return; Some(UiElement::Sprite(s)) => {
Rc::new(RefCell::new(UiElement::Sprite(s)))
}
Some(UiElement::Text(t)) => {
Rc::new(RefCell::new(UiElement::Text(t)))
}
_ => unreachable!(),
};
// Add a subelement pointing to this sprite
ui_state.add_element(UiElement::SubElement {
parent: name.clone(),
element: e.clone(),
});
// Add this sprite to a scrollbox
match ui_state.get_mut_by_name(&name) {
Some(UiElement::Scrollbox(s)) => {
s.add_element(e);
}
_ => unreachable!(),
};
}
Some(_) => {
error!("cannot add `{name}` to scrollbox `{name}`, invalid type.")
}
None => {
error!("called `scrollbox::add_element` with a non-existing target `{target}`")
}
} }
}, }
Some(UiElement::Scrollbox(_)) => {}
_ => { _ => {
error!("called `scrollbox::add_element` on an invalid name `{name}`"); error!("called `scrollbox::add_element` on an invalid name `{name}`")
return;
}
}
// Replace the target with a SubElement, without changing its position in the array.
match ui_state.get_mut_by_name(&target) {
Some(UiElement::Text(_)) | Some(UiElement::Sprite(_)) => {
let e = ui_state.get_mut_by_name(&target).unwrap();
let new = (*e).clone();
*e = UiElement::SubElement {
parent: name.clone(),
element: Box::new(new),
};
}
Some(_) => {
error!("cannot add `{name}` to scrollbox `{name}`, invalid type.")
}
None => {
error!(
"called `scrollbox::add_element` with a non-existing target `{target}`"
)
} }
} }
}, },

View File

@ -26,7 +26,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
return; return;
} }
if ui_state.contains_name(&name) { if ui_state.elements.contains_key(&name) {
error!("tried to make a sprite using an existing name `{name}`"); error!("tried to make a sprite using an existing name `{name}`");
return; return;
} }
@ -55,7 +55,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
if ui_state.contains_name(&name) { if ui_state.elements.contains_key(&name) {
ui_state.remove_element(&name); ui_state.remove_element(&name);
} else { } else {
error!("called `sprite::remove` on an invalid name `{name}`") error!("called `sprite::remove` on an invalid name `{name}`")
@ -96,36 +96,29 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
move |name: ImmutableString, edge_name: ImmutableString, duration: f32| { move |name: ImmutableString, edge_name: ImmutableString, duration: f32| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Sprite(x)) => {
UiElement::Sprite(x) => x, let sprite = x.anim.get_sprite();
_ => {
error!("called `sprite::jump_to` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Sprite(x)) => x, let edge =
resolve_edge_as_edge(&sprite.sections, edge_name.as_str(), duration);
let edge = match edge {
Err(_) => {
error!(
"called `sprite::jump_to` on an invalid edge `{}` on sprite `{}`",
edge_name, sprite.index
);
return;
}
Ok(s) => s,
};
x.anim.jump_to(&edge);
}
_ => { _ => {
error!("called `sprite::jump_to` on an invalid name `{name}`"); error!("called `sprite::jump_to` on an invalid name `{name}`")
return;
} }
}; }
let sprite = e.anim.get_sprite();
let edge = resolve_edge_as_edge(&sprite.sections, edge_name.as_str(), duration);
let edge = match edge {
Err(_) => {
error!(
"called `sprite::jump_to` on an invalid edge `{}` on sprite `{}`",
edge_name, sprite.index
);
return;
}
Ok(s) => s,
};
e.anim.jump_to(&edge);
}, },
); );
@ -134,22 +127,12 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, x: f32| { .set_into_module(&mut module, move |name: ImmutableString, x: f32| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Sprite(s)) => s.set_angle(x),
UiElement::Sprite(x) => x,
_ => {
error!("called `sprite::set_angle` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Sprite(x)) => x,
_ => { _ => {
error!("called `sprite::set_angle` on an invalid name `{name}`"); error!("called `sprite::set_angle` on an invalid name `{name}`")
return;
} }
}; }
e.set_angle(x)
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -157,22 +140,12 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, x: Rect| { .set_into_module(&mut module, move |name: ImmutableString, x: Rect| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Sprite(s)) => s.set_rect(x),
UiElement::Sprite(x) => x,
_ => {
error!("called `sprite::set_rect` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Sprite(x)) => x,
_ => { _ => {
error!("called `sprite::set_rect` on an invalid name `{name}`"); error!("called `sprite::set_rect` on an invalid name `{name}`")
return;
} }
}; }
e.set_rect(x);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -180,22 +153,12 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, x: Color| { .set_into_module(&mut module, move |name: ImmutableString, x: Color| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Sprite(s)) => s.set_color(x),
UiElement::Sprite(x) => x,
_ => {
error!("called `sprite::set_color` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Sprite(x)) => x,
_ => { _ => {
error!("called `sprite::set_color` on an invalid name `{name}`"); error!("called `sprite::set_color` on an invalid name `{name}`")
return;
} }
}; }
e.set_color(x);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -203,46 +166,12 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, x: bool| { .set_into_module(&mut module, move |name: ImmutableString, x: bool| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Sprite(s)) => s.set_preserve_aspect(x),
UiElement::Sprite(x) => x,
_ => {
error!("called `sprite::preserve_aspect` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Sprite(x)) => x,
_ => { _ => {
error!("called `sprite::preserve_aspect` on an invalid name `{name}`"); error!("called `sprite::set_preserve_aspect` on an invalid name `{name}`")
return;
} }
}; }
e.set_preserve_aspect(x);
}); });
let state = state_src.clone();
let _ = FuncRegistration::new("set_disable_events")
.with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, x: bool| {
let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element {
UiElement::Sprite(x) => x,
_ => {
error!("called `sprite::set_disable_events` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Sprite(x)) => x,
_ => {
error!("called `sprite::set_disable_events` on an invalid name `{name}`");
return;
}
};
e.set_disable_events(x);
});
return module; return module;
} }

View File

@ -27,7 +27,7 @@ pub fn build_textbox_module(
color: Color| { color: Color| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
if ui_state.contains_name(&name) { if ui_state.elements.contains_key(&name) {
error!("tried to make a textbox using an existing name `{name}`"); error!("tried to make a textbox using an existing name `{name}`");
return; return;
} }
@ -51,24 +51,12 @@ pub fn build_textbox_module(
&mut module, &mut module,
move |name: ImmutableString, text: ImmutableString| { move |name: ImmutableString, text: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
match ui_state.get_mut_by_name(&name) {
let e = match ui_state.get_mut_by_name(&name) { Some(UiElement::Text(x)) => x.set_text(&mut font.borrow_mut(), text.as_str()),
Some(UiElement::SubElement { element, .. }) => match &mut **element {
UiElement::Text(x) => x,
_ => {
error!("called `textbox::set_text` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::set_text` on an invalid name `{name}`"); error!("called `textbox::set_text` on an invalid name `{name}`")
return;
} }
}; }
e.set_text(&mut font.borrow_mut(), text.as_str());
}, },
); );
@ -78,24 +66,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
match ui_state.get_mut_by_name(&name) {
let e = match ui_state.get_mut_by_name(&name) { Some(UiElement::Text(x)) => x.set_align(&mut font.borrow_mut(), Align::Left),
Some(UiElement::SubElement { element, .. }) => match &mut **element {
UiElement::Text(x) => x,
_ => {
error!("called `textbox::align_left` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::align_left` on an invalid name `{name}`"); error!("called `textbox::align_left` on an invalid name `{name}`")
return;
} }
}; }
e.set_align(&mut font.borrow_mut(), Align::Left);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -104,23 +80,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_align(&mut font.borrow_mut(), Align::Right),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::align_right` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::align_right` on an invalid name `{name}`"); error!("called `textbox::align_right` on an invalid name `{name}`")
return;
} }
}; }
e.set_align(&mut font.borrow_mut(), Align::Right);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -129,23 +94,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_align(&mut font.borrow_mut(), Align::Justified),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::align_justify` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::align_justify` on an invalid name `{name}`"); error!("called `textbox::align_justify` on an invalid name `{name}`")
return;
} }
}; }
e.set_align(&mut font.borrow_mut(), Align::Justified);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -154,23 +108,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_align(&mut font.borrow_mut(), Align::Center),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::align_center` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::align_center` on an invalid name `{name}`"); error!("called `textbox::align_center` on an invalid name `{name}`")
return;
} }
}; }
e.set_align(&mut font.borrow_mut(), Align::Center);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -179,23 +122,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_weight(&mut font.borrow_mut(), Weight::BOLD),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::weight_bold` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::weight_bold` on an invalid name `{name}`"); error!("called `textbox::weight_bold` on an invalid name `{name}`")
return;
} }
}; }
e.set_weight(&mut font.borrow_mut(), Weight::BOLD);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -204,23 +136,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_weight(&mut font.borrow_mut(), Weight::NORMAL),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::weight_normal` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::weight_normal` on an invalid name `{name}`"); error!("called `textbox::weight_normal` on an invalid name `{name}`")
return;
} }
}; }
e.set_weight(&mut font.borrow_mut(), Weight::NORMAL);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -229,23 +150,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_font(&mut font.borrow_mut(), FamilyOwned::Serif),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::font_serif` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::font_serif` on an invalid name `{name}`"); error!("called `textbox::font_serif` on an invalid name `{name}`")
return;
} }
}; }
e.set_font(&mut font.borrow_mut(), FamilyOwned::Serif);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -254,23 +164,14 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => {
UiElement::Text(x) => x, x.set_font(&mut font.borrow_mut(), FamilyOwned::SansSerif)
_ => {
error!("called `textbox::font_sans` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => {
error!("called `textbox::font_sans` on an invalid name `{name}`");
return;
} }
}; _ => {
error!("called `textbox::font_sans` on an invalid name `{name}`")
e.set_font(&mut font.borrow_mut(), FamilyOwned::SansSerif); }
}
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -279,23 +180,14 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => {
UiElement::Text(x) => x, x.set_font(&mut font.borrow_mut(), FamilyOwned::Monospace)
_ => {
error!("called `textbox::font_mono` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => {
error!("called `textbox::font_mono` on an invalid name `{name}`");
return;
} }
}; _ => {
error!("called `textbox::font_mono` on an invalid name `{name}`")
e.set_font(&mut font.borrow_mut(), FamilyOwned::Monospace); }
}
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -304,23 +196,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_style(&mut font.borrow_mut(), Style::Normal),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::style_normal` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::style_normal` on an invalid name `{name}`"); error!("called `textbox::style_normal` on an invalid name `{name}`")
return;
} }
}; }
e.set_style(&mut font.borrow_mut(), Style::Normal);
}); });
let state = state_src.clone(); let state = state_src.clone();
@ -329,23 +210,12 @@ pub fn build_textbox_module(
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString| { .set_into_module(&mut module, move |name: ImmutableString| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::Text(x)) => x.set_style(&mut font.borrow_mut(), Style::Italic),
UiElement::Text(x) => x,
_ => {
error!("called `textbox::style_italic` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => { _ => {
error!("called `textbox::style_italic` on an invalid name `{name}`"); error!("called `textbox::style_italic` on an invalid name `{name}`")
return;
} }
}; }
e.set_style(&mut font.borrow_mut(), Style::Italic);
}); });
return module; return module;

View File

@ -38,7 +38,7 @@ pub fn register_into_engine(
.build_type::<PlayerShipStateEvent>() .build_type::<PlayerShipStateEvent>()
.build_type::<KeyboardEvent>() .build_type::<KeyboardEvent>()
.build_type::<ScrollEvent>() .build_type::<ScrollEvent>()
// Enums // Bigger modules
.register_type_with_name::<Anchor>("Anchor") .register_type_with_name::<Anchor>("Anchor")
.register_static_module("Anchor", exported_module!(anchor_mod).into()) .register_static_module("Anchor", exported_module!(anchor_mod).into())
.register_type_with_name::<PlayerDirective>("PlayerDirective") .register_type_with_name::<PlayerDirective>("PlayerDirective")

View File

@ -241,9 +241,9 @@ unsafe impl Send for State {}
unsafe impl Sync for State {} unsafe impl Sync for State {}
impl State { impl State {
pub fn new(state: &RenderState, input: &Arc<RenderInput>) -> Self { pub fn new(state: &RenderState, input: Arc<RenderInput>) -> Self {
Self { Self {
input: input.clone(), input,
window_aspect: state.window_aspect, window_aspect: state.window_aspect,
} }
} }

View File

@ -1,14 +1,17 @@
use nalgebra::Vector2; use nalgebra::Vector2;
use rhai::{Dynamic, ImmutableString}; use rhai::{Dynamic, ImmutableString};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use winit::window::Window;
use super::super::api::Rect; use super::{super::api::Rect, OwnedTextArea};
use crate::{InputEvent, RenderInput, RenderState}; use crate::{ui::UiElement, InputEvent, RenderInput, RenderState};
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct UiScrollbox { pub struct UiScrollbox {
pub name: ImmutableString, pub name: ImmutableString,
pub rect: Rect, pub rect: Rect,
pub offset: Vector2<f32>, pub offset: Vector2<f32>,
pub elements: HashMap<ImmutableString, Rc<RefCell<UiElement>>>,
has_mouse: bool, has_mouse: bool,
} }
@ -18,13 +21,31 @@ impl UiScrollbox {
Self { Self {
name, name,
rect, rect,
elements: HashMap::new(),
offset: Vector2::new(0.0, 0.0), offset: Vector2::new(0.0, 0.0),
has_mouse: false, has_mouse: false,
} }
} }
pub fn step(&mut self, _t: f32) { pub fn add_element(&mut self, e: Rc<RefCell<UiElement>>) {
// TODO: inertia let name = e.borrow().get_name().clone();
self.elements.insert(name, e);
}
pub fn remove_element(&mut self, sprite: &ImmutableString) {
self.elements.remove(sprite);
}
pub fn step(&mut self, t: f32) {
for (_name, e) in &self.elements {
match &mut *e.clone().borrow_mut() {
UiElement::Sprite(sprite) => sprite.step(t),
UiElement::RadialBar(_) => {}
UiElement::Text(..) => {}
UiElement::Scrollbox(..) => {}
UiElement::SubElement { .. } => {}
}
}
} }
pub fn handle_event( pub fn handle_event(
@ -33,21 +54,31 @@ impl UiScrollbox {
state: &mut RenderState, state: &mut RenderState,
event: &InputEvent, event: &InputEvent,
) -> Option<Dynamic> { ) -> Option<Dynamic> {
self.handle_event_with_offset(input, state, event, Vector2::new(0.0, 0.0)) let r = self
}
pub fn handle_event_with_offset(
&mut self,
input: &RenderInput,
state: &mut RenderState,
event: &InputEvent,
offset: Vector2<f32>,
) -> Option<Dynamic> {
let mut r = self
.rect .rect
.to_centered(&state.window, input.ct.config.ui_scale); .to_centered(&state.window, input.ct.config.ui_scale);
r.pos += offset;
// TODO: handle only if used in event()
// i.e, scrollable sprites shouldn't break scrollboxes
// First, check if this event is captured by any sub-elements
for (_, e) in &mut self.elements {
let arg = match &mut *e.borrow_mut() {
UiElement::Sprite(sprite) => sprite.handle_event(&input, state, &event),
UiElement::Scrollbox(sbox) => sbox.handle_event(&input, state, &event),
UiElement::RadialBar(_) | UiElement::Text(..) => None,
// Subelements are intentionally skipped,
// they should be handled by their parent's `handle_event` method.
UiElement::SubElement { .. } => None,
};
if arg.is_some() {
return arg;
}
}
// If no inner events were captured, handle self events.
match event { match event {
InputEvent::MouseMove(pos) => { InputEvent::MouseMove(pos) => {
if r.contains_mouse(state, pos) && !self.has_mouse { if r.contains_mouse(state, pos) && !self.has_mouse {
@ -71,3 +102,35 @@ impl UiScrollbox {
return None; return None;
} }
} }
impl UiScrollbox {
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
for (_name, e) in &self.elements {
match &*e.clone().borrow() {
UiElement::Sprite(sprite) => {
sprite.push_to_buffer_with_offset(input, state, self.offset)
}
UiElement::RadialBar(..) => {}
UiElement::Text(..) => {}
UiElement::Scrollbox(..) => {}
UiElement::SubElement { .. } => {}
}
}
}
}
// TODO: don't allocate here
impl<'a> UiScrollbox {
pub fn get_textareas(&'a self, input: &RenderInput, window: &Window) -> Vec<OwnedTextArea> {
let mut v = Vec::with_capacity(32);
for e in self.elements.values() {
match &*e.clone().borrow() {
UiElement::Text(x) => {
v.push(x.get_textarea_with_offset(input, window, self.offset))
}
_ => {}
}
}
return v;
}
}

View File

@ -19,9 +19,6 @@ pub struct UiSprite {
/// Sprite angle, in degrees /// Sprite angle, in degrees
angle: f32, angle: f32,
/// If true, this sprite ignores all events
disable_events: bool,
/// If true, this sprite will be scaled to fit in its box without affecting aspect ratio. /// If true, this sprite will be scaled to fit in its box without affecting aspect ratio.
/// If false, this sprite will be stretched to fit in its box /// If false, this sprite will be stretched to fit in its box
preserve_aspect: bool, preserve_aspect: bool,
@ -46,7 +43,6 @@ impl UiSprite {
has_mouse: false, has_mouse: false,
has_click: false, has_click: false,
preserve_aspect: false, preserve_aspect: false,
disable_events: false,
} }
} }
@ -66,10 +62,6 @@ impl UiSprite {
self.color = color; self.color = color;
} }
pub fn set_disable_events(&mut self, disable_events: bool) {
self.disable_events = disable_events;
}
pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) { pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) {
self.preserve_aspect = preserve_aspect; self.preserve_aspect = preserve_aspect;
} }
@ -132,24 +124,14 @@ impl UiSprite {
state: &mut RenderState, state: &mut RenderState,
event: &InputEvent, event: &InputEvent,
) -> Option<Dynamic> { ) -> Option<Dynamic> {
self.handle_event_with_offset(input, state, event, Vector2::new(0.0, 0.0)) let r = self
}
pub fn handle_event_with_offset(
&mut self,
input: &RenderInput,
state: &mut RenderState,
event: &InputEvent,
offset: Vector2<f32>,
) -> Option<Dynamic> {
if self.disable_events {
return None;
}
let mut r = self
.rect .rect
.to_centered(&state.window, input.ct.config.ui_scale); .to_centered(&state.window, input.ct.config.ui_scale);
r.pos += offset;
// Release mouse when cursor leaves box
if self.has_click && !self.has_mouse {
self.has_click = false;
}
match event { match event {
InputEvent::MouseMove(pos) => { InputEvent::MouseMove(pos) => {
@ -163,7 +145,6 @@ impl UiSprite {
if !r.contains_mouse(state, pos) && self.has_mouse { if !r.contains_mouse(state, pos) && self.has_mouse {
self.has_mouse = false; self.has_mouse = false;
self.has_click = false;
return Some(Dynamic::from(MouseHoverEvent { return Some(Dynamic::from(MouseHoverEvent {
enter: false, enter: false,
element: self.name.clone(), element: self.name.clone(),

View File

@ -10,7 +10,7 @@ use winit::window::Window;
use super::{super::api::Rect, OwnedTextArea}; use super::{super::api::Rect, OwnedTextArea};
use crate::{ui::api, RenderInput}; use crate::{ui::api, RenderInput};
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct UiTextBox { pub struct UiTextBox {
pub name: ImmutableString, pub name: ImmutableString,

View File

@ -3,7 +3,7 @@ use galactica_content::Content;
use galactica_system::{phys::PhysSimShipHandle, PlayerDirective}; use galactica_system::{phys::PhysSimShipHandle, PlayerDirective};
use galactica_util::rhai_error_to_anyhow; use galactica_util::rhai_error_to_anyhow;
use log::{debug, error}; use log::{debug, error};
use rhai::{CallFnOptions, Dynamic, Engine, ImmutableString, Scope}; use rhai::{Dynamic, Engine, ImmutableString, Scope};
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc}; use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
@ -58,119 +58,87 @@ impl UiScriptExecutor {
pub fn process_input( pub fn process_input(
&mut self, &mut self,
state: &mut RenderState, state: &mut RenderState,
input: &Arc<RenderInput>, input: Arc<RenderInput>,
event: InputEvent, event: InputEvent,
) -> Result<PlayerDirective> { ) -> Result<PlayerDirective> {
let current_scene = (*self.state).borrow().get_scene().clone(); let current_scene = (*self.state).borrow().get_scene().clone();
if current_scene.is_none() { if current_scene.is_none() {
return Ok(PlayerDirective::None); return Ok(PlayerDirective::None);
} }
let mut arg: Option<Dynamic> = None;
// First, check if this event is captured by any ui elements. // First, check if this event is captured by any ui elements.
let len = (*self.state).borrow().len(); for (_, e) in &mut self.state.borrow_mut().elements {
// Iterate front to back arg = match e {
// (this also ensures that sub-elements are handled BEFORE their container.)
for i in (0..len).rev() {
let mut ui_state = self.state.borrow_mut();
let arg = match ui_state.get_mut_by_idx(i).unwrap() {
UiElement::Sprite(sprite) => sprite.handle_event(&input, state, &event), UiElement::Sprite(sprite) => sprite.handle_event(&input, state, &event),
UiElement::Scrollbox(sbox) => sbox.handle_event(&input, state, &event), UiElement::Scrollbox(sbox) => sbox.handle_event(&input, state, &event),
UiElement::RadialBar(_) | UiElement::Text(..) => None, UiElement::RadialBar(_) | UiElement::Text(..) => None,
UiElement::SubElement { parent, element } => {
// Be very careful here, to avoid // Subelements are intentionally skipped,
// borrowing mutably twice... // they should be handled by their parent's `handle_event` method.
let e_name = element.get_name(); UiElement::SubElement { .. } => None,
let p_name = parent.clone();
let parent = ui_state.get_by_name(&p_name).unwrap();
match parent {
UiElement::Scrollbox(sbox) => {
let offset = sbox.offset;
let element = ui_state.get_mut_by_name(&e_name).unwrap();
match element {
UiElement::SubElement { element, .. } => match &mut **element {
UiElement::Sprite(sprite) => sprite
.handle_event_with_offset(&input, state, &event, offset),
UiElement::Scrollbox(sbox) => {
sbox.handle_event_with_offset(&input, state, &event, offset)
}
UiElement::RadialBar(_) | UiElement::Text(..) => None,
UiElement::SubElement { .. } => unreachable!(),
},
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
}; };
drop(ui_state);
// Return if event hook returns any PlayerDirective (including None), if arg.is_some() {
// continue to the next element if the hook returns (). break;
if let Some(arg) = arg {
let result = self.run_event_callback(state, &input, arg)?;
if let Some(result) = result {
return Ok(result);
}
} }
} }
// If nothing was caught, check global events // If nothing was caught, check global events
let arg = match event { if arg.is_none() {
InputEvent::Scroll(val) => Some(Dynamic::from(ScrollEvent { val })), arg = match event {
InputEvent::Keyboard { down, key } => { InputEvent::Scroll(val) => Some(Dynamic::from(ScrollEvent { val })),
let str = match key { InputEvent::Keyboard { down, key } => {
VirtualKeyCode::A => Some("A"), let str = match key {
VirtualKeyCode::B => Some("B"), VirtualKeyCode::A => Some("A"),
VirtualKeyCode::C => Some("C"), VirtualKeyCode::B => Some("B"),
VirtualKeyCode::D => Some("D"), VirtualKeyCode::C => Some("C"),
VirtualKeyCode::E => Some("E"), VirtualKeyCode::D => Some("D"),
VirtualKeyCode::F => Some("F"), VirtualKeyCode::E => Some("E"),
VirtualKeyCode::G => Some("G"), VirtualKeyCode::F => Some("F"),
VirtualKeyCode::H => Some("H"), VirtualKeyCode::G => Some("G"),
VirtualKeyCode::I => Some("I"), VirtualKeyCode::H => Some("H"),
VirtualKeyCode::J => Some("J"), VirtualKeyCode::I => Some("I"),
VirtualKeyCode::K => Some("K"), VirtualKeyCode::J => Some("J"),
VirtualKeyCode::L => Some("L"), VirtualKeyCode::K => Some("K"),
VirtualKeyCode::M => Some("M"), VirtualKeyCode::L => Some("L"),
VirtualKeyCode::N => Some("N"), VirtualKeyCode::M => Some("M"),
VirtualKeyCode::O => Some("O"), VirtualKeyCode::N => Some("N"),
VirtualKeyCode::P => Some("P"), VirtualKeyCode::O => Some("O"),
VirtualKeyCode::Q => Some("Q"), VirtualKeyCode::P => Some("P"),
VirtualKeyCode::R => Some("R"), VirtualKeyCode::Q => Some("Q"),
VirtualKeyCode::S => Some("S"), VirtualKeyCode::R => Some("R"),
VirtualKeyCode::T => Some("T"), VirtualKeyCode::S => Some("S"),
VirtualKeyCode::U => Some("U"), VirtualKeyCode::T => Some("T"),
VirtualKeyCode::V => Some("V"), VirtualKeyCode::U => Some("U"),
VirtualKeyCode::W => Some("W"), VirtualKeyCode::V => Some("V"),
VirtualKeyCode::X => Some("X"), VirtualKeyCode::W => Some("W"),
VirtualKeyCode::Y => Some("Y"), VirtualKeyCode::X => Some("X"),
VirtualKeyCode::Z => Some("Z"), VirtualKeyCode::Y => Some("Y"),
VirtualKeyCode::Up => Some("up"), VirtualKeyCode::Z => Some("Z"),
VirtualKeyCode::Down => Some("down"), VirtualKeyCode::Up => Some("up"),
VirtualKeyCode::Left => Some("left"), VirtualKeyCode::Down => Some("down"),
VirtualKeyCode::Right => Some("right"), VirtualKeyCode::Left => Some("left"),
VirtualKeyCode::Space => Some("space"), VirtualKeyCode::Right => Some("right"),
_ => None, VirtualKeyCode::Space => Some("space"),
}; _ => None,
if let Some(str) = str { };
Some(Dynamic::from(KeyboardEvent { if let Some(str) = str {
down, Some(Dynamic::from(KeyboardEvent {
key: ImmutableString::from(str), down,
})) key: ImmutableString::from(str),
} else { }))
None } else {
None
}
} }
} _ => None,
_ => None, };
}; }
if let Some(arg) = arg { if let Some(arg) = arg {
Ok(self self.run_event_callback(state, input, arg)
.run_event_callback(state, &input, arg)?
.unwrap_or(PlayerDirective::None))
} else { } else {
return Ok(PlayerDirective::None); return Ok(PlayerDirective::None);
} }
@ -179,42 +147,39 @@ impl UiScriptExecutor {
fn run_event_callback( fn run_event_callback(
&mut self, &mut self,
state: &mut RenderState, state: &mut RenderState,
input: &Arc<RenderInput>, input: Arc<RenderInput>,
arg: Dynamic, arg: Dynamic,
) -> Result<Option<PlayerDirective>> { ) -> Result<PlayerDirective> {
let current_scene = (*self.state).borrow().get_scene().clone(); let current_scene = (*self.state).borrow().get_scene().clone();
if current_scene.is_none() { if current_scene.is_none() {
return Ok(None); return Ok(PlayerDirective::None);
} }
let current_scene = current_scene.unwrap(); let current_scene = current_scene.unwrap();
let ct = (*self.state).borrow().ct.clone(); let ct = (*self.state).borrow().ct.clone();
let ast = ct.config.ui_scenes.get(current_scene.as_str()).unwrap();
let d: Dynamic = rhai_error_to_anyhow::<Dynamic, _>(self.engine.call_fn_with_options( let d: Dynamic = rhai_error_to_anyhow(self.engine.call_fn(
CallFnOptions::new().rewind_scope(true),
&mut self.scope, &mut self.scope,
ast, ct.config.ui_scenes.get(current_scene.as_str()).unwrap(),
"event", "event",
(State::new(state, input), arg.clone()), (State::new(state, input.clone()), arg.clone()),
)) ))
.with_context(|| format!("while calling `event()`")) .with_context(|| format!("while handling event `{:?}`", arg))
.with_context(|| format!("in UI scene `{}`", current_scene.as_str()))?; .with_context(|| format!("in ui scene `{}`", current_scene))?;
if d.is::<PlayerDirective>() { if d.is::<PlayerDirective>() {
return Ok(Some(d.cast::<PlayerDirective>())); return Ok(d.cast());
} else if !(d.is_unit()) { } else if !(d.is_unit()) {
error!( error!(
"`event()` in UI scene `{}` returned invalid type `{}`", "`event()` in UI scene `{current_scene}` returned invalid type `{}`",
d, d
current_scene.as_str()
) )
} }
return Ok(None); return Ok(PlayerDirective::None);
} }
/// Change the current scene /// Change the current scene
pub fn set_scene(&mut self, state: &RenderState, input: &Arc<RenderInput>) -> Result<()> { pub fn set_scene(&mut self, state: &RenderState, input: Arc<RenderInput>) -> Result<()> {
let current_scene = (*self.state).borrow().get_scene().clone(); let current_scene = (*self.state).borrow().get_scene().clone();
if self.last_scene == current_scene { if self.last_scene == current_scene {
return Ok(()); return Ok(());
@ -230,44 +195,33 @@ impl UiScriptExecutor {
current_scene.as_ref().unwrap() current_scene.as_ref().unwrap()
); );
let ct = (*self.state).borrow().ct.clone();
let ast = ct
.config
.ui_scenes
.get(current_scene.as_ref().unwrap().as_str())
.unwrap();
// Clear previous state
self.scope.clear(); self.scope.clear();
self.state.borrow_mut().clear();
rhai_error_to_anyhow(self.engine.call_fn_with_options( // Drop this right away, since all script calls borrow elm mutably.
CallFnOptions::new().rewind_scope(false), let mut elm = self.state.borrow_mut();
&mut self.scope, elm.clear();
ast, drop(elm);
"init",
(State::new(state, input),), let ct = (*self.state).borrow().ct.clone();
)) rhai_error_to_anyhow(
self.engine.call_fn(
&mut self.scope,
ct.config
.ui_scenes
.get(current_scene.as_ref().unwrap().as_str())
.unwrap(),
"init",
(State::new(state, input.clone()),),
),
)
.with_context(|| format!("while running `init()`")) .with_context(|| format!("while running `init()`"))
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?; .with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
// Make sure input isn't borrowed (which would happen if the UI script adds a state class to the scope)
// At this point, the only reference to input should be the one in main.rs.
// Note how we always pass input as &Arc, not Arc.
if Arc::strong_count(&input) != 1 {
error!(
"State has been captured in the scope of UI scene `{}`",
current_scene.as_ref().unwrap()
);
error!("Clearing scope to prevent a panic, this may break the UI script.");
self.scope.clear();
};
return Ok(()); return Ok(());
} }
/// Draw all ui elements /// Draw all ui elements
pub fn draw(&mut self, state: &mut RenderState, input: &Arc<RenderInput>) -> Result<()> { pub fn draw(&mut self, state: &mut RenderState, input: Arc<RenderInput>) -> Result<()> {
let ct = (*self.state).borrow().ct.clone(); let ct = (*self.state).borrow().ct.clone();
// Initialize start scene if we haven't yet // Initialize start scene if we haven't yet
@ -276,10 +230,10 @@ impl UiScriptExecutor {
.borrow_mut() .borrow_mut()
.set_scene(ImmutableString::from(&ct.config.start_ui_scene)); .set_scene(ImmutableString::from(&ct.config.start_ui_scene));
} }
self.set_scene(state, input)?; self.set_scene(state, input.clone())?;
let current_scene = (*self.state).borrow().get_scene().clone(); let current_scene = (*self.state).borrow().get_scene().clone();
(*self.state).borrow_mut().step(state, input); (*self.state).borrow_mut().step(state, input.clone());
// Run step() (if it is defined) // Run step() (if it is defined)
let ast = ct let ast = ct
@ -288,12 +242,11 @@ impl UiScriptExecutor {
.get(current_scene.as_ref().unwrap().as_str()) .get(current_scene.as_ref().unwrap().as_str())
.unwrap(); .unwrap();
if ast.iter_functions().any(|x| x.name == "step") { if ast.iter_functions().any(|x| x.name == "step") {
rhai_error_to_anyhow(self.engine.call_fn_with_options( rhai_error_to_anyhow(self.engine.call_fn(
CallFnOptions::new().rewind_scope(true),
&mut self.scope, &mut self.scope,
ast, ast,
"step", "step",
(State::new(state, input),), (State::new(state, input.clone()),),
)) ))
.with_context(|| format!("while calling `step()`")) .with_context(|| format!("while calling `step()`"))
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?; .with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
@ -318,12 +271,12 @@ impl UiScriptExecutor {
true true
} }
} { } {
self.run_event_callback(state, &input, Dynamic::from(PlayerShipStateEvent {}))?; self.run_event_callback(state, input.clone(), Dynamic::from(PlayerShipStateEvent {}))?;
} }
let len = (*self.state).borrow().len(); let len = (*self.state).borrow().len();
for i in 0..len { for i in 0..len {
match self.state.borrow().get_by_idx(i).unwrap() { match (*self.state).borrow_mut().get_mut_by_idx(i).unwrap() {
UiElement::Sprite(sprite) => { UiElement::Sprite(sprite) => {
sprite.push_to_buffer(&input, state); sprite.push_to_buffer(&input, state);
} }
@ -332,27 +285,15 @@ impl UiScriptExecutor {
x.push_to_buffer(&input, state); x.push_to_buffer(&input, state);
} }
UiElement::Scrollbox(_) => {} UiElement::Scrollbox(x) => {
UiElement::Text(..) => {} x.push_to_buffer(&input, state);
UiElement::SubElement { element, parent } => {
match self.state.borrow().get_by_name(parent).unwrap() {
UiElement::Scrollbox(sbox) => match &**element {
UiElement::Sprite(sprite) => {
sprite.push_to_buffer_with_offset(input, state, sbox.offset)
}
UiElement::RadialBar(..) => {}
UiElement::Text(..) => {}
UiElement::Scrollbox(..) => {}
UiElement::SubElement { .. } => {}
},
_ => {}
}
} }
UiElement::SubElement { .. } | UiElement::Text(..) => {}
} }
} }
//self.scope.rewind(0); self.scope.rewind(0);
return Ok(()); return Ok(());
} }
} }

View File

@ -1,7 +1,7 @@
use galactica_content::Content; use galactica_content::Content;
use log::{debug, error}; use log::{debug, error};
use rhai::ImmutableString; use rhai::ImmutableString;
use std::{collections::HashMap, sync::Arc, time::Instant}; use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, time::Instant};
use winit::window::Window; use winit::window::Window;
use super::{ use super::{
@ -10,7 +10,7 @@ use super::{
}; };
use crate::{RenderInput, RenderState}; use crate::{RenderInput, RenderState};
#[derive(Debug, Clone)] #[derive(Debug)]
pub enum UiElement { pub enum UiElement {
Sprite(UiSprite), Sprite(UiSprite),
RadialBar(UiRadialBar), RadialBar(UiRadialBar),
@ -20,7 +20,7 @@ pub enum UiElement {
/// This is a sub-element managed by another element /// This is a sub-element managed by another element
SubElement { SubElement {
parent: ImmutableString, parent: ImmutableString,
element: Box<UiElement>, element: Rc<RefCell<UiElement>>,
}, },
} }
@ -31,7 +31,7 @@ impl UiElement {
Self::RadialBar(x) => x.name.clone(), Self::RadialBar(x) => x.name.clone(),
Self::Text(x) => x.name.clone(), Self::Text(x) => x.name.clone(),
Self::Scrollbox(x) => x.name.clone(), Self::Scrollbox(x) => x.name.clone(),
Self::SubElement { element, .. } => element.get_name(), Self::SubElement { element, .. } => element.borrow().get_name(),
} }
} }
} }
@ -43,11 +43,8 @@ pub(crate) struct UiConfig {
} }
pub(crate) struct UiState { pub(crate) struct UiState {
/// The Ui elements on the screen right now pub elements: HashMap<ImmutableString, UiElement>,
elements: HashMap<ImmutableString, UiElement>, pub names: Vec<ImmutableString>,
/// Keeps track of element order
elements_ordered: Vec<ImmutableString>,
pub ct: Arc<Content>, pub ct: Arc<Content>,
current_scene: Option<ImmutableString>, current_scene: Option<ImmutableString>,
@ -71,7 +68,7 @@ impl UiState {
Self { Self {
ct, ct,
elements: HashMap::new(), elements: HashMap::new(),
elements_ordered: Vec::new(), names: Vec::new(),
current_scene: None, current_scene: None,
show_timings: true, show_timings: true,
@ -87,32 +84,29 @@ impl UiState {
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.elements.clear(); self.elements.clear();
self.elements_ordered.clear(); self.names.clear();
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
assert!(self.elements.len() == self.elements_ordered.len()); self.elements.len()
return self.elements.len();
} }
pub fn contains_name(&self, name: &ImmutableString) -> bool { /*
self.elements.contains_key(name) pub fn get_by_idx(&self, idx: usize) -> Option<&UiElement> {
self.elements.get(idx)
} }
pub fn get_by_name(&self, name: &ImmutableString) -> Option<&UiElement> { pub fn get_by_name(&self, name: &ImmutableString) -> Option<&UiElement> {
self.elements.get(name) let idx = self.names.get(name);
} if idx.is_none() {
pub fn get_by_idx(&self, idx: usize) -> Option<&UiElement> {
let name = self.elements_ordered.get(idx);
if name.is_none() {
return None; return None;
} }
self.elements.get(name.unwrap()) self.get_by_idx(*idx.unwrap())
} }
*/
pub fn get_mut_by_idx(&mut self, idx: usize) -> Option<&mut UiElement> { pub fn get_mut_by_idx(&mut self, idx: usize) -> Option<&mut UiElement> {
let name = self.elements_ordered.get(idx); let name = self.names.get(idx);
if name.is_none() { if name.is_none() {
return None; return None;
} }
@ -137,19 +131,12 @@ impl UiState {
self.current_scene = Some(scene); self.current_scene = Some(scene);
} }
pub fn step(&mut self, state: &mut RenderState, input: &Arc<RenderInput>) { pub fn step(&mut self, state: &mut RenderState, input: Arc<RenderInput>) {
let t = self.last_step.elapsed().as_secs_f32(); let t = self.last_step.elapsed().as_secs_f32();
for (_, e) in &mut self.elements { for (_, e) in &mut self.elements {
match e { match e {
UiElement::Sprite(sprite) => sprite.step(t), UiElement::Sprite(sprite) => sprite.step(t),
UiElement::Scrollbox(sbox) => sbox.step(t), UiElement::Scrollbox(sbox) => sbox.step(t),
UiElement::SubElement { element, .. } => match &mut **element {
UiElement::Sprite(sprite) => sprite.step(t),
UiElement::RadialBar(_) => {}
UiElement::Text(..) => {}
UiElement::Scrollbox(..) => {}
UiElement::SubElement { .. } => {}
},
_ => {} _ => {}
} }
} }
@ -162,14 +149,34 @@ impl UiState {
} }
pub fn add_element(&mut self, e: UiElement) { pub fn add_element(&mut self, e: UiElement) {
self.elements_ordered.push(e.get_name().clone()); self.names.push(e.get_name().clone());
self.elements.insert(e.get_name().clone(), e); self.elements.insert(e.get_name().clone(), e);
} }
// Remove an element from this sprite.
// This does NOT remove subelements from their parent sprites.
pub fn remove_element_incomplete(&mut self, name: &ImmutableString) -> Option<UiElement> {
let e = self.elements.remove(name);
self.names.retain(|x| *x != name);
return e;
}
// Remove an element from this sprite and from all subsprites. // Remove an element from this sprite and from all subsprites.
pub fn remove_element(&mut self, name: &ImmutableString) { pub fn remove_element(&mut self, name: &ImmutableString) {
let _ = self.elements.remove(name); let e = self.elements.remove(name);
self.elements_ordered.retain(|x| *x != name); self.names.retain(|x| *x != name);
match e {
Some(UiElement::SubElement { parent, element }) => {
let x = Rc::into_inner(element).unwrap().into_inner();
let parent = self.elements.get_mut(&parent).unwrap();
match parent {
UiElement::Scrollbox(s) => s.remove_element(&x.get_name()),
_ => unreachable!("invalid subelement parent"),
}
}
_ => {}
}
} }
} }
@ -189,18 +196,7 @@ impl<'a> UiState {
for e in self.elements.values() { for e in self.elements.values() {
match &e { match &e {
UiElement::Text(t) => v.push(t.get_textarea(input, window)), UiElement::Text(t) => v.push(t.get_textarea(input, window)),
UiElement::SubElement { parent, element } => { UiElement::Scrollbox(b) => v.extend(b.get_textareas(input, window)),
let parent = self.get_by_name(parent).unwrap();
match parent {
UiElement::Scrollbox(sbox) => match &**element {
UiElement::Text(x) => {
v.push(x.get_textarea_with_offset(input, window, sbox.offset))
}
_ => {}
},
_ => unreachable!(),
}
}
_ => {} _ => {}
} }
} }