use bevy::prelude::*;
use once_cell::sync::Lazy;
use std::collections::{HashMap, VecDeque};
use std::sync::Mutex;
use crate::html::HtmlSource;
use crate::widgets::{Body, UIGenID, WidgetId, WidgetKind};
pub static UI_ID_GENERATE: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static BODY_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static DIV_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static BUTTON_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static CHECK_BOX_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static CHOICE_BOX_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static DIVIDER_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static FIELDSET_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static HEADLINE_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static IMAGE_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static INPUT_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static PARAGRAPH_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static PROGRESS_BAR_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static RADIO_BUTTON_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static SCROLL_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static SLIDER_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static SWITCH_BUTTON_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static TOGGLE_BUTTON_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub struct IdPool {
counter: usize,
free_list: VecDeque<usize>,
}
impl IdPool {
pub fn new() -> Self {
Self {
counter: 0,
free_list: VecDeque::new(),
}
}
pub fn acquire(&mut self) -> usize {
if let Some(id) = self.free_list.pop_front() {
id
} else {
let id = self.counter;
self.counter += 1;
id
}
}
pub fn release(&mut self, id: usize) {
self.free_list.push_back(id);
}
}
#[derive(Default, Resource, Reflect, Debug)]
#[reflect(Resource)]
pub struct UiRegistry {
pub collection: HashMap<String, HtmlSource>,
pub current: Option<String>,
pub ui_update: bool,
}
impl UiRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, name: String, source: HtmlSource) {
self.collection.insert(name.clone(), HtmlSource { source_id: name.clone(), ..source });
}
pub fn add_and_use(&mut self, name: String, source: HtmlSource) {
self.add(name.clone(), HtmlSource { source_id: name.clone(), ..source});
self.use_ui(&name);
}
pub fn remove(&mut self, name: &str) {
if let Some(current) = self.current.clone() {
if current.eq(&name.to_string()) {
self.current = None;
}
}
self.collection.remove(name);
}
pub fn remove_and_use(&mut self, name: &str, to_switch: &str) {
self.remove(name);
self.use_ui(to_switch);
}
pub fn remove_all(&mut self) {
self.collection.clear();
self.current = None;
}
pub fn get(&self, name: &str) -> Option<&HtmlSource> {
self.collection.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut HtmlSource> {
self.collection.get_mut(name)
}
pub fn use_ui(&mut self, name: &str) {
if self.get(name).is_some() {
self.current = Some(name.to_string());
self.ui_update = true;
} else {
warn!("Ui was empty and will removed now!");
self.current = None;
}
}
}
#[derive(Default, Resource, Debug)]
pub struct UiInitResource(pub bool);
#[derive(Resource, Debug)]
struct LastUiUsage(pub Option<String>);
pub struct ExtendedRegistryPlugin;
impl Plugin for ExtendedRegistryPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<UiInitResource>();
app.init_resource::<UiRegistry>();
app.add_systems(
Update,
(
despawn_widget_ids,
update_que,
)
.chain()
.run_if(resource_changed::<UiRegistry>)
);
}
}
fn update_que(
mut commands: Commands,
mut ui_registry: ResMut<UiRegistry>,
mut ui_init: ResMut<UiInitResource>,
query: Query<(Entity, &HtmlSource), With<HtmlSource>>,
mut body_query: Query<(Entity, &mut Visibility, &Body), (Without<HtmlSource>, With<Body>)>,
) {
if let Some(name) = ui_registry.current.clone() {
if query.is_empty() {
spawn_ui_source(&mut commands, &name, &ui_registry, &mut ui_init);
return;
}
for (entity, html_source) in query.iter() {
if html_source.source_id == name && !ui_registry.ui_update{
continue;
}
for (body_entity, mut body_vis, body) in body_query.iter_mut() {
if ui_registry.ui_update {
commands.entity(body_entity).despawn();
} else {
if let Some(bind) = body.html_key.clone() {
if bind.eq(&html_source.source_id) {
*body_vis = Visibility::Hidden;
}
}
}
}
ui_registry.ui_update = false;
spawn_ui_source(&mut commands, &name, &ui_registry, &mut ui_init);
commands.entity(entity).despawn();
}
} else {
for (entity, html_source) in query.iter() {
for (_, mut body_vis, body) in body_query.iter_mut() {
if let Some(bind) = body.html_key.clone() {
if bind.eq(&html_source.source_id) {
*body_vis = Visibility::Hidden;
}
}
}
commands.entity(entity).despawn();
}
}
}
fn spawn_ui_source(commands: &mut Commands, name: &str, ui_registry: &UiRegistry, ui_init: &mut UiInitResource) {
if let Some(source) = ui_registry.get(name) {
ui_init.0 = true;
commands.spawn((
Name::new(name.to_string()),
source.clone(),
));
debug!("Loaded Registered UI {:?}", source);
} else {
warn!("UI source {} not found in registry", name);
}
}
fn despawn_widget_ids(
mut commands: Commands,
ui_registry: Res<UiRegistry>,
last_ui_usage: Option<Res<LastUiUsage>>,
query: Query<Entity, With<WidgetId>>,
widget_ids: Query<&WidgetId>,
ui_id: Query<&UIGenID>
) {
if let Some(name) = ui_registry.current.clone() {
if let Some(last_ui) = last_ui_usage {
if let Some(last_ui_name) = last_ui.0.clone() {
if last_ui_name.eq(&name) {
debug!("UI unchanged: current: {}, last: {}", name, last_ui_name);
}
}
}
}
for id in ui_id.iter() {
UI_ID_GENERATE.lock().unwrap().release(id.get());
}
for entity in query.iter() {
if let Ok(widget_id) = widget_ids.get(entity) {
match widget_id.kind {
WidgetKind::Body => BODY_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Div => DIV_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Headline => HEADLINE_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Paragraph => PARAGRAPH_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Button => BUTTON_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::CheckBox => CHECK_BOX_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Slider => SLIDER_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::InputField => INPUT_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::ChoiceBox => CHOICE_BOX_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Img => IMAGE_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::ProgressBar => PROGRESS_BAR_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::RadioButton => RADIO_BUTTON_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::SwitchButton => SWITCH_BUTTON_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::ToggleButton => TOGGLE_BUTTON_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Scrollbar => SCROLL_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Divider => DIVIDER_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::FieldSet => FIELDSET_ID_POOL.lock().unwrap().release(widget_id.id),
}
}
}
commands.insert_resource(LastUiUsage(ui_registry.current.clone()));
}