use crate::events::ElementSpawn;
use crate::widgets::Widget;
use crate::widgets::button::ButtonWidget;
use crate::widgets::checkbox::CheckboxWidget;
use crate::widgets::divider::DividerWidget;
use crate::widgets::dropdown::DropdownWidget;
use crate::widgets::image::ImageWidget;
use crate::widgets::node::NodeWidget;
use crate::widgets::progress_bar::ProgressBarWidget;
use crate::widgets::scroll_view::ScrollViewWidget;
use crate::widgets::slider::SliderWidget;
use crate::widgets::text::TextWidget;
use crate::widgets::text_input::TextInputWidget;
use crate::widgets::tooltip::TooltipWidget;
use bevy::asset::AssetServer;
use bevy::color::Color;
use bevy::ecs::relationship::RelatedSpawnerCommands;
use bevy::prelude::{
BorderColor, ChildOf, Component, Entity, EntityCommands, GlobalTransform, Interaction,
Transform,
};
use bevy::ui::{BackgroundColor, Node};
use rustc_hash::FxHashMap;
use smol_str::SmolStr;
#[derive(Clone, Debug, Component)]
pub struct Element {
pub props: ElementProps,
pub overrides: ElementOverrides,
pub id: Option<ElementId>,
pub children: Vec<Element>,
}
impl Element {
pub(crate) fn spawn(
&self,
parent: &mut RelatedSpawnerCommands<ChildOf>,
assets: &AssetServer,
reg: &mut ElementRegistry,
) -> Entity {
let mut entity_cmd = parent.spawn((
self.clone(),
self.props.node.clone(),
Interaction::None,
Transform::default(),
GlobalTransform::default(),
BackgroundColor(self.props.bg_color.unwrap_or(Color::NONE)),
self.props.border_color.unwrap_or(BorderColor::DEFAULT),
));
if let Some(id) = &self.id {
entity_cmd.insert(id.clone());
}
let root_entity = entity_cmd.id();
let target_entity = self.props.widget.spawn(&mut entity_cmd, assets);
if target_entity == root_entity {
entity_cmd.with_children(|p| {
for child in &self.children {
child.spawn(p, assets, reg);
}
});
} else {
parent
.commands_mut()
.entity(target_entity)
.with_children(|p| {
for child in &self.children {
child.spawn(p, assets, reg);
}
});
}
if let Some(id) = self.id() {
reg.register_element(id, root_entity);
}
parent.commands_mut().trigger(ElementSpawn {
entity: root_entity,
id: self.id(),
});
root_entity
}
pub fn id(&self) -> Option<ElementId> {
self.id.clone()
}
pub fn overrides(&self) -> &ElementOverrides {
&self.overrides
}
}
#[derive(Clone, Debug)]
pub struct ElementProps {
pub node: Node,
pub bg_color: Option<Color>,
pub border_color: Option<BorderColor>,
pub widget: ElementWidget,
}
#[derive(Clone, Debug)]
pub enum ElementWidget {
Node(NodeWidget),
Button(ButtonWidget),
Text(TextWidget),
Image(ImageWidget),
Checkbox(CheckboxWidget),
TextInput(TextInputWidget),
Slider(SliderWidget),
ProgressBar(ProgressBarWidget),
Tooltip(TooltipWidget),
Divider(DividerWidget),
ScrollView(ScrollViewWidget),
Dropdown(DropdownWidget),
}
impl ElementWidget {
pub fn spawn(&self, commands: &mut EntityCommands, assets: &AssetServer) -> Entity {
match self {
ElementWidget::Node(w) => w.spawn(commands, assets),
ElementWidget::Button(w) => w.spawn(commands, assets),
ElementWidget::Text(w) => w.spawn(commands, assets),
ElementWidget::Image(w) => w.spawn(commands, assets),
ElementWidget::Checkbox(w) => w.spawn(commands, assets),
ElementWidget::TextInput(w) => w.spawn(commands, assets),
ElementWidget::Slider(w) => w.spawn(commands, assets),
ElementWidget::ProgressBar(w) => w.spawn(commands, assets),
ElementWidget::Tooltip(w) => w.spawn(commands, assets),
ElementWidget::Divider(w) => w.spawn(commands, assets),
ElementWidget::ScrollView(w) => w.spawn(commands, assets),
ElementWidget::Dropdown(w) => w.spawn(commands, assets),
}
}
pub fn as_node(&self) -> Option<&NodeWidget> {
match self {
ElementWidget::Node(node) => Some(node),
_ => None,
}
}
pub fn as_button(&self) -> Option<&ButtonWidget> {
match self {
ElementWidget::Button(button) => Some(button),
_ => None,
}
}
pub fn as_text(&self) -> Option<&TextWidget> {
match self {
ElementWidget::Text(text) => Some(text),
_ => None,
}
}
pub fn as_image(&self) -> Option<&ImageWidget> {
match self {
ElementWidget::Image(image) => Some(image),
_ => None,
}
}
pub fn as_checkbox(&self) -> Option<&CheckboxWidget> {
match self {
ElementWidget::Checkbox(checkbox) => Some(checkbox),
_ => None,
}
}
pub fn as_text_input(&self) -> Option<&TextInputWidget> {
match self {
ElementWidget::TextInput(text_input) => Some(text_input),
_ => None,
}
}
pub fn as_slider(&self) -> Option<&SliderWidget> {
match self {
ElementWidget::Slider(slider) => Some(slider),
_ => None,
}
}
pub fn as_progress_bar(&self) -> Option<&ProgressBarWidget> {
match self {
ElementWidget::ProgressBar(progress_bar) => Some(progress_bar),
_ => None,
}
}
pub fn as_tooltip(&self) -> Option<&TooltipWidget> {
match self {
ElementWidget::Tooltip(tooltip) => Some(tooltip),
_ => None,
}
}
pub fn as_divider(&self) -> Option<&DividerWidget> {
match self {
ElementWidget::Divider(divider) => Some(divider),
_ => None,
}
}
pub fn as_scroll_view(&self) -> Option<&ScrollViewWidget> {
match self {
ElementWidget::ScrollView(scroll_view) => Some(scroll_view),
_ => None,
}
}
pub fn as_dropdown(&self) -> Option<&DropdownWidget> {
match self {
ElementWidget::Dropdown(dropdown) => Some(dropdown),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Component)]
pub struct ElementId(SmolStr);
impl ElementId {
pub fn new(s: impl AsRef<str>) -> Self {
Self(SmolStr::new(s))
}
pub fn new_static(s: &'static str) -> Self {
Self(SmolStr::new_static(s))
}
}
impl From<&'static str> for ElementId {
fn from(s: &'static str) -> Self {
Self::new_static(s)
}
}
#[derive(Clone, Debug)]
pub struct ElementOverrides {
pub default: ElementProps,
pub on_hover: ElementProps,
pub on_click: ElementProps,
}
#[derive(Clone, Debug)]
pub(crate) struct ElementRegistry {
ids: FxHashMap<ElementId, Entity>,
}
impl ElementRegistry {
pub(crate) fn new(cap: usize) -> Self {
Self {
ids: FxHashMap::with_capacity_and_hasher(cap, Default::default()),
}
}
pub(crate) fn register_element(&mut self, id: ElementId, entity: Entity) {
self.ids.insert(id, entity);
}
pub(crate) fn get_element(&self, id: ElementId) -> Option<Entity> {
self.ids.get(&id).cloned()
}
}