mod theme_provider;
use bevy::prelude::*;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
pub use theme_provider::{ThemeProvider, ThemeProviderState};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum ProviderChildPolicy {
#[default]
Any,
Only(Vec<String>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProviderRules {
pub requires_body_child: bool,
pub child_policy: ProviderChildPolicy,
pub allow_in_head: bool,
}
impl Default for ProviderRules {
fn default() -> Self {
Self {
requires_body_child: false,
child_policy: ProviderChildPolicy::Any,
allow_in_head: true,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ProviderEffect {
pub extra_css_paths: Vec<String>,
}
#[derive(Clone, Copy)]
pub struct ProviderResolveContext<'a> {
attributes: &'a HashMap<String, String>,
source_path: &'a str,
theme_asset_dir: Option<&'a str>,
known_themes: Option<&'a HashSet<String>>,
fallback_theme: Option<&'a str>,
active_theme: Option<&'a str>,
}
impl<'a> ProviderResolveContext<'a> {
pub fn new(attributes: &'a HashMap<String, String>, source_path: &'a str) -> Self {
Self {
attributes,
source_path,
theme_asset_dir: None,
known_themes: None,
fallback_theme: None,
active_theme: None,
}
}
pub fn with_theme_scope(
mut self,
theme_asset_dir: Option<&'a str>,
known_themes: Option<&'a HashSet<String>>,
fallback_theme: Option<&'a str>,
active_theme: Option<&'a str>,
) -> Self {
self.theme_asset_dir = theme_asset_dir;
self.known_themes = known_themes;
self.fallback_theme = fallback_theme;
self.active_theme = active_theme;
self
}
pub fn attr(&self, key: &str) -> Option<&str> {
self.attributes.get(key).map(String::as_str)
}
pub fn source_path(&self) -> &str {
self.source_path
}
pub fn theme_asset_dir(&self) -> Option<&str> {
self.theme_asset_dir
}
pub fn known_themes(&self) -> Option<&HashSet<String>> {
self.known_themes
}
pub fn fallback_theme(&self) -> Option<&str> {
self.fallback_theme
}
pub fn active_theme(&self) -> Option<&str> {
self.active_theme
}
}
pub trait UiProvider: Send + Sync + 'static {
fn tag(&self) -> &'static str;
fn rules(&self) -> ProviderRules {
ProviderRules::default()
}
fn resolve(&self, ctx: ProviderResolveContext<'_>) -> Result<ProviderEffect, String>;
}
#[derive(Resource, Default)]
pub struct UiProviderRegistry {
providers: Vec<Arc<dyn UiProvider>>,
}
impl UiProviderRegistry {
pub fn register<P: UiProvider>(&mut self, provider: P) {
let tag = provider.tag();
self.providers.retain(|current| current.tag() != tag);
self.providers.push(Arc::new(provider));
}
pub fn iter(&self) -> impl Iterator<Item = &Arc<dyn UiProvider>> {
self.providers.iter()
}
}
pub trait UiProviderAppExt {
fn register_ui_provider<P: UiProvider>(&mut self, provider: P) -> &mut Self;
}
impl UiProviderAppExt for App {
fn register_ui_provider<P: UiProvider>(&mut self, provider: P) -> &mut Self {
self.init_resource::<UiProviderRegistry>();
self.world_mut()
.resource_mut::<UiProviderRegistry>()
.register(provider);
self
}
}
pub struct ExtendedUiProviderPlugin;
impl Plugin for ExtendedUiProviderPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<UiProviderRegistry>();
app.init_resource::<ThemeProviderState>();
app.world_mut()
.resource_mut::<UiProviderRegistry>()
.register(ThemeProvider);
app.add_systems(Startup, theme_provider::refresh_theme_provider_state);
app.add_systems(
Update,
(
theme_provider::refresh_theme_provider_state
.run_if(resource_changed::<crate::ExtendedUiConfiguration>),
theme_provider::apply_theme_switch_requests,
),
);
}
}