mod abilities; mod character; mod content; mod inventory; mod page_content; mod traits; use anyhow::Result; use downcast_rs::{Downcast, impl_downcast}; use engine::prelude::*; use rpg_components::{ components::{ crafting_materials::CraftingMaterials, inventory::{Inventory, Storable}, }, items::ability_book::Ability, }; use std::{ any::Any, collections::HashMap, ops::{Deref, DerefMut}, sync::{ Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, atomic::{AtomicUsize, Ordering::SeqCst}, }, }; use self::{abilities::AbilityPage, character::CharacterPage, inventory::InventoryPage}; trait Page: Any + Send + Sync + Downcast { fn enable(&mut self) -> Result<Arc<Grid>>; fn disable(&mut self) -> Result<()>; fn select(&self) -> Result<()>; fn next_tab(&mut self) -> Result<()>; fn previous_tab(&mut self) -> Result<()>; fn event(&mut self, button: ControllerButton) -> Result<bool>; } impl_downcast!(Page); struct Tab<'a> { index: usize, tabs: RwLockReadGuard<'a, [Box<dyn Page>; 3]>, } impl<'a> Deref for Tab<'a> { type Target = Box<dyn Page>; fn deref(&self) -> &Self::Target { &self.tabs[self.index] } } struct TabMut<'a> { index: usize, tabs: RwLockWriteGuard<'a, [Box<dyn Page>; 3]>, } impl<'a> TabMut<'a> { pub fn downcast_mut<T: Page>(&mut self) -> &mut T { self.tabs[self.index].downcast_mut().unwrap() } } impl<'a> Deref for TabMut<'a> { type Target = Box<dyn Page>; fn deref(&self) -> &Self::Target { &self.tabs[self.index] } } impl<'a> DerefMut for TabMut<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.tabs[self.index] } } pub struct Tabs<'a> { tabs: RwLockReadGuard<'a, [Box<dyn Page>; 3]>, } #[allow(unused)] impl<'a> Tabs<'a> { pub fn character(&mut self) -> &CharacterPage { self.tabs[0].downcast_ref().unwrap() } pub fn inventory<A: Ability + 'static>(&mut self) -> &InventoryPage<A> { self.tabs[1].downcast_ref().unwrap() } pub fn abilities<A: Ability + 'static>(&mut self) -> &AbilityPage<A> { self.tabs[2].downcast_ref().unwrap() } } pub struct TabsMut<'a> { tabs: RwLockWriteGuard<'a, [Box<dyn Page>; 3]>, } #[allow(unused)] impl<'a> TabsMut<'a> { pub fn character(&mut self) -> &mut CharacterPage { self.tabs[0].downcast_mut().unwrap() } pub fn inventory<A: Ability + 'static>(&mut self) -> &mut InventoryPage<A> { self.tabs[1].downcast_mut().unwrap() } pub fn abilities<A: Ability + 'static>(&mut self) -> &mut AbilityPage<A> { self.tabs[2].downcast_mut().unwrap() } } pub struct CharacterWindow { close: Box<dyn FutureStateChange>, menu_gui: Arc<GuiBuilder>, tab_content_grid: Arc<Grid>, tooltips: Mutex<HashMap<String, Arc<GuiBuilder>>>, tabs: RwLock<[Box<dyn Page>; 3]>, tab: AtomicUsize, } impl CharacterWindow { pub fn new<A: Ability + 'static>( world: &mut World, hero: Entity, name: &str, close: Box<dyn FutureStateChange>, ) -> Result<Arc<Self>> { let menu_gui = GuiBuilder::from_str( world.resources.get_mut::<GuiHandler>(), include_str!("../resources/menu.xml"), )?; let content_grid = menu_gui.element("tab_content")?; let open_character_page: Arc<Button> = menu_gui.element("open_statistics")?; let open_inventory_page: Arc<Button> = menu_gui.element("open_inventory")?; let open_ability_page: Arc<Button> = menu_gui.element("open_abilities")?; let close_button: Arc<Button> = menu_gui.element("close")?; let character_window = Arc::new_cyclic(|me| CharacterWindow { close, menu_gui, tab_content_grid: content_grid, tooltips: Mutex::default(), tabs: RwLock::new([ Box::new(CharacterPage::new(world, hero, name, me).unwrap()), Box::new(InventoryPage::<A>::new(world, hero, me.clone(), &close_button).unwrap()), Box::new(AbilityPage::<A>::new(world, hero, me.clone(), &close_button).unwrap()), ]), tab: AtomicUsize::new(0), }); let open_tab = { let weak_me = Arc::downgrade(&character_window); move |gui_handler: &mut GuiHandler, index| { if let Some(me) = weak_me.upgrade() { me.tab.store(index, SeqCst); me.tab_content_grid .attach(gui_handler, me.tab_mut().enable()?, 0, 0, 1, 1)?; me.tab().select()?; } Ok(()) } }; open_character_page.set_callback({ let open_tab = open_tab.clone(); move |world| open_tab(world.resources.get_mut::<GuiHandler>(), 0) }); open_inventory_page.set_callback({ let open_tab = open_tab.clone(); move |world| open_tab(world.resources.get_mut::<GuiHandler>(), 1) }); open_ability_page.set_callback({ let open_tab = open_tab.clone(); move |world| open_tab(world.resources.get_mut::<GuiHandler>(), 2) }); Self::setup_menu(&character_window)?; Ok(character_window) } pub fn event(&self, button: ControllerButton) -> Result<bool> { self.tabs.write().unwrap()[self.tab.load(SeqCst)].event(button) } pub fn tabs<'a>(&'a self) -> Tabs<'a> { Tabs { tabs: self.tabs.read().unwrap(), } } pub fn tabs_mut<'a>(&'a self) -> TabsMut<'a> { TabsMut { tabs: self.tabs.write().unwrap(), } } fn tab(&self) -> Tab<'_> { Tab { index: self.tab.load(SeqCst), tabs: self.tabs.read().unwrap(), } } fn tab_mut(&self) -> TabMut<'_> { TabMut { index: self.tab.load(SeqCst), tabs: self.tabs.write().unwrap(), } } pub fn add_tooltip(&self, name: impl ToString, gui: impl Into<Arc<GuiBuilder>>) { self.tooltips .lock() .unwrap() .insert(name.to_string(), gui.into()); } pub fn remove_tooltip(&self, name: impl ToString) { self.tooltips.lock().unwrap().remove(&name.to_string()); } pub fn salvage_from_inventory<A, F, S>(world: &mut World, hero: Entity, f: F) -> Result<()> where A: Ability + 'static, F: FnOnce(&mut Inventory<A>) -> S, S: Storable, { let entity = world.entity_mut(hero)?; let mut multi_mut = entity.multi_mut(); let crafting_materials = multi_mut.get::<CraftingMaterials>()?; let inventory = multi_mut.get::<Inventory<A>>()?; // remove callback let storable = f(inventory); crafting_materials.increment(storable.rarity()); Ok(()) } } impl TopLevelGui for CharacterWindow { fn gui_traits(&self) -> &dyn GuiElementTraits { self } fn top_gui(&self) -> Option<&dyn TopGui> { Some(self) } fn elements(&self) -> Option<&HashMap<String, UiElement>> { None } fn functionality(&self) -> Option<&dyn Functionality> { None } fn enable(&self, gui_handler: &mut GuiHandler) -> Result<()> { self.menu_gui.enable(gui_handler)?; self.tab_content_grid .attach(gui_handler, self.tab_mut().enable()?, 0, 0, 1, 1)?; self.tab().select()?; let close_button: Arc<Button> = self.menu_gui.element("close")?; close_button.set_info_icon( gui_handler, &self.engine.controller_icon(ControllerButton::B)?, )?; let left_info: Arc<Icon> = self.menu_gui.element("left_info")?; left_info.set_icon( gui_handler, &self.engine.controller_icon(ControllerButton::LeftButton)?, )?; let right_info: Arc<Icon> = self.menu_gui.element("right_info")?; right_info.set_icon( gui_handler, &self.engine.controller_icon(ControllerButton::RightButton)?, )?; Ok(()) } fn disable(&self, gui_handler: &mut GuiHandler) -> Result<()> { self.menu_gui.disable(gui_handler)?; Ok(()) } } impl GuiElementTraits for CharacterWindow { fn gridable(&self) -> Option<&dyn Gridable> { None } fn visibility(&self) -> Option<&dyn Visibility> { None } fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> { None } } impl TopGui for CharacterWindow { fn decline(&self, world: &mut World) -> Result<()> { (self.close)(world) } fn next_tab(&self, world: &mut World, second_level: bool) -> Result<()> { match second_level { false => { // disable old tab self.tab_mut().disable()?; // add to tab index self.tab.store((self.tab.load(SeqCst) + 1) % 3, SeqCst); // update tab content self.tab_content_grid.attach( world.resources.get_mut::<GuiHandler>(), self.tab_mut().enable()?, 0, 0, 1, 1, )?; self.tab().select()?; } true => { self.tab_mut().next_tab()?; } } Ok(()) } fn previous_tab(&self, world: &mut World, second_level: bool) -> Result<()> { match second_level { false => { // disable old tab self.tab_mut().disable()?; // subtract from tab index if self.tab.load(SeqCst) == 0 { self.tab.store(2, SeqCst); } else { self.tab.store(self.tab.load(SeqCst) - 1, SeqCst); } // update tab content self.tab_content_grid.attach( world.resources.get_mut::<GuiHandler>(), self.tab_mut().enable()?, 0, 0, 1, 1, )?; self.tab().select()?; } true => { self.tab_mut().previous_tab()?; } } Ok(()) } } impl CharacterWindow { fn setup_menu(&self) -> Result<()> { // let open_statistics = self.switch_tab(STATISTICS_TAB); // let open_abilities = self.switch_tab(ABILITY_TAB); // let open_inventory = self.switch_tab(INVENTORY_TAB); let close = self.close.clone(); self.menu_gui.set_click_callbacks( ClickCallbacks::default() // .add("open_statistics", open_statistics) // .add("open_abilities", open_abilities) // .add("open_inventory", open_inventory) .add("close", |world, _gui_handler| close(world)) .into(), )?; Ok(()) } }