Reworked subelements

Cleaned up argument passing
Added persistent UI script variables
master
Mark 2024-02-08 20:40:12 -08:00
parent 3d59ffffef
commit 847542a8cb
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
10 changed files with 287 additions and 306 deletions

View File

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

View File

@ -279,17 +279,14 @@ impl GPUState {
/// Handle user input
pub fn process_input(
&mut self,
input: RenderInput,
input: &Arc<RenderInput>,
event: InputEvent,
) -> Result<PlayerDirective> {
let input = Arc::new(input);
self.ui.process_input(&mut self.state, input, event)
}
/// Main render function. Draws sprites on a window.
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
let input = Arc::new(input);
pub fn render(&mut self, input: &Arc<RenderInput>) -> Result<(), wgpu::SurfaceError> {
if let Some(ship) = input.player.ship {
let o = input.phys_img.get_ship(&PhysSimShipHandle(ship));
if let Some(o) = o {
@ -390,7 +387,7 @@ impl GPUState {
self.push_effects(&input, (clip_ne, clip_sw));
}
self.ui.draw(&mut self.state, input.clone()).unwrap();
self.ui.draw(&mut self.state, input).unwrap();
// These should match the indices in each shader,
// and should each have a corresponding bind group layout.

View File

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

View File

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

View File

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

View File

@ -1,17 +1,14 @@
use nalgebra::Vector2;
use rhai::{Dynamic, ImmutableString};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use winit::window::Window;
use super::{super::api::Rect, OwnedTextArea};
use crate::{ui::UiElement, InputEvent, RenderInput, RenderState};
use super::super::api::Rect;
use crate::{InputEvent, RenderInput, RenderState};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct UiScrollbox {
pub name: ImmutableString,
pub rect: Rect,
pub offset: Vector2<f32>,
pub elements: HashMap<ImmutableString, Rc<RefCell<UiElement>>>,
has_mouse: bool,
}
@ -21,31 +18,13 @@ impl UiScrollbox {
Self {
name,
rect,
elements: HashMap::new(),
offset: Vector2::new(0.0, 0.0),
has_mouse: false,
}
}
pub fn add_element(&mut self, e: Rc<RefCell<UiElement>>) {
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 step(&mut self, _t: f32) {
// TODO: inertia
}
pub fn handle_event(
@ -54,31 +33,21 @@ impl UiScrollbox {
state: &mut RenderState,
event: &InputEvent,
) -> Option<Dynamic> {
let r = self
self.handle_event_with_offset(input, state, event, Vector2::new(0.0, 0.0))
}
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
.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 {
InputEvent::MouseMove(pos) => {
if r.contains_mouse(state, pos) && !self.has_mouse {
@ -102,35 +71,3 @@ impl UiScrollbox {
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,6 +19,9 @@ pub struct UiSprite {
/// Sprite angle, in degrees
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 false, this sprite will be stretched to fit in its box
preserve_aspect: bool,
@ -43,6 +46,7 @@ impl UiSprite {
has_mouse: false,
has_click: false,
preserve_aspect: false,
disable_events: false,
}
}
@ -62,6 +66,10 @@ impl UiSprite {
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) {
self.preserve_aspect = preserve_aspect;
}
@ -124,14 +132,24 @@ impl UiSprite {
state: &mut RenderState,
event: &InputEvent,
) -> Option<Dynamic> {
let r = self
self.handle_event_with_offset(input, state, event, Vector2::new(0.0, 0.0))
}
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
.to_centered(&state.window, input.ct.config.ui_scale);
// Release mouse when cursor leaves box
if self.has_click && !self.has_mouse {
self.has_click = false;
}
r.pos += offset;
match event {
InputEvent::MouseMove(pos) => {
@ -145,6 +163,7 @@ impl UiSprite {
if !r.contains_mouse(state, pos) && self.has_mouse {
self.has_mouse = false;
self.has_click = false;
return Some(Dynamic::from(MouseHoverEvent {
enter: false,
element: self.name.clone(),

View File

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

View File

@ -3,7 +3,7 @@ use galactica_content::Content;
use galactica_system::{phys::PhysSimShipHandle, PlayerDirective};
use galactica_util::rhai_error_to_anyhow;
use log::{debug, error};
use rhai::{Dynamic, Engine, ImmutableString, Scope};
use rhai::{CallFnOptions, Dynamic, Engine, ImmutableString, Scope};
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
use winit::event::VirtualKeyCode;
@ -58,36 +58,67 @@ impl UiScriptExecutor {
pub fn process_input(
&mut self,
state: &mut RenderState,
input: Arc<RenderInput>,
input: &Arc<RenderInput>,
event: InputEvent,
) -> Result<PlayerDirective> {
let current_scene = (*self.state).borrow().get_scene().clone();
if current_scene.is_none() {
return Ok(PlayerDirective::None);
}
let mut arg: Option<Dynamic> = None;
// First, check if this event is captured by any ui elements.
for (_, e) in &mut self.state.borrow_mut().elements {
arg = match e {
let len = (*self.state).borrow().len();
// Iterate front to back
// (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::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,
UiElement::SubElement { parent, element } => {
// Be very careful here, to avoid
// borrowing mutably twice...
let e_name = element.get_name();
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);
if arg.is_some() {
break;
// Return if event hook returns any PlayerDirective (including None),
// continue to the next element if the hook returns ().
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 arg.is_none() {
arg = match event {
let arg = match event {
InputEvent::Scroll(val) => Some(Dynamic::from(ScrollEvent { val })),
InputEvent::Keyboard { down, key } => {
let str = match key {
@ -135,10 +166,11 @@ impl UiScriptExecutor {
}
_ => None,
};
}
if let Some(arg) = arg {
self.run_event_callback(state, input, arg)
Ok(self
.run_event_callback(state, &input, arg)?
.unwrap_or(PlayerDirective::None))
} else {
return Ok(PlayerDirective::None);
}
@ -147,39 +179,42 @@ impl UiScriptExecutor {
fn run_event_callback(
&mut self,
state: &mut RenderState,
input: Arc<RenderInput>,
input: &Arc<RenderInput>,
arg: Dynamic,
) -> Result<PlayerDirective> {
) -> Result<Option<PlayerDirective>> {
let current_scene = (*self.state).borrow().get_scene().clone();
if current_scene.is_none() {
return Ok(PlayerDirective::None);
return Ok(None);
}
let current_scene = current_scene.unwrap();
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(self.engine.call_fn(
let d: Dynamic = rhai_error_to_anyhow::<Dynamic, _>(self.engine.call_fn_with_options(
CallFnOptions::new().rewind_scope(true),
&mut self.scope,
ct.config.ui_scenes.get(current_scene.as_str()).unwrap(),
ast,
"event",
(State::new(state, input.clone()), arg.clone()),
(State::new(state, input), arg.clone()),
))
.with_context(|| format!("while handling event `{:?}`", arg))
.with_context(|| format!("in ui scene `{}`", current_scene))?;
.with_context(|| format!("while calling `event()`"))
.with_context(|| format!("in UI scene `{}`", current_scene.as_str()))?;
if d.is::<PlayerDirective>() {
return Ok(d.cast());
return Ok(Some(d.cast::<PlayerDirective>()));
} else if !(d.is_unit()) {
error!(
"`event()` in UI scene `{current_scene}` returned invalid type `{}`",
d
"`event()` in UI scene `{}` returned invalid type `{}`",
d,
current_scene.as_str()
)
}
return Ok(PlayerDirective::None);
return Ok(None);
}
/// 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();
if self.last_scene == current_scene {
return Ok(());
@ -195,33 +230,44 @@ impl UiScriptExecutor {
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.config
let ast = ct
.config
.ui_scenes
.get(current_scene.as_ref().unwrap().as_str())
.unwrap(),
.unwrap();
// Clear previous state
self.scope.clear();
self.state.borrow_mut().clear();
rhai_error_to_anyhow(self.engine.call_fn_with_options(
CallFnOptions::new().rewind_scope(false),
&mut self.scope,
ast,
"init",
(State::new(state, input.clone()),),
),
)
(State::new(state, input),),
))
.with_context(|| format!("while running `init()`"))
.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(());
}
/// 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();
// Initialize start scene if we haven't yet
@ -230,10 +276,10 @@ impl UiScriptExecutor {
.borrow_mut()
.set_scene(ImmutableString::from(&ct.config.start_ui_scene));
}
self.set_scene(state, input.clone())?;
self.set_scene(state, input)?;
let current_scene = (*self.state).borrow().get_scene().clone();
(*self.state).borrow_mut().step(state, input.clone());
(*self.state).borrow_mut().step(state, input);
// Run step() (if it is defined)
let ast = ct
@ -242,11 +288,12 @@ impl UiScriptExecutor {
.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(
rhai_error_to_anyhow(self.engine.call_fn_with_options(
CallFnOptions::new().rewind_scope(true),
&mut self.scope,
ast,
"step",
(State::new(state, input.clone()),),
(State::new(state, input),),
))
.with_context(|| format!("while calling `step()`"))
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
@ -271,12 +318,12 @@ impl UiScriptExecutor {
true
}
} {
self.run_event_callback(state, input.clone(), Dynamic::from(PlayerShipStateEvent {}))?;
self.run_event_callback(state, &input, Dynamic::from(PlayerShipStateEvent {}))?;
}
let len = (*self.state).borrow().len();
for i in 0..len {
match (*self.state).borrow_mut().get_mut_by_idx(i).unwrap() {
match self.state.borrow().get_by_idx(i).unwrap() {
UiElement::Sprite(sprite) => {
sprite.push_to_buffer(&input, state);
}
@ -285,15 +332,27 @@ impl UiScriptExecutor {
x.push_to_buffer(&input, state);
}
UiElement::Scrollbox(x) => {
x.push_to_buffer(&input, state);
}
UiElement::Scrollbox(_) => {}
UiElement::Text(..) => {}
UiElement::SubElement { .. } | UiElement::Text(..) => {}
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 { .. } => {}
},
_ => {}
}
}
}
}
self.scope.rewind(0);
//self.scope.rewind(0);
return Ok(());
}
}

View File

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