use galactica_content::Content; use log::{debug, error}; use rhai::ImmutableString; use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, time::Instant}; use winit::window::Window; use super::{ elements::{FpsIndicator, OwnedTextArea, UiRadialBar, UiScrollbox, UiSprite, UiTextBox}, Camera, }; use crate::{RenderInput, RenderState}; #[derive(Debug)] pub enum UiElement { Sprite(UiSprite), RadialBar(UiRadialBar), Text(UiTextBox), Scrollbox(UiScrollbox), /// This is a sub-element managed by another element SubElement { parent: ImmutableString, element: Rc>, }, } impl UiElement { pub fn get_name(&self) -> ImmutableString { match self { Self::Sprite(x) => x.name.clone(), 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(), } } } #[derive(Clone, Debug)] pub(crate) struct UiConfig { pub show_phys: bool, pub show_starfield: bool, } pub(crate) struct UiState { pub elements: HashMap, pub names: Vec, pub ct: Arc, current_scene: Option, show_timings: bool, fps_indicator: FpsIndicator, last_step: Instant, pub config: UiConfig, /// The player's camera. /// Only used when drawing physics. pub camera: Camera, } // TODO: remove this unsafe impl Send for UiState {} unsafe impl Sync for UiState {} impl UiState { pub fn new(ct: Arc, state: &mut RenderState) -> Self { Self { ct, elements: HashMap::new(), names: Vec::new(), current_scene: None, show_timings: true, fps_indicator: FpsIndicator::new(state), config: UiConfig { show_phys: false, show_starfield: false, }, last_step: Instant::now(), camera: Camera::new(), } } pub fn clear(&mut self) { self.elements.clear(); self.names.clear(); } pub fn len(&self) -> usize { self.elements.len() } /* pub fn get_by_idx(&self, idx: usize) -> Option<&UiElement> { self.elements.get(idx) } pub fn get_by_name(&self, name: &ImmutableString) -> Option<&UiElement> { let idx = self.names.get(name); if idx.is_none() { return None; } self.get_by_idx(*idx.unwrap()) } */ pub fn get_mut_by_idx(&mut self, idx: usize) -> Option<&mut UiElement> { let name = self.names.get(idx); if name.is_none() { return None; } self.elements.get_mut(name.unwrap()) } pub fn get_mut_by_name(&mut self, name: &ImmutableString) -> Option<&mut UiElement> { self.elements.get_mut(name) } pub fn get_scene(&self) -> &Option { &self.current_scene } pub fn set_scene(&mut self, scene: ImmutableString) { if !self.ct.config.ui_scenes.contains_key(scene.as_str()) { error!("tried to switch to ui scene `{scene}`, which doesn't exist"); return; } debug!("switching to {:?}", scene); self.current_scene = Some(scene); } pub fn step(&mut self, state: &mut RenderState, input: Arc) { 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), _ => {} } } if self.show_timings { self.fps_indicator .step(&input, &mut state.text_font_system.borrow_mut()); } self.last_step = Instant::now(); } pub fn add_element(&mut self, e: UiElement) { self.names.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 { 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"), } } _ => {} } } } // TODO: don't allocate here, return an iterator impl<'a> UiState { pub fn get_textareas(&'a self, input: &RenderInput, window: &Window) -> Vec { let mut v = Vec::with_capacity(32); if self.current_scene.is_none() { return v; } if self.show_timings { v.push(self.fps_indicator.get_textarea(input, window)) } for 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)), _ => {} } } return v; } }