ui/src/builder/builder.rs

609 lines
18 KiB
Rust

use crate::prelude::*;
use anyhow::{bail, Context, Result};
use assetpath::AssetPath;
use super::validator::buttoninfo::{NeighbourDirection, NeighbourInfo};
use super::validator::gridinfo::GridInfo;
use super::validator::uiinfoelement::UiInfoElement;
use super::validator::validator::{handle_function_suffix, Validator};
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
pub struct GuiBuilder {
grids: Vec<Arc<Grid>>,
default_select: Option<Arc<Button>>,
ids: HashMap<String, UiElement>,
decline_callback: RwLock<Option<Box<dyn Fn() -> Result<()> + Send + Sync>>>,
}
impl GuiBuilder {
pub fn new(gui_handler: &Arc<GuiHandler>, path: &AssetPath) -> Result<Arc<Self>> {
let validator = Validator::new(
#[cfg(feature = "audio")]
gui_handler,
path,
)
.with_context(|| format!("validator for {}", path.full_path()))?;
Ok(Arc::new(Self::_new(gui_handler, validator).with_context(
|| format!("for file {}", path.full_path()),
)?))
}
pub fn from_str(gui_handler: &Arc<GuiHandler>, s: &str) -> Result<Arc<Self>> {
let validator = Validator::from_str(
#[cfg(feature = "audio")]
gui_handler,
s,
)?;
Ok(Arc::new(Self::_new(gui_handler, validator)?))
}
pub fn merge<'a>(
gui_handler: &Arc<GuiHandler>,
pathes: impl IntoIterator<Item = &'a AssetPath>,
) -> Result<Arc<Self>> {
let mut me = Self {
grids: Default::default(),
default_select: Default::default(),
ids: Default::default(),
decline_callback: Default::default(),
};
for path in pathes.into_iter() {
let validator = Validator::new(
#[cfg(feature = "audio")]
gui_handler,
path,
)?;
let builder = Self::_new(gui_handler, validator)
.with_context(|| format!("for file {}", path.full_path()))?;
me.grids.extend(builder.grids);
if me.default_select.is_some() && builder.default_select.is_some() {
bail!("multiple default selects are not supported!");
}
if builder.default_select.is_some() {
me.default_select = builder.default_select;
}
me.ids.extend(builder.ids);
}
Ok(Arc::new(me))
}
#[inline]
fn _new(gui_handler: &Arc<GuiHandler>, validator: Validator) -> Result<Self> {
let root = validator.root();
let mut ids = HashMap::new();
let mut opt_default_select = None;
let mut custom_neighbours = Vec::new();
let mut grids = Vec::new();
for child in &root.children {
let tree = Self::create_tree(
gui_handler,
&mut ids,
child,
&mut opt_default_select,
&mut custom_neighbours,
validator.dimensions(),
validator.layer().unwrap_or(5),
)?;
grids.push(tree);
}
Self::connect_custom_neighbours(&ids, custom_neighbours)?;
Ok(GuiBuilder {
grids,
default_select: opt_default_select,
ids,
decline_callback: RwLock::new(None),
})
}
pub(crate) fn connect_custom_neighbours(
ids: &HashMap<String, UiElement>,
neighbour_infos: Vec<(String, Vec<NeighbourInfo>)>,
) -> Result<()> {
for (id, neighbour_infos) in neighbour_infos.iter() {
let button: Arc<Button> = ids.element(id)?;
for neighbour_info in neighbour_infos.iter() {
let neighbour: Arc<Button> = ids.element(&neighbour_info.id)?;
let neighbour_selectable = neighbour.selectable();
button
.selectable()
.map(|selectable| match neighbour_info.direction {
NeighbourDirection::East => {
selectable.set_east_neighbour(neighbour_selectable)
}
NeighbourDirection::West => {
selectable.set_west_neighbour(neighbour_selectable)
}
NeighbourDirection::North => {
selectable.set_north_neighbour(neighbour_selectable)
}
NeighbourDirection::South => {
selectable.set_south_neighbour(neighbour_selectable)
}
});
}
}
Ok(())
}
pub fn set_decline_callback<F>(&self, decline_callback: F)
where
F: Fn() -> Result<()> + 'static + Send + Sync,
{
*self.decline_callback.write().unwrap() = Some(Box::new(decline_callback));
}
pub fn set_ui_layer(&self, layer: i32) -> Result<()> {
for grid in self.grids.iter() {
grid.set_layer(layer)?;
}
Ok(())
}
pub fn ids(&self) -> Vec<&str> {
self.ids.keys().map(|n| n.as_str()).collect()
}
}
// --- traits ---
impl Functionality for GuiBuilder {
fn set_click_callbacks(
&self,
functions: Vec<(&str, Box<dyn Fn() -> 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(&dyn Any) -> Result<()> + Send + Sync>)>,
) -> Result<()> {
Ok(())
}
fn set_select_callbacks(
&self,
callbacks: Vec<(&str, Box<CustomCallback<bool, ()>>)>,
) -> 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<CustomCallback<ControllerButton, bool>>)>,
) -> 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 Visibility for GuiBuilder {
fn visible(&self) -> bool {
// assume that all grids are visible when the first one is visible
self.grids
.get(0)
.map(|grid| grid.visible())
.unwrap_or(false)
}
fn set_visibility(&self, visibility: bool) -> Result<()> {
for grid in &self.grids {
grid.set_visibility(visibility)?;
}
Ok(())
}
}
impl GuiElementTraits for GuiBuilder {
fn gridable(&self) -> Option<&dyn Gridable> {
None
}
fn visibility(&self) -> Option<&dyn Visibility> {
Some(self)
}
fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> {
Some(GuiElement::GuiBuilder(self))
}
}
impl TopGui for GuiBuilder {
fn decline(&self) -> Result<()> {
if let Some(decline_callback) = self.decline_callback.read().unwrap().as_ref() {
decline_callback()?;
}
Ok(())
}
fn next_tab(&self, _: bool) -> Result<()> {
Ok(())
}
fn previous_tab(&self, _: bool) -> Result<()> {
Ok(())
}
}
impl TopLevelGui for GuiBuilder {
fn gui_traits(&self) -> &dyn GuiElementTraits {
self
}
fn top_gui(&self) -> Option<&dyn TopGui> {
Some(self)
}
fn elements(&self) -> Option<&HashMap<String, UiElement>> {
Some(&self.ids)
}
fn functionality(&self) -> Option<&dyn Functionality> {
Some(self)
}
fn enable(&self) -> Result<()> {
self.set_visibility(true)?;
if let Some(button) = &self.default_select {
Button::select(button)?;
}
Ok(())
}
fn disable(&self) -> Result<()> {
self.set_visibility(false)?;
Ok(())
}
}
macro_rules! impl_element {
($data_type:ident $(< $($lt:lifetime),+ >)? ) => {
impl GetElement<$data_type$(<$($lt,)+>)?> for GuiBuilder {
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);
// private
impl GuiBuilder {
fn create_tree(
gui_handler: &Arc<GuiHandler>,
ids: &mut HashMap<String, UiElement>,
root_grid_info: &Arc<GridInfo>,
default_button: &mut Option<Arc<Button>>,
neighbour_infos: &mut Vec<(String, Vec<NeighbourInfo>)>,
(reference_width, reference_height): (Option<u32>, Option<u32>),
layer: i32,
) -> Result<Arc<Grid>> {
let root_grid = Grid::try_from(root_grid_info, gui_handler, true)?;
let root_layer = root_grid_info.layer.unwrap_or(layer);
root_grid.set_layer(root_layer)?;
if let Some(ref_width) = reference_width {
if let Some(ref_height) = reference_height {
root_grid
.framable
.set_reference_size(ref_width, ref_height)?;
}
}
root_grid.set_frame(
root_grid_info
.x_offset
.get()
.with_context(|| "x_offset of root grid")?,
root_grid_info
.y_offset
.get()
.with_context(|| "y_offset of root grid")?,
root_grid_info
.width
.get()
.with_context(|| "width of root grid")?,
root_grid_info
.height
.get()
.with_context(|| "height of root grid")?,
root_grid_info
.vertical_alignment
.get()
.with_context(|| "vertical alignment of root grid")?,
root_grid_info
.horizontal_alignment
.get()
.with_context(|| "horizontal alignment of root grid")?,
)?;
Self::insert_id(ids, &root_grid_info.id, &root_grid)?;
for child in root_grid_info.children.read().unwrap().iter() {
Self::create_child(
gui_handler,
child,
&root_grid,
ids,
default_button,
neighbour_infos,
root_layer,
)?;
}
Ok(root_grid)
}
fn create_child(
gui_handler: &Arc<GuiHandler>,
child: &UiInfoElement,
grid: &Grid,
ids: &mut HashMap<String, UiElement>,
default_button: &mut Option<Arc<Button>>,
neighbour_infos: &mut Vec<(String, Vec<NeighbourInfo>)>,
layer: i32,
) -> Result<()> {
match child {
UiInfoElement::Button(button_info) => {
let button = Button::try_from(button_info, gui_handler)?;
Self::insert_id(ids, &button_info.id, &button)?;
grid.attach(
button.clone(),
button_info
.x_slot
.get()
.with_context(|| "x_slot of button")?,
button_info
.y_slot
.get()
.with_context(|| "y_slot of button")?,
button_info.x_dim,
button_info.y_dim,
)?;
button.set_layer(layer)?;
neighbour_infos.push((button_info.id.clone(), button_info.neighbour_infos.clone()));
if button_info.select {
match default_button {
Some(_) => {
return Err(anyhow::Error::msg(
"It is not allowed to select multiple UI elements",
))
}
None => *default_button = Some(button.clone()),
}
}
}
UiInfoElement::Label(label_info) => {
let label = Label::try_from(label_info, gui_handler)?;
Self::insert_id(ids, &label_info.id, &label)?;
grid.attach(
label.clone(),
label_info.x_slot.get().with_context(|| "x_slot of label")?,
label_info.y_slot.get().with_context(|| "y_slot of label")?,
label_info.x_dim,
label_info.y_dim,
)?;
label.set_layer(layer)?;
}
UiInfoElement::MultiLineLabel(multi_line_label_info) => {
let multi_line_label =
MultiLineLabel::try_from(multi_line_label_info, gui_handler)?;
GuiBuilder::insert_id(ids, &multi_line_label_info.id, &multi_line_label)?;
grid.attach(
multi_line_label.clone(),
multi_line_label_info
.x_slot
.get()
.with_context(|| "x_slot of label")?,
multi_line_label_info
.y_slot
.get()
.with_context(|| "y_slot of label")?,
multi_line_label_info.x_dim,
multi_line_label_info.y_dim,
)?;
multi_line_label.set_layer(layer)?;
}
UiInfoElement::MultiLineTextField(multi_line_text_field_info) => {
let multi_line_text_field =
MultiLineTextField::try_from(multi_line_text_field_info, gui_handler)?;
GuiBuilder::insert_id(ids, &multi_line_text_field_info.id, &multi_line_text_field)?;
grid.attach(
multi_line_text_field.clone(),
multi_line_text_field_info
.x_slot
.get()
.with_context(|| "x_slot of text field")?,
multi_line_text_field_info
.y_slot
.get()
.with_context(|| "y_slot of text field")?,
multi_line_text_field_info.x_dim,
multi_line_text_field_info.y_dim,
)?;
multi_line_text_field.set_layer(layer)?;
}
UiInfoElement::TextField(text_field_info) => {
let text_field = TextField::try_from(text_field_info, gui_handler)?;
Self::insert_id(ids, &text_field_info.id, &text_field)?;
grid.attach(
text_field.clone(),
text_field_info
.x_slot
.get()
.with_context(|| "x_slot of text field")?,
text_field_info
.y_slot
.get()
.with_context(|| "y_slot of text field")?,
text_field_info.x_dim,
text_field_info.y_dim,
)?;
text_field.set_layer(layer)?;
}
UiInfoElement::Icon(icon_info) => {
let icon = Icon::try_from(icon_info, gui_handler)?;
Self::insert_id(ids, &icon_info.id, UiElement::Icon(Arc::downgrade(&icon)))?;
grid.attach(
icon.clone(),
icon_info.x_slot.get().with_context(|| "x_slot of icon")?,
icon_info.y_slot.get().with_context(|| "y_slot of icon")?,
icon_info.x_dim,
icon_info.y_dim,
)?;
icon.set_layer(layer)?;
}
UiInfoElement::ProgressBar(progress_bar_info) => {
let progress_bar = ProgressBar::try_from(progress_bar_info, gui_handler)?;
Self::insert_id(ids, &progress_bar_info.id, &progress_bar)?;
grid.attach(
progress_bar.clone(),
progress_bar_info
.x_slot
.get()
.with_context(|| "x_slot of progress bar")?,
progress_bar_info
.y_slot
.get()
.with_context(|| "y_slot of progress bar")?,
progress_bar_info.x_dim,
progress_bar_info.y_dim,
)?;
progress_bar.set_layer(layer)?;
}
UiInfoElement::Grid(grid_info) => {
let sub_grid = Grid::try_from(grid_info, gui_handler, false)?;
Self::insert_id(ids, &grid_info.id, &sub_grid)?;
grid.attach(
sub_grid.clone(),
grid_info.x_slot.get().with_context(|| "x_slot of grid")?,
grid_info.y_slot.get().with_context(|| "y_slot of grid")?,
grid_info.x_dim,
grid_info.y_dim,
)?;
let sub_grid_layer = grid_info.layer.unwrap_or(layer);
sub_grid.set_layer(sub_grid_layer)?;
for child in grid_info.children.read().unwrap().iter() {
Self::create_child(
gui_handler,
child,
&sub_grid,
ids,
default_button,
neighbour_infos,
sub_grid_layer,
)?;
}
}
}
Ok(())
}
pub(crate) fn insert_id(
ids: &mut HashMap<String, UiElement>,
id: &String,
element: impl Into<UiElement>,
) -> Result<()> {
if !id.is_empty() {
if let Some(_) = ids.insert(id.clone(), element.into()) {
return Err(anyhow::Error::msg(format!(
"ID ({}) is used multiple times",
id,
)));
}
}
Ok(())
}
}