489 lines
14 KiB
Rust
489 lines
14 KiB
Rust
|
use crate::prelude::*;
|
||
|
use anyhow::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(gui_handler, path)?;
|
||
|
|
||
|
Self::_new(gui_handler, validator)
|
||
|
}
|
||
|
|
||
|
pub fn from_str(gui_handler: &Arc<GuiHandler>, s: &str) -> Result<Arc<Self>> {
|
||
|
let validator = Validator::from_str(gui_handler, s)?;
|
||
|
|
||
|
Self::_new(gui_handler, validator)
|
||
|
}
|
||
|
|
||
|
#[inline]
|
||
|
fn _new(gui_handler: &Arc<GuiHandler>, validator: Validator) -> Result<Arc<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(),
|
||
|
)?;
|
||
|
|
||
|
if let Some(layer) = validator.layer() {
|
||
|
tree.set_layer(layer)?;
|
||
|
}
|
||
|
|
||
|
grids.push(tree);
|
||
|
}
|
||
|
|
||
|
Self::connect_custom_neighbours(&ids, custom_neighbours)?;
|
||
|
|
||
|
Ok(Arc::new(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(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// --- 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>),
|
||
|
) -> Result<Arc<Grid>> {
|
||
|
// let mut ids = HashMap::new();
|
||
|
|
||
|
let root_grid = Grid::try_from(root_grid_info, gui_handler, true)?;
|
||
|
|
||
|
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()?,
|
||
|
root_grid_info.y_offset.get()?,
|
||
|
root_grid_info.width.get()?,
|
||
|
root_grid_info.height.get()?,
|
||
|
root_grid_info.vertical_alignment.get()?,
|
||
|
root_grid_info.horizontal_alignment.get()?,
|
||
|
)?;
|
||
|
|
||
|
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,
|
||
|
)?;
|
||
|
}
|
||
|
|
||
|
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>)>,
|
||
|
) -> 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()?,
|
||
|
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()));
|
||
|
|
||
|
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,
|
||
|
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, gui_handler)?;
|
||
|
|
||
|
GuiBuilder::insert_id(ids, &multi_line_label_info.id, &multi_line_label)?;
|
||
|
|
||
|
grid.attach(
|
||
|
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, gui_handler)?;
|
||
|
|
||
|
GuiBuilder::insert_id(ids, &multi_line_text_field_info.id, &multi_line_text_field)?;
|
||
|
|
||
|
grid.attach(
|
||
|
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)?;
|
||
|
|
||
|
Self::insert_id(ids, &text_field_info.id, &text_field)?;
|
||
|
|
||
|
grid.attach(
|
||
|
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)?;
|
||
|
|
||
|
Self::insert_id(ids, &icon_info.id, UiElement::Icon(Arc::downgrade(&icon)))?;
|
||
|
|
||
|
grid.attach(
|
||
|
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)?;
|
||
|
|
||
|
Self::insert_id(ids, &progress_bar_info.id, &progress_bar)?;
|
||
|
|
||
|
grid.attach(
|
||
|
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(grid_info, gui_handler, false)?;
|
||
|
|
||
|
Self::insert_id(ids, &grid_info.id, &sub_grid)?;
|
||
|
|
||
|
grid.attach(
|
||
|
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(
|
||
|
gui_handler,
|
||
|
child,
|
||
|
&sub_grid,
|
||
|
ids,
|
||
|
default_button,
|
||
|
neighbour_infos,
|
||
|
)?;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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(())
|
||
|
}
|
||
|
}
|