rpg_base/character_window/src/lib.rs

403 lines
10 KiB
Rust
Raw Normal View History

2025-02-28 07:43:35 +00:00
mod abilities;
mod character;
mod content;
mod inventory;
mod page_content;
mod traits;
use anyhow::Result;
2025-03-03 18:06:15 +00:00
use downcast_rs::{Downcast, impl_downcast};
2025-02-28 07:43:35 +00:00
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,
2025-03-03 18:06:15 +00:00
atomic::{AtomicUsize, Ordering::SeqCst},
2025-02-28 07:43:35 +00:00
},
};
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>(
2025-03-03 18:06:15 +00:00
world: &World,
2025-02-28 07:43:35 +00:00
hero: Entity,
name: &str,
close: Box<dyn FutureStateChange>,
) -> Result<Arc<Self>> {
2025-03-03 18:06:15 +00:00
let menu_gui = GuiBuilder::from_str(
world.resources.get::<Arc<GuiHandler>>(),
include_str!("../resources/menu.xml"),
)?;
2025-02-28 07:43:35 +00:00
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(&engine, hero, name, me).unwrap()),
Box::new(
InventoryPage::<A>::new(&engine, hero, me.clone(), &close_button).unwrap(),
),
Box::new(AbilityPage::<A>::new(&engine, hero, me.clone(), &close_button).unwrap()),
]),
tab: AtomicUsize::new(0),
engine,
});
let open_tab = {
let weak_me = Arc::downgrade(&character_window);
move |index| {
if let Some(me) = weak_me.upgrade() {
me.tab.store(index, SeqCst);
me.tab_content_grid
.attach(me.tab_mut().enable()?, 0, 0, 1, 1)?;
me.tab().select()?;
}
Ok(())
}
};
open_character_page.set_callback({
let open_tab = open_tab.clone();
move || open_tab(0)
});
open_inventory_page.set_callback({
let open_tab = open_tab.clone();
move || open_tab(1)
});
open_ability_page.set_callback({
let open_tab = open_tab.clone();
move || open_tab(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>(engine: &Engine, hero: Entity, f: F) -> Result<()>
where
A: Ability + 'static,
F: FnOnce(&mut Inventory<A>) -> S,
S: Storable,
{
engine.on_scene_mut(|scene| {
let entity = scene.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) -> Result<()> {
self.menu_gui.enable()?;
self.tab_content_grid
.attach(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(&self.engine.controller_icon(ControllerButton::B)?)?;
let left_info: Arc<Icon> = self.menu_gui.element("left_info")?;
left_info.set_icon(&self.engine.controller_icon(ControllerButton::LeftButton)?)?;
let right_info: Arc<Icon> = self.menu_gui.element("right_info")?;
right_info.set_icon(&self.engine.controller_icon(ControllerButton::RightButton)?)?;
Ok(())
}
fn disable(&self) -> Result<()> {
self.menu_gui.disable()?;
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) -> Result<()> {
(self.close)()
}
fn next_tab(&self, 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(self.tab_mut().enable()?, 0, 0, 1, 1)?;
self.tab().select()?;
}
true => {
self.tab_mut().next_tab()?;
}
}
Ok(())
}
fn previous_tab(&self, 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(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", close)
.into(),
)?;
Ok(())
}
}