use crate::prelude::*; use anyhow::Result; use ecs::World; use crate::builder::validator::gridinfo::GridInfo; use std::sync::{ Arc, RwLock, RwLockReadGuard, atomic::{AtomicBool, AtomicU32, Ordering::SeqCst}, }; use std::{ops::Deref, sync::Weak}; use super::fill_type::FillType; pub enum ConnectDirection { East, West, South, North, } #[derive(Clone)] enum ChildState { Some { child: Arc, x: u32, y: u32, }, Extend { weak_child: Weak, x: u32, y: u32, }, None, } impl ChildState { fn downgrade(&self) -> Self { match self { Self::Some { child, x, y } => Self::Extend { weak_child: Arc::downgrade(child), x: *x, y: *y, }, _ => panic!(), } } fn is_some(&self) -> bool { match self { ChildState::Some { .. } => true, ChildState::Extend { .. } => false, ChildState::None => false, } } fn is_extend(&self) -> bool { match self { ChildState::Some { .. } => false, ChildState::Extend { .. } => true, ChildState::None => false, } } fn get_some(&self) -> Option<(&Arc, u32, u32)> { match self { ChildState::Some { child, x, y } => Some((child, *x, *y)), ChildState::Extend { .. } => None, ChildState::None => None, } } fn get(&self) -> Option<(Arc, u32, u32)> { match self { ChildState::Some { child, x, y } => Some((child.clone(), *x, *y)), ChildState::Extend { weak_child, x, y } => { weak_child.upgrade().map(|child| (child, *x, *y)) } ChildState::None => None, } } fn map(&self, f: C) -> Option where C: FnOnce(&Arc) -> R, { match self { ChildState::Some { child, .. } => Some(f(child)), ChildState::Extend { weak_child, .. } => weak_child.upgrade().map(|child| f(&child)), ChildState::None => None, } } } pub struct Grid { pub(crate) framable: Arc, background: RwLock>, children: RwLock>>, dim_x: usize, dim_y: usize, padding: AtomicU32, margin: AtomicU32, visible: AtomicBool, } impl Grid { pub fn new( gui_handler: &GuiHandler, dim_x: usize, dim_y: usize, top_level: bool, ) -> Result> { let grid = Arc::new(Grid { framable: Framable::new(gui_handler.width(), gui_handler.height(), top_level)?, background: RwLock::new(None), children: RwLock::new(vec![vec![ChildState::None; dim_y]; dim_x]), dim_x, dim_y, padding: AtomicU32::new(0), margin: AtomicU32::new(10), visible: AtomicBool::new(false), }); // if top_level { // let weak_grid = Arc::downgrade(&grid); // grid.framable.add_callback( // weak_grid.clone(), // Box::new(move |gui_handler| { // if let Some(grid) = weak_grid.upgrade() { // grid.calculate_child_positions(gui_handler)?; // } // Ok(()) // }), // ); // } Ok(grid) } pub fn dimensions(&self) -> (usize, usize) { (self.dim_x, self.dim_y) } pub fn set_background( &self, gui_handler: &mut GuiHandler, background: impl Into, ) -> Result<()> { super::set_background( gui_handler, self.visible(), &self.framable, &self.background, background, ) } pub(crate) fn background(&self) -> RwLockReadGuard<'_, Option> { self.background.read().unwrap() } pub fn set_margin(&self, margin: u32) { self.margin.store(margin, SeqCst); } pub fn set_padding(&self, padding: u32) { self.padding.store(padding, SeqCst); } pub fn child_at(&self, x: usize, y: usize) -> Result>> { if x >= self.dim_x { return Err(anyhow::anyhow!( "Tried to access Grid at {} while only being {} wide", x, self.dim_x )); } if y >= self.dim_y { return Err(anyhow::anyhow!( "Tried to access Grid at {} while only being {} tall", y, self.dim_y )); } Ok(self.children.read().unwrap()[x][y].map(|c| c.clone())) } pub fn detach( &self, world: &mut World, pos_x: usize, pos_y: usize, ) -> Result>> { if cfg!(debug_assertions) { if pos_x >= self.dim_x as usize { panic!( "x position ({}) must not be bigger than grid x-dimension ({})", pos_x, self.dim_x ); } if pos_y >= self.dim_y as usize { panic!( "y position ({}) must not be bigger than grid y-dimension ({})", pos_y, self.dim_y ); } } let current = self.children.write().unwrap()[pos_x][pos_y].clone(); if current.is_extend() { todo!(); } self.children.write().unwrap()[pos_x][pos_y] = ChildState::None; match current.get() { Some((child, _x, _y)) => { if self.visible() { if let Some(child_visibility) = child.visibility() { child_visibility.set_visibility(world, false)?; } } Ok(Some(child)) } None => Ok(None), } } /// Returns `true` if item got detached pub fn detach_item(&self, world: &mut World, item: Arc) -> Result { let mut grid = self.children.write().unwrap(); let mut removed = false; 'outer: for column in grid.iter_mut() { for child_opt in column.iter_mut() { let mut remove = false; if let Some((child, _x, _y)) = child_opt.get() { // ugly workaround because Arc::ptr_eq does not work (7th, April 2020) let item_ptr = Arc::into_raw(item.clone()); let child_ptr = Arc::into_raw(child.clone()); if std::ptr::eq(item_ptr, child_ptr) { remove = true; if self.visible() { if let Some(child_visibility) = child.visibility() { child_visibility.set_visibility(world, false)?; } } } unsafe { Arc::from_raw(item_ptr); Arc::from_raw(child_ptr); } } if remove { *child_opt = ChildState::None; removed = true; break 'outer; } } } Ok(removed) } pub fn set_position(&self, gui_handler: &mut GuiHandler, x: i32, y: i32) -> Result<()> { // update own position self.framable.change_position(gui_handler, x, y)?; if let Some(background) = self.background.read().unwrap().as_ref() { background.update_frame(gui_handler)?; } self.calculate_child_positions(gui_handler)?; Ok(()) } fn calculate_child_positions(&self, gui_handler: &mut GuiHandler) -> Result<()> { // recalculate positions of the children let children = self.children.read().unwrap(); for (x, row) in children.iter().enumerate() { for (y, child_opt) in row.iter().enumerate() { if let Some((child, dim_x, dim_y)) = child_opt.get_some() { if let Some(child_gridable) = child.gridable() { self.child_position(gui_handler, child_gridable, x, y, dim_x, dim_y)?; } } } } Ok(()) } pub fn position(&self) -> (i32, i32) { (self.framable.left(), self.framable.top()) } pub fn extents(&self) -> (u32, u32) { let width = (self.framable.right() as i32 - self.framable.left()) as u32; let height = (self.framable.bottom() as i32 - self.framable.top()) as u32; (width, height) } pub fn disallow_position_scale(&self, gui_handler: &mut GuiHandler) -> Result<()> { self.framable.allow_position_scale(gui_handler, false) } pub fn disallow_size_scale(&self, gui_handler: &mut GuiHandler) -> Result<()> { self.framable.allow_size_scale(gui_handler, false) } pub fn connect<'a>( &self, direction: ConnectDirection, elements: impl IntoIterator)>, ) -> Result<()> { for (index, selectable) in elements { match direction { ConnectDirection::East => { let y = index; for x in (0..self.dim_x).rev() { if let Some(child) = self.child_at(x, y)? { if let Some(gridable) = child.gridable() { if let Some(grid_selectable) = gridable.selectable() { Selectable::connect_horizontally(grid_selectable, selectable); break; } } } } } ConnectDirection::West => { let y = index; for x in 0..self.dim_x { if let Some(child) = self.child_at(x, y)? { if let Some(gridable) = child.gridable() { if let Some(grid_selectable) = gridable.selectable() { Selectable::connect_horizontally(selectable, grid_selectable); break; } } } } } ConnectDirection::South => { let x = index; for y in (0..self.dim_y).rev() { if let Some(child) = self.child_at(x, y)? { if let Some(gridable) = child.gridable() { if let Some(grid_selectable) = gridable.selectable() { Selectable::connect_vertically(grid_selectable, selectable); break; } } } } } ConnectDirection::North => { let x = index; for y in (0..self.dim_y).rev() { if let Some(child) = self.child_at(x, y)? { if let Some(gridable) = child.gridable() { if let Some(grid_selectable) = gridable.selectable() { Selectable::connect_vertically(grid_selectable, selectable); break; } } } } } } } Ok(()) } pub fn attach( &self, world: &mut World, child: Arc, pos_x: usize, pos_y: usize, dim_x: u32, dim_y: u32, ) -> Result<()> { if pos_x >= self.dim_x as usize { panic!( "x postion ({}) must not be bigger than grid x-dimension ({})", pos_x, self.dim_x ); } if pos_y >= self.dim_y as usize { panic!( "y postion ({}) must not be bigger than grid y-dimension ({})", pos_y, self.dim_y ); } assert_ne!(dim_x, 0); assert_ne!(dim_y, 0); // get grid let mut grid = self.children.write().unwrap(); if let Some(child_gridable) = child.gridable() { // check for neighbourships if let Some(current_selectable) = child_gridable.selectable() { Self::set_neighbours( &*grid, current_selectable, (pos_x, pos_y), (self.dim_x, self.dim_y), )?; } if self.framable.is_framed() { self.child_position( world.resources.get_mut::(), child_gridable, pos_x, pos_y, dim_x, dim_y, )?; } child_gridable.set_layer(self.framable.ui_layer())?; } // disable the old element match &grid[pos_x][pos_y] { ChildState::Some { child, .. } => { if self.visible() { if let Some(child_visibility) = child.visibility() { child_visibility.set_visibility(world, false)?; } } } ChildState::Extend { .. } => todo!(), ChildState::None => (), } if self.visible() { if let Some(child_visibility) = child.visibility() { child_visibility.set_visibility(world, true)?; } } let child_state = ChildState::Some { child, x: dim_x, y: dim_y, }; let weak = child_state.downgrade(); // insert the element for x in pos_x..(pos_x + dim_x as usize) { for y in pos_y..(pos_y + dim_y as usize) { grid[x][y] = weak.clone(); } } grid[pos_x][pos_y] = child_state; Ok(()) } pub fn has_attachment_at(&self, x: usize, y: usize) -> Result { let children = self.children.read().unwrap(); Ok(children[x][y].is_some()) } pub fn try_from( gui_handler: &mut GuiHandler, grid_info: &GridInfo, top_level: bool, ) -> Result> { let grid = Grid::new( gui_handler, grid_info.x_dimension.get()?, grid_info.y_dimension.get()?, top_level, )?; grid.set_margin(grid_info.margin); grid.set_padding(grid_info.padding); if let Some(background) = &grid_info.background_type { grid.set_background(gui_handler, background.clone())?; } Ok(grid) } pub fn change_position_unscaled( &self, gui_handler: &mut GuiHandler, x: i32, y: i32, ) -> Result<()> { self.framable.change_position_unscaled(gui_handler, x, y)?; if let Some(background) = self.background.read().unwrap().as_ref() { background.update_frame(gui_handler)?; } self.calculate_child_positions(gui_handler)?; Ok(()) } fn child_position( &self, gui_handler: &mut GuiHandler, child: &dyn Gridable, pos_x: usize, pos_y: usize, dim_x: u32, dim_y: u32, ) -> Result<()> { // calculate the extents for the new child let single_width = ((self.framable.right() as i32 - self.framable.left()) - (2 * self.padding.load(SeqCst) as i32 + (self.dim_x as i32 - 1) * self.margin.load(SeqCst) as i32)) / self.dim_x as i32; let single_height = ((self.framable.bottom() as i32 - self.framable.top()) - (2 * self.padding.load(SeqCst) as i32 + (self.dim_y as i32 - 1) * self.margin.load(SeqCst) as i32)) / self.dim_y as i32; let child_width = if dim_x == 1 { single_width } else { single_width + (dim_x - 1) as i32 * (single_width + self.margin.load(SeqCst) as i32) }; let child_height = if dim_y == 1 { single_height } else { single_height + (dim_y - 1) as i32 * (single_height + self.margin.load(SeqCst) as i32) }; let win_width = gui_handler.width(); let win_height = gui_handler.height(); let y_align = match self.framable.vertical_alignment() { VerticalAlign::Top => 0, VerticalAlign::Middle => win_height / 2, VerticalAlign::Bottom => win_height, VerticalAlign::NotSet => panic!("grid: vertical alignment is not set"), }; let x_align = match self.framable.horizontal_alignment() { HorizontalAlign::Left => 0, HorizontalAlign::Middle => win_width / 2, HorizontalAlign::Right => win_width, HorizontalAlign::NotSet => panic!("grid: horizontal alignment is not set"), }; let child_x = self.framable.left() + self.padding.load(SeqCst) as i32 + pos_x as i32 * (single_width + self.margin.load(SeqCst) as i32); let child_y = self.framable.top() + self.padding.load(SeqCst) as i32 + pos_y as i32 * (single_height + self.margin.load(SeqCst) as i32); child.set_frame( gui_handler, child_x - x_align as i32, child_y - y_align as i32, child_width as u32, child_height as u32, self.framable.vertical_alignment(), self.framable.horizontal_alignment(), )?; Ok(()) } pub fn select(&self, gui_handler: &mut GuiHandler, x: u32, y: u32) -> Result<()> { match self.children.read().unwrap()[x as usize][y as usize].get_some() { Some((child, ..)) => match child.gridable() { Some(gridable) => match gridable.selectable() { Some(selectable) => { selectable.select(gui_handler)?; Ok(()) } None => panic!("gridable at position ({}, {}), is not selectable", x, y), }, None => panic!("element at position ({}, {}) is not a gridable", x, y), }, None => panic!("no element at position ({}, {})", x, y), } } fn disable_tree(&self, world: &mut World, gui_handler: &mut GuiHandler) -> Result<()> { self.framable.delete(gui_handler)?; if let Some(background) = self.background.read().unwrap().as_ref() { background.disable(gui_handler)?; } self.set_tree_visibility(world, false)?; Ok(()) } fn set_tree_visibility(&self, world: &mut World, visible: bool) -> Result<()> { let tree = self.children.read().unwrap(); for row in tree.deref() { for child_state in row { if let Some((child, ..)) = child_state.get_some() { if let Some(visibility) = child.visibility() { visibility.set_visibility(world, visible)?; } } } } Ok(()) } #[inline] fn set_neighbours( grid: &Vec>, current_child: &Arc, pos: (usize, usize), dim: (usize, usize), ) -> Result<()> { // set east neighbour Self::set_east_neighbour(grid, current_child, pos, dim)?; // set west neighbour Self::set_west_neighbour(grid, current_child, pos, dim)?; // set north neighbour Self::set_north_neighbour(grid, current_child, pos, dim)?; // set south neighbour Self::set_south_neighbour(grid, current_child, pos, dim)?; Ok(()) } #[inline] fn set_north_neighbour( grid: &Vec>, current_child: &Arc, pos: (usize, usize), dim: (usize, usize), ) -> Result { match Self::search_neighbour_in_direction(grid, pos, (0, 1), dim) { Some(neighbour) => { current_child.set_south_neighbour(Some(&neighbour)); neighbour.set_north_neighbour(Some(current_child)); Ok(true) } None => Ok(false), } } #[inline] fn set_south_neighbour( grid: &Vec>, current_child: &Arc, pos: (usize, usize), dim: (usize, usize), ) -> Result { match Self::search_neighbour_in_direction(grid, pos, (0, -1), dim) { Some(neighbour) => { current_child.set_north_neighbour(Some(&neighbour)); neighbour.set_south_neighbour(Some(current_child)); Ok(true) } None => Ok(false), } } #[inline] fn set_east_neighbour( grid: &Vec>, current_child: &Arc, pos: (usize, usize), dim: (usize, usize), ) -> Result { match Self::search_neighbour_in_direction(grid, pos, (1, 0), dim) { Some(neighbour) => { current_child.set_east_neighbour(Some(&neighbour)); neighbour.set_west_neighbour(Some(current_child)); Ok(true) } None => Ok(false), } } #[inline] fn set_west_neighbour( grid: &Vec>, current_child: &Arc, pos: (usize, usize), dim: (usize, usize), ) -> Result { match Self::search_neighbour_in_direction(grid, pos, (-1, 0), dim) { Some(neighbour) => { current_child.set_west_neighbour(Some(&neighbour)); neighbour.set_east_neighbour(Some(current_child)); Ok(true) } None => Ok(false), } } #[inline] fn search_neighbour_in_direction( grid: &Vec>, pos: (usize, usize), dir: (i32, i32), dim: (usize, usize), ) -> Option> { let (mut x, mut y) = pos; let (x_step, y_step) = dir; let (dim_x, dim_y) = dim; while ((x as i32 + x_step) < dim_x as i32 && (x as i32 + x_step) >= 0) && ((y as i32 + y_step) < dim_y as i32 && (y as i32 + y_step) >= 0) { x = (x as i32 + x_step) as usize; y = (y as i32 + y_step) as usize; if let Some((neighbour, ..)) = grid[x][y].get() { if let Some(gridable) = neighbour.gridable() { if let Some(selectable) = gridable.selectable() { return Some(selectable.clone()); } } } } None } } impl GuiElementTraits for Grid { fn gridable(&self) -> Option<&dyn Gridable> { Some(self) } fn visibility(&self) -> Option<&dyn Visibility> { Some(self) } fn downcast<'a>(&'a self) -> Option> { Some(GuiElement::Grid(self)) } } impl Visibility for Grid { fn visible(&self) -> bool { self.visible.load(SeqCst) } fn set_visibility(&self, world: &mut World, visibility: bool) -> Result<()> { if visibility != self.visible.load(SeqCst) { let gui_handler = world.resources.get_mut_unchecked::(); self.visible.store(visibility, SeqCst); if visibility { self.framable.add(gui_handler)?; if let Some(background) = self.background.read().unwrap().as_ref() { background.enable(gui_handler)?; } self.set_tree_visibility(world, true)?; } else { self.disable_tree(world, gui_handler)?; } } Ok(()) } } impl Gridable for Grid { fn set_frame( &self, gui_handler: &mut GuiHandler, x: i32, y: i32, w: u32, h: u32, vert_align: VerticalAlign, hori_align: HorizontalAlign, ) -> Result<()> { self.framable .set_frame(gui_handler, x, y, w, h, vert_align, hori_align); if let Some(background) = self.background.read().unwrap().as_ref() { background.update_frame(gui_handler)?; } self.calculate_child_positions(gui_handler)?; Ok(()) } fn selectable(&self) -> Option<&Arc> { None } fn type_name(&self) -> &str { "Grid" } fn set_layer(&self, layer: i32) -> Result<()> { self.framable.set_ui_layer(layer); if let Some(background) = self.background.read().unwrap().as_ref() { background.set_ui_layer(layer); } for row in self.children.read().unwrap().iter() { for child_state in row.iter() { if let Some((child, ..)) = child_state.get_some() { if let Some(gridable) = child.gridable() { gridable.set_layer(layer)?; } } } } Ok(()) } fn position_extent(&self) -> (i32, i32, u32, u32) { self.framable.position() } }