ui/src/builder/snippet.rs
2025-03-05 08:04:19 +01:00

351 lines
10 KiB
Rust

use std::{any::Any, collections::HashMap, sync::Arc};
use assetpath::AssetPath;
use ecs::World;
use super::validator::{
buttoninfo::NeighbourInfo,
uiinfoelement::UiInfoElement,
validator::{Validator, handle_function_suffix},
};
use crate::prelude::*;
use anyhow::Result;
pub struct GuiSnippet {
grid: Arc<Grid>,
ids: HashMap<String, UiElement>,
}
impl GuiSnippet {
pub fn new(world: &mut World, path: &AssetPath) -> Result<Arc<Self>> {
let validator = Validator::new(
#[cfg(feature = "audio")]
world.resources.get::<GuiHandler>(),
path,
)?;
Self::_new(world, validator)
}
pub fn from_str(world: &mut World, s: &str) -> Result<Arc<Self>> {
let validator = Validator::from_str(
#[cfg(feature = "audio")]
world.resources.get::<GuiHandler>(),
s,
)?;
Self::_new(world, validator)
}
fn _new(world: &mut World, validator: Validator) -> Result<Arc<Self>> {
let root = validator.root();
let mut ids = HashMap::new();
let mut custom_neighbours = Vec::new();
if root.children.len() != 1 {
return Err(anyhow::Error::msg(format!(
"Expected exact 1 root. Found {} roots",
root.children.len()
)));
}
let gui_handler = world.resources.get_mut_unchecked::<GuiHandler>();
let grid_info = &root.children[0];
let grid = Grid::try_from(gui_handler, grid_info, false)?;
GuiBuilder::insert_id(&mut ids, &grid_info.id, &grid)?;
for child in grid_info.children.read().unwrap().iter() {
Self::create_child(
world,
gui_handler,
child,
&grid,
&mut ids,
&mut custom_neighbours,
)?;
}
GuiBuilder::connect_custom_neighbours(&ids, custom_neighbours)?;
Ok(Arc::new(GuiSnippet { grid, ids }))
}
pub fn ids(&self) -> Vec<&str> {
self.ids.keys().map(|n| n.as_str()).collect()
}
}
macro_rules! impl_element {
($data_type:ident $(< $($lt:lifetime),+ >)?) => {
impl GetElement<$data_type$(<$($lt,)+>)?> for GuiSnippet {
fn element(&self, id: &str) -> Result<Arc<$data_type$(<$($lt,)+>)?>> {
self.ids.element(id)
}
}
};
}
impl_element!(Button);
impl_element!(Grid);
impl_element!(Label);
impl_element!(TextField);
impl_element!(Icon);
impl_element!(ProgressBar);
impl_element!(MultiLineLabel);
impl_element!(MultiLineTextField);
impl Visibility for GuiSnippet {
fn visible(&self) -> bool {
self.grid.visible()
}
fn set_visibility(&self, world: &mut World, visibility: bool) -> Result<()> {
self.grid.set_visibility(world, visibility)
}
}
impl GuiElementTraits for GuiSnippet {
fn gridable(&self) -> Option<&dyn Gridable> {
Some(self)
}
fn visibility(&self) -> Option<&dyn Visibility> {
Some(self)
}
fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> {
Some(GuiElement::GuiSnippet(self))
}
}
impl Gridable for GuiSnippet {
fn set_frame(
&self,
gui_handler: &mut GuiHandler,
x: i32,
y: i32,
w: u32,
h: u32,
vert_align: VerticalAlign,
hori_align: HorizontalAlign,
) -> Result<()> {
self.grid
.set_frame(gui_handler, x, y, w, h, vert_align, hori_align)
}
fn selectable(&self) -> Option<&Arc<Selectable>> {
self.grid.selectable()
}
fn type_name(&self) -> &str {
"GuiSnippet"
}
fn set_layer(&self, layer: i32) -> Result<()> {
self.grid.set_layer(layer)
}
fn position_extent(&self) -> (i32, i32, u32, u32) {
self.grid.position_extent()
}
}
impl Functionality for GuiSnippet {
fn set_click_callbacks(
&self,
functions: Vec<(&str, Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>)>,
) -> Result<()> {
for (function_name, callback) in functions {
let suffix_less_function_name = handle_function_suffix(function_name);
let button: Arc<Button> = self.element(&suffix_less_function_name)?;
button.set_callback(callback);
}
Ok(())
}
fn set_vec_callbacks(
&self,
_functions: Vec<(
&str,
Box<dyn Fn(&mut World, &dyn Any) -> Result<()> + Send + Sync>,
)>,
) -> Result<()> {
Ok(())
}
fn set_select_callbacks(
&self,
callbacks: Vec<(
&str,
Box<dyn Fn(&mut World, bool) -> Result<()> + Send + Sync>,
)>,
) -> Result<()> {
for (function_name, callback) in callbacks {
let suffix_less_function_name = handle_function_suffix(function_name);
let button: Arc<Button> = self.element(&suffix_less_function_name)?;
button.set_select_callback(callback);
}
Ok(())
}
fn set_custom_callbacks(
&self,
callbacks: Vec<(
&str,
Box<dyn Fn(&mut World, ControllerButton) -> Result<bool> + Send + Sync>,
)>,
) -> Result<()> {
for (function_name, callback) in callbacks {
let suffix_less_function_name = handle_function_suffix(function_name);
let button: Arc<Button> = self.element(&suffix_less_function_name)?;
button.set_custom_callback(callback);
}
Ok(())
}
}
impl GuiSnippet {
fn create_child(
world: &mut World,
gui_handler: &mut GuiHandler,
child: &UiInfoElement,
grid: &Grid,
ids: &mut HashMap<String, UiElement>,
neighbour_infos: &mut Vec<(String, Vec<NeighbourInfo>)>,
) -> Result<()> {
match child {
UiInfoElement::Button(button_info) => {
let button = Button::try_from(gui_handler, button_info)?;
GuiBuilder::insert_id(ids, &button_info.id, &button)?;
grid.attach(
world,
button.clone(),
button_info.x_slot.get()?,
button_info.y_slot.get()?,
button_info.x_dim,
button_info.y_dim,
)?;
neighbour_infos.push((button_info.id.clone(), button_info.neighbour_infos.clone()));
}
UiInfoElement::Label(label_info) => {
let label = Label::try_from(label_info, gui_handler)?;
GuiBuilder::insert_id(ids, &label_info.id, &label)?;
grid.attach(
world,
label,
label_info.x_slot.get()?,
label_info.y_slot.get()?,
label_info.x_dim,
label_info.y_dim,
)?;
}
UiInfoElement::MultiLineLabel(multi_line_label_info) => {
let multi_line_label =
MultiLineLabel::try_from(multi_line_label_info, world, gui_handler)?;
GuiBuilder::insert_id(ids, &multi_line_label_info.id, &multi_line_label)?;
grid.attach(
world,
multi_line_label,
multi_line_label_info.x_slot.get()?,
multi_line_label_info.y_slot.get()?,
multi_line_label_info.x_dim,
multi_line_label_info.y_dim,
)?;
}
UiInfoElement::MultiLineTextField(multi_line_text_field_info) => {
let multi_line_text_field =
MultiLineTextField::try_from(multi_line_text_field_info, world, gui_handler)?;
GuiBuilder::insert_id(ids, &multi_line_text_field_info.id, &multi_line_text_field)?;
grid.attach(
world,
multi_line_text_field,
multi_line_text_field_info.x_slot.get()?,
multi_line_text_field_info.y_slot.get()?,
multi_line_text_field_info.x_dim,
multi_line_text_field_info.y_dim,
)?;
}
UiInfoElement::TextField(text_field_info) => {
let text_field = TextField::try_from(text_field_info, gui_handler)?;
GuiBuilder::insert_id(ids, &text_field_info.id, &text_field)?;
grid.attach(
world,
text_field,
text_field_info.x_slot.get()?,
text_field_info.y_slot.get()?,
text_field_info.x_dim,
text_field_info.y_dim,
)?;
}
UiInfoElement::Icon(icon_info) => {
let icon = Icon::try_from(icon_info, gui_handler)?;
GuiBuilder::insert_id(ids, &icon_info.id, UiElement::Icon(Arc::downgrade(&icon)))?;
grid.attach(
world,
icon,
icon_info.x_slot.get()?,
icon_info.y_slot.get()?,
icon_info.x_dim,
icon_info.y_dim,
)?;
}
UiInfoElement::ProgressBar(progress_bar_info) => {
let progress_bar = ProgressBar::try_from(progress_bar_info, gui_handler)?;
GuiBuilder::insert_id(ids, &progress_bar_info.id, &progress_bar)?;
grid.attach(
world,
progress_bar,
progress_bar_info.x_slot.get()?,
progress_bar_info.y_slot.get()?,
progress_bar_info.x_dim,
progress_bar_info.y_dim,
)?;
}
UiInfoElement::Grid(grid_info) => {
let sub_grid = Grid::try_from(gui_handler, grid_info, false)?;
GuiBuilder::insert_id(ids, &grid_info.id, &sub_grid)?;
grid.attach(
world,
sub_grid.clone(),
grid_info.x_slot.get()?,
grid_info.y_slot.get()?,
grid_info.x_dim,
grid_info.y_dim,
)?;
for child in grid_info.children.read().unwrap().iter() {
Self::create_child(world, gui_handler, child, &sub_grid, ids, neighbour_infos)?;
}
}
}
Ok(())
}
}