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-04-05 10:16:34 +00:00
|
|
|
use ecs::*;
|
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 {
|
2025-03-05 08:45:39 +00:00
|
|
|
fn enable(&mut self, world: &mut World) -> Result<Arc<Grid>>;
|
|
|
|
fn disable(&mut self, world: &mut World) -> Result<()>;
|
|
|
|
fn select(&self, world: &mut World) -> Result<()>;
|
|
|
|
fn next_tab(&mut self, world: &mut World) -> Result<()>;
|
|
|
|
fn previous_tab(&mut self, world: &mut World) -> Result<()>;
|
|
|
|
fn event(&mut self, world: &mut World, button: ControllerButton) -> Result<bool>;
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-04 17:40:56 +00:00
|
|
|
world: &mut World,
|
2025-02-28 07:43:35 +00:00
|
|
|
hero: Entity,
|
|
|
|
name: &str,
|
|
|
|
close: Box<dyn FutureStateChange>,
|
|
|
|
) -> Result<Arc<Self>> {
|
2025-03-05 08:45:39 +00:00
|
|
|
let menu_gui = GuiBuilder::from_str(world, 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([
|
2025-03-04 17:40:56 +00:00
|
|
|
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()),
|
2025-02-28 07:43:35 +00:00
|
|
|
]),
|
|
|
|
tab: AtomicUsize::new(0),
|
|
|
|
});
|
|
|
|
|
|
|
|
let open_tab = {
|
|
|
|
let weak_me = Arc::downgrade(&character_window);
|
|
|
|
|
2025-03-05 08:45:39 +00:00
|
|
|
move |world: &mut World, index| {
|
2025-02-28 07:43:35 +00:00
|
|
|
if let Some(me) = weak_me.upgrade() {
|
|
|
|
me.tab.store(index, SeqCst);
|
|
|
|
|
2025-03-05 09:31:16 +00:00
|
|
|
let child = me.tab_mut().enable(world)?;
|
|
|
|
me.tab_content_grid.attach(world, child, 0, 0, 1, 1)?;
|
2025-03-05 08:45:39 +00:00
|
|
|
me.tab().select(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
open_character_page.set_callback({
|
|
|
|
let open_tab = open_tab.clone();
|
|
|
|
|
2025-03-05 08:45:39 +00:00
|
|
|
move |world| open_tab(world, 0)
|
2025-02-28 07:43:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
open_inventory_page.set_callback({
|
|
|
|
let open_tab = open_tab.clone();
|
|
|
|
|
2025-03-05 08:45:39 +00:00
|
|
|
move |world| open_tab(world, 1)
|
2025-02-28 07:43:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
open_ability_page.set_callback({
|
|
|
|
let open_tab = open_tab.clone();
|
|
|
|
|
2025-03-05 08:45:39 +00:00
|
|
|
move |world| open_tab(world, 2)
|
2025-02-28 07:43:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Self::setup_menu(&character_window)?;
|
|
|
|
|
|
|
|
Ok(character_window)
|
|
|
|
}
|
|
|
|
|
2025-03-05 08:45:39 +00:00
|
|
|
pub fn event(&self, world: &mut World, button: ControllerButton) -> Result<bool> {
|
|
|
|
self.tabs.write().unwrap()[self.tab.load(SeqCst)].event(world, button)
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2025-03-06 16:30:50 +00:00
|
|
|
pub fn remove_tooltip(&self, world: &mut World, name: impl ToString) -> Result<()> {
|
|
|
|
if let Some(tooltip) = self.tooltips.lock().unwrap().remove(&name.to_string()) {
|
|
|
|
tooltip.disable(world)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
|
2025-03-04 17:40:56 +00:00
|
|
|
pub fn salvage_from_inventory<A, F, S>(world: &mut World, hero: Entity, f: F) -> Result<()>
|
2025-02-28 07:43:35 +00:00
|
|
|
where
|
|
|
|
A: Ability + 'static,
|
|
|
|
F: FnOnce(&mut Inventory<A>) -> S,
|
|
|
|
S: Storable,
|
|
|
|
{
|
2025-03-04 17:40:56 +00:00
|
|
|
let entity = world.entity_mut(hero)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
2025-04-05 10:16:34 +00:00
|
|
|
let (crafting_materials, inventory): (&mut CraftingMaterials, &mut Inventory<A>) =
|
|
|
|
entity.get_components_mut()?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
2025-03-04 17:40:56 +00:00
|
|
|
// remove callback
|
|
|
|
let storable = f(inventory);
|
2025-02-28 07:43:35 +00:00
|
|
|
|
2025-03-04 17:40:56 +00:00
|
|
|
crafting_materials.increment(storable.rarity());
|
2025-02-28 07:43:35 +00:00
|
|
|
|
2025-03-04 17:40:56 +00:00
|
|
|
Ok(())
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2025-03-05 08:45:39 +00:00
|
|
|
fn enable(&self, world: &mut World) -> Result<()> {
|
|
|
|
self.menu_gui.enable(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
2025-03-05 09:31:16 +00:00
|
|
|
let child = self.tab_mut().enable(world)?;
|
|
|
|
self.tab_content_grid.attach(world, child, 0, 0, 1, 1)?;
|
2025-03-05 08:45:39 +00:00
|
|
|
self.tab().select(world)?;
|
|
|
|
|
2025-04-05 10:16:34 +00:00
|
|
|
let (gui_handler, engine_settings, context): (
|
|
|
|
&mut GuiHandler,
|
|
|
|
&mut EngineSettings,
|
|
|
|
&mut Context,
|
|
|
|
) = world.resources.get_mut()?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
|
|
|
let close_button: Arc<Button> = self.menu_gui.element("close")?;
|
2025-03-04 17:40:56 +00:00
|
|
|
close_button.set_info_icon(
|
|
|
|
gui_handler,
|
2025-03-05 08:45:39 +00:00
|
|
|
&engine_settings.controller_icon(context, ControllerButton::B)?,
|
2025-03-04 17:40:56 +00:00
|
|
|
)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
|
|
|
let left_info: Arc<Icon> = self.menu_gui.element("left_info")?;
|
2025-03-04 17:40:56 +00:00
|
|
|
left_info.set_icon(
|
|
|
|
gui_handler,
|
2025-03-05 08:45:39 +00:00
|
|
|
&engine_settings.controller_icon(context, ControllerButton::LeftButton)?,
|
2025-03-04 17:40:56 +00:00
|
|
|
)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
|
|
|
let right_info: Arc<Icon> = self.menu_gui.element("right_info")?;
|
2025-03-04 17:40:56 +00:00
|
|
|
right_info.set_icon(
|
|
|
|
gui_handler,
|
2025-03-05 08:45:39 +00:00
|
|
|
&engine_settings.controller_icon(context, ControllerButton::RightButton)?,
|
2025-03-04 17:40:56 +00:00
|
|
|
)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2025-03-05 08:45:39 +00:00
|
|
|
fn disable(&self, world: &mut World) -> Result<()> {
|
|
|
|
self.menu_gui.disable(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
|
|
|
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 {
|
2025-03-04 17:40:56 +00:00
|
|
|
fn decline(&self, world: &mut World) -> Result<()> {
|
|
|
|
(self.close)(world)
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
|
2025-03-04 17:40:56 +00:00
|
|
|
fn next_tab(&self, world: &mut World, second_level: bool) -> Result<()> {
|
2025-02-28 07:43:35 +00:00
|
|
|
match second_level {
|
|
|
|
false => {
|
|
|
|
// disable old tab
|
2025-03-05 08:45:39 +00:00
|
|
|
self.tab_mut().disable(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
|
|
|
// add to tab index
|
|
|
|
self.tab.store((self.tab.load(SeqCst) + 1) % 3, SeqCst);
|
|
|
|
|
|
|
|
// update tab content
|
2025-03-05 09:31:16 +00:00
|
|
|
let child = self.tab_mut().enable(world)?;
|
|
|
|
self.tab_content_grid.attach(world, child, 0, 0, 1, 1)?;
|
2025-03-05 08:45:39 +00:00
|
|
|
self.tab().select(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
true => {
|
2025-03-05 08:45:39 +00:00
|
|
|
self.tab_mut().next_tab(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2025-03-04 17:40:56 +00:00
|
|
|
fn previous_tab(&self, world: &mut World, second_level: bool) -> Result<()> {
|
2025-02-28 07:43:35 +00:00
|
|
|
match second_level {
|
|
|
|
false => {
|
|
|
|
// disable old tab
|
2025-03-05 08:45:39 +00:00
|
|
|
self.tab_mut().disable(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
|
|
|
|
// 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
|
2025-03-05 09:31:16 +00:00
|
|
|
let child = self.tab_mut().enable(world)?;
|
|
|
|
self.tab_content_grid.attach(world, child, 0, 0, 1, 1)?;
|
2025-03-05 08:45:39 +00:00
|
|
|
self.tab().select(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
true => {
|
2025-03-05 08:45:39 +00:00
|
|
|
self.tab_mut().previous_tab(world)?;
|
2025-02-28 07:43:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2025-03-05 08:45:39 +00:00
|
|
|
.add("close", move |world| close(world))
|
2025-02-28 07:43:35 +00:00
|
|
|
.into(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|