use crate::html::HtmlSource;
use bevy::prelude::*;
use once_cell::sync::Lazy;
use std::collections::{HashMap, VecDeque};
use std::sync::Mutex;
#[cfg(not(feature = "extended-framework"))]
use crate::widgets::{Body, UIGenID, WidgetId, WidgetKind};
#[cfg(not(feature = "extended-framework"))]
use std::collections::HashSet;
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 FORM_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 HYPER_LINK_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 DATE_PICKER_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 TOOL_TIP_ID_POOL: Lazy<Mutex<IdPool>> = Lazy::new(|| Mutex::new(IdPool::new()));
pub static BADGE_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 COLOR_PICKER_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 static LIST_BOX_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<Vec<String>>,
pub ui_update: bool,
}
#[allow(deprecated)]
impl UiRegistry {
#[inline]
fn ensure_legacy_registry_available() {
#[cfg(feature = "extended-framework")]
panic!(
"UiRegistry is part of the legacy system and is disabled when `extended-framework` is active."
);
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn new() -> Self {
Self::ensure_legacy_registry_available();
Self::default()
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn add(&mut self, name: String, source: HtmlSource) {
Self::ensure_legacy_registry_available();
self.collection.insert(
name.clone(),
HtmlSource {
source_id: name.clone(),
..source
},
);
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn add_and_use(&mut self, name: String, source: HtmlSource) {
Self::ensure_legacy_registry_available();
self.add(
name.clone(),
HtmlSource {
source_id: name.clone(),
..source
},
);
self.use_ui(&name);
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn add_and_use_multiple(&mut self, entries: Vec<(String, HtmlSource)>) {
Self::ensure_legacy_registry_available();
let mut names = Vec::with_capacity(entries.len());
for (name, source) in entries {
self.add(
name.clone(),
HtmlSource {
source_id: name.clone(),
..source
},
);
names.push(name);
}
self.use_uis(names);
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn remove(&mut self, name: &str) {
Self::ensure_legacy_registry_available();
if let Some(current) = &mut self.current {
current.retain(|current| current != name);
if current.is_empty() {
self.current = None;
}
}
self.collection.remove(name);
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn remove_and_use(&mut self, name: &str, to_switch: &str) {
Self::ensure_legacy_registry_available();
self.remove(name);
self.use_ui(to_switch);
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn remove_all(&mut self) {
Self::ensure_legacy_registry_available();
self.collection.clear();
self.current = None;
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn get(&self, name: &str) -> Option<&HtmlSource> {
Self::ensure_legacy_registry_available();
self.collection.get(name)
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn get_mut(&mut self, name: &str) -> Option<&mut HtmlSource> {
Self::ensure_legacy_registry_available();
self.collection.get_mut(name)
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn use_ui(&mut self, name: &str) {
Self::ensure_legacy_registry_available();
if self.get(name).is_some() {
self.current = Some(vec![name.to_string()]);
self.ui_update = true;
} else {
warn!("Ui was empty and will removed now!");
self.current = None;
}
}
#[deprecated(
note = "Legacy UiRegistry method. Prefer the extended-framework component entry via index.html."
)]
pub fn use_uis(&mut self, names: Vec<String>) {
Self::ensure_legacy_registry_available();
let mut valid = Vec::new();
for name in names {
if self.get(&name).is_some() {
valid.push(name);
} else {
warn!("Ui was empty and will removed now!");
}
}
if valid.is_empty() {
self.current = None;
} else {
self.current = Some(valid);
}
self.ui_update = true;
}
}
#[derive(Default, Resource, Debug)]
pub struct UiInitResource(pub bool);
#[cfg(not(feature = "extended-framework"))]
#[derive(Resource, Debug)]
struct LastUiUsage(pub Option<Vec<String>>);
pub struct ExtendedRegistryPlugin;
impl Plugin for ExtendedRegistryPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "extended-framework")]
{
let _ = app;
panic!(
"ExtendedRegistryPlugin is disabled when `extended-framework` is active. Use the component system with index.html instead."
);
}
#[cfg(not(feature = "extended-framework"))]
{
app.init_resource::<UiInitResource>();
app.init_resource::<UiRegistry>();
app.add_systems(
Update,
(despawn_widget_ids, update_que)
.chain()
.run_if(resource_changed::<UiRegistry>),
);
}
}
}
#[cfg(not(feature = "extended-framework"))]
fn update_que(
mut commands: Commands,
mut ui_registry: ResMut<UiRegistry>,
mut ui_init: ResMut<UiInitResource>,
mut structure_map: ResMut<crate::html::HtmlStructureMap>,
query: Query<(Entity, &HtmlSource), With<HtmlSource>>,
mut body_query: Query<(Entity, &mut Visibility, &Body), (Without<HtmlSource>, With<Body>)>,
) {
structure_map.active = ui_registry.current.clone();
let Some(active_names) = ui_registry.current.clone() else {
for (entity, html_source) in query.iter() {
hide_bound_bodies_for_source(&html_source.source_id, &mut body_query);
commands.entity(entity).despawn();
}
ui_registry.ui_update = false;
return;
};
let active_set: HashSet<String> = active_names.iter().cloned().collect();
if ui_registry.ui_update {
for (body_entity, _, body) in body_query.iter_mut() {
if let Some(bind) = body.html_key.as_ref() {
if active_set.contains(bind) {
commands.entity(body_entity).despawn();
}
}
}
for (entity, html_source) in query.iter() {
if active_set.contains(&html_source.source_id) {
commands.entity(entity).despawn();
}
}
}
let mut existing = HashSet::new();
for (_, html_source) in query.iter() {
if active_set.contains(&html_source.source_id) {
existing.insert(html_source.source_id.clone());
}
}
if ui_registry.ui_update {
existing.clear();
}
for name in &active_names {
if !existing.contains(name) {
spawn_ui_source(&mut commands, name, &ui_registry, &mut ui_init);
}
}
for (entity, html_source) in query.iter() {
if !active_set.contains(&html_source.source_id) {
hide_bound_bodies_for_source(&html_source.source_id, &mut body_query);
commands.entity(entity).despawn();
}
}
ui_registry.ui_update = false;
}
#[cfg(not(feature = "extended-framework"))]
fn hide_bound_bodies_for_source(
source_id: &str,
body_query: &mut Query<(Entity, &mut Visibility, &Body), (Without<HtmlSource>, With<Body>)>,
) {
for (_, mut body_vis, body) in body_query.iter_mut() {
if body.html_key.as_deref() == Some(source_id) {
*body_vis = Visibility::Hidden;
}
}
}
#[cfg(not(feature = "extended-framework"))]
#[allow(deprecated)]
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);
}
}
#[cfg(not(feature = "extended-framework"))]
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(current) = ui_registry.current.as_ref() {
if let Some(last_ui) = last_ui_usage {
if last_ui.0.as_ref() == Some(current) {
debug!("UI unchanged: current: {:?}", current);
}
}
}
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::Form => FORM_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Headline => HEADLINE_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::HyperLink => HYPER_LINK_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Paragraph => PARAGRAPH_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::ToolTip => TOOL_TIP_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::Badge => BADGE_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::ColorPicker => {
COLOR_PICKER_ID_POOL.lock().unwrap().release(widget_id.id)
}
WidgetKind::InputField => INPUT_ID_POOL.lock().unwrap().release(widget_id.id),
WidgetKind::DatePicker => DATE_PICKER_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),
WidgetKind::ListBox => LIST_BOX_ID_POOL.lock().unwrap().release(widget_id.id),
}
}
}
commands.insert_resource(LastUiUsage(ui_registry.current.clone()));
}