Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cf83998
Reorganize `widgets` module into new `panels` & general `elements` mo…
augenfrosch May 28, 2026
7f4732e
Refactor `RenderInfo` struct of the dev-panel to directly own its state
augenfrosch May 28, 2026
c0578b2
Move search state into `AppContext` struct
augenfrosch Jun 1, 2026
8fa7316
Deduplicate food/potion select code
augenfrosch Jun 1, 2026
11e3dab
Fix Clippy warnings
augenfrosch Jun 1, 2026
22fb449
WIP: move solve state into new struct
augenfrosch Jun 1, 2026
e902336
Use `Pending` status to determine if solve is waiting for thread pool…
augenfrosch Jun 1, 2026
1be4089
Use `last_solve_info` to detect changed config for simulator panel's …
augenfrosch Jun 1, 2026
a77f486
Clear `Pending` status when loading from saved rotations
augenfrosch Jun 1, 2026
b9a5c11
Fix/improve config changed warning behavior during & after cancelling…
augenfrosch Jun 1, 2026
8f38f5a
Fix Clippy warnings
augenfrosch Jun 1, 2026
f94461e
Refactor font loading to ensure CJK Unified Ideographs are displayed …
augenfrosch Jun 2, 2026
d650165
Set `show_custom_recipe_select` to the appropriate value when loading…
augenfrosch Jun 5, 2026
2515dde
Use more robust approach to keeping the simulator's progress bar text…
augenfrosch Jun 5, 2026
6f8f267
Remove no longer used glyphs from `XIV_Icon_Recreations` font
augenfrosch Jun 5, 2026
1c83d40
Update `translations.toml`
augenfrosch Jun 5, 2026
18ec9b1
Expect Clippy warning for web version
augenfrosch Jun 5, 2026
b25d51c
Remove leftover commented out code and assert & update `translations.…
augenfrosch Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/fonts/XIV_Icon_Recreations/XIV_Icon_Recreations.ttf
Binary file not shown.
274 changes: 137 additions & 137 deletions raphael-translations/translations.toml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion raphael-translations/update-toml.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ RAPHAEL_TRANSLATIONS_RESET_APPEARANCES=true \
cargo build --package raphael-xiv --features=raphael-translations/update-toml

RUSTFLAGS='-Ctarget-feature=+atomics,+bulk-memory' BASE_URL='' \
cargo +nightly build --package raphael-xiv --features=raphael-translations/update-toml --target wasm32-unknown-unknown
cargo build --package raphael-xiv --features=raphael-translations/update-toml --target wasm32-unknown-unknown
486 changes: 102 additions & 384 deletions src/app.rs

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use serde::{Deserialize, Serialize, de::DeserializeOwned};

use crate::{
config::{AppConfig, CrafterConfig, QualitySource, QualityTarget, RecipeConfiguration},
widgets::{MacroViewConfig, SavedRotationsConfig, SavedRotationsData},
elements::panels::{
MacroViewConfig, RecipeSearchDomain, SavedRotationsConfig, SavedRotationsData,
},
};

fn load<T: DeserializeOwned>(cc: &eframe::CreationContext<'_>, key: &'static str, default: T) -> T {
Expand All @@ -13,6 +15,25 @@ fn load<T: DeserializeOwned>(cc: &eframe::CreationContext<'_>, key: &'static str
}
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SearchState {
pub recipe: RecipeSearchState,
pub food: GenericSearchState,
pub potion: GenericSearchState,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RecipeSearchState {
pub show_custom_recipe_select: bool,
pub search_domain: RecipeSearchDomain,
pub search_text: String,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GenericSearchState {
pub search_text: String,
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SolverConfig {
pub quality_target: QualityTarget,
Expand All @@ -27,6 +48,7 @@ pub struct SolverConfig {
pub struct AppContext {
pub locale: Locale,
pub app_config: AppConfig,
pub search_state: SearchState,
pub recipe_config: RecipeConfiguration,
pub selected_food: Option<Consumable>,
pub selected_potion: Option<Consumable>,
Expand All @@ -42,6 +64,7 @@ impl AppContext {
Self {
locale: load(cc, "LOCALE", Locale::EN),
app_config: load(cc, "APP_CONFIG", AppConfig::default()),
search_state: load(cc, "SEARCH_STATE", SearchState::default()),
recipe_config: load(cc, "RECIPE_CONFIG", RecipeConfiguration::default()),
selected_food: load(cc, "SELECTED_FOOD", None),
selected_potion: load(cc, "SELECTED_POTION", None),
Expand All @@ -60,6 +83,7 @@ impl AppContext {
pub fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, "LOCALE", &self.locale);
eframe::set_value(storage, "APP_CONFIG", &self.app_config);
eframe::set_value(storage, "SEARCH_STATE", &self.search_state);
eframe::set_value(storage, "RECIPE_CONFIG", &self.recipe_config);
eframe::set_value(storage, "SELECTED_FOOD", &self.selected_food);
eframe::set_value(storage, "SELECTED_POTION", &self.selected_potion);
Expand Down
3 changes: 3 additions & 0 deletions src/elements/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod panels;
pub mod util;
pub mod widgets;
259 changes: 259 additions & 0 deletions src/elements/panels/consumable_select.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
use std::marker::PhantomData;

use egui::{
Align, Id, Layout, Widget,
util::cache::{ComputerMut, FrameCache},
};
use egui_extras::Column;
use raphael_data::{Consumable, CrafterStats, Locale, find_meals, find_potions};
use raphael_translations::t;

use crate::{
context::AppContext,
elements::{
util::{self, TableColumnWidth},
widgets::{GameDataNameLabel, collapse_persisted},
},
};

trait ConsumableSelectType {
const COLLAPSE_ID: &str;
const TABLE_ID: &str;

const CONSUMABLE_TYPE: ConsumableType;

fn panel_name(locale: Locale) -> &'static str;
}

struct ConsumableSelect<'a, Type> {
search_text: &'a mut String,
crafter_stats: &'a CrafterStats,
selected_consumable: &'a mut Option<Consumable>,
locale: Locale,
r#type: PhantomData<Type>,
}

#[derive(Debug, Clone, Copy)]
enum ConsumableType {
Food,
Potion,
}

impl<'a, Type> ConsumableSelect<'a, Type>
where
Type: ConsumableSelectType,
{
pub fn new(app_context: &'a mut AppContext) -> Self {
let AppContext {
locale,
search_state,
selected_food,
selected_potion,
crafter_config,
..
} = app_context;
let (search_text, selected_consumable) = match Type::CONSUMABLE_TYPE {
ConsumableType::Food => (&mut search_state.food.search_text, selected_food),
ConsumableType::Potion => (&mut search_state.potion.search_text, selected_potion),
};

Self {
search_text,
crafter_stats: crafter_config.active_stats(),
selected_consumable,
locale: *locale,
r#type: PhantomData,
}
}
}

impl<Type> Widget for ConsumableSelect<'_, Type>
where
Type: Default
+ ConsumableSelectType
+ for<'a> ComputerMut<(&'a str, raphael_data::Locale), std::vec::Vec<&'static Consumable>>,
{
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
let Self {
search_text,
crafter_stats,
selected_consumable,
locale,
..
} = self;
ui.group(|ui| {
ui.style_mut().spacing.item_spacing = egui::vec2(8.0, 3.0);
ui.vertical(|ui| {
let mut collapsed = false;

ui.horizontal(|ui| {
collapse_persisted(ui, Id::new(Type::COLLAPSE_ID), &mut collapsed);
ui.label(egui::RichText::new(Type::panel_name(locale)).strong());
match selected_consumable {
None => ui.label(t!(locale, "None")),
Some(item) => ui.add(GameDataNameLabel::new(&*item, locale)),
};
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if ui
.add_enabled(selected_consumable.is_some(), egui::Button::new("🗑"))
.clicked()
{
*selected_consumable = None;
}
});
});

if collapsed {
return;
}

ui.separator();

if egui::TextEdit::singleline(search_text)
.desired_width(f32::INFINITY)
.hint_text(t!(locale, "🔍 Search"))
.ui(ui)
.changed()
{
*search_text = search_text.replace('\0', "");
}
ui.separator();

let search_result = ui.ctx().memory_mut(|mem| {
let search_cache = mem
.caches
.cache::<FrameCache<Vec<&'static Consumable>, Type>>();
search_cache.get((search_text, locale)).clone()
});

let line_height = ui.spacing().interact_size.y;
let line_spacing = ui.spacing().item_spacing.y;
let table_height = 4.3 * line_height + 4.0 * line_spacing;

// Column::remainder().clip(true) is buggy when resizing the table
let column_widths = util::calculate_column_widths(
ui,
[
TableColumnWidth::SelectButton,
TableColumnWidth::RelativeToRemainingClamped {
scale: 0.7,
min: 220.0,
max: 320.0,
},
TableColumnWidth::Remaining,
],
locale,
);

let table = egui_extras::TableBuilder::new(ui)
.id_salt(Type::TABLE_ID)
.auto_shrink(false)
.striped(true)
.column(Column::exact(column_widths[0]))
.column(Column::exact(column_widths[1]))
.column(Column::exact(column_widths[2]))
.min_scrolled_height(table_height)
.max_scroll_height(table_height);
table.body(|body| {
body.rows(line_height, search_result.len(), |mut row| {
let item = search_result[row.index()];
row.col(|ui| {
if ui.button(t!(locale, "Select")).clicked() {
*selected_consumable = Some(*item);
}
});
row.col(|ui| {
ui.add(GameDataNameLabel::new(item, locale));
});
row.col(|ui| {
ui.label(util::effect_string(*item, crafter_stats, locale));
});
});
});
});
})
.response
}
}

#[derive(Default)]
struct FoodType {}

impl ConsumableSelectType for FoodType {
const COLLAPSE_ID: &str = "FOOD_SEARCH_COLLAPSED";
const TABLE_ID: &str = "FOOD_SELECT_TABLE";

const CONSUMABLE_TYPE: ConsumableType = ConsumableType::Food;

#[inline]
fn panel_name(locale: Locale) -> &'static str {
t!(locale, "Food")
}
}

impl ComputerMut<(&str, Locale), Vec<&'static Consumable>> for FoodType {
fn compute(&mut self, (text, locale): (&str, Locale)) -> Vec<&'static Consumable> {
find_meals(text, locale).collect::<Vec<_>>()
}
}

pub struct FoodSelect<'a> {
inner: ConsumableSelect<'a, FoodType>,
}

impl<'a> FoodSelect<'a> {
#[inline]
pub fn new(app_context: &'a mut AppContext) -> Self {
Self {
inner: ConsumableSelect::new(app_context),
}
}
}

impl Widget for FoodSelect<'_> {
#[inline]
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
self.inner.ui(ui)
}
}

#[derive(Default)]
struct PotionType {}

impl ConsumableSelectType for PotionType {
const COLLAPSE_ID: &str = "POTION_SEARCH_COLLAPSED";
const TABLE_ID: &str = "POTION_SELECT_TABLE";

const CONSUMABLE_TYPE: ConsumableType = ConsumableType::Potion;

#[inline]
fn panel_name(locale: Locale) -> &'static str {
t!(locale, "Potion")
}
}

impl ComputerMut<(&str, Locale), Vec<&'static Consumable>> for PotionType {
fn compute(&mut self, (text, locale): (&str, Locale)) -> Vec<&'static Consumable> {
find_potions(text, locale).collect::<Vec<_>>()
}
}

pub struct PotionSelect<'a> {
inner: ConsumableSelect<'a, PotionType>,
}

impl<'a> PotionSelect<'a> {
#[inline]
pub fn new(app_context: &'a mut AppContext) -> Self {
Self {
inner: ConsumableSelect::new(app_context),
}
}
}

impl Widget for PotionSelect<'_> {
#[inline]
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
self.inner.ui(ui)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};

use crate::{
context::AppContext,
widgets::{HelpText, MultilineMonospace},
elements::widgets::{HelpText, MultilineMonospace},
};

#[inline]
Expand Down
24 changes: 24 additions & 0 deletions src/elements/panels/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
mod macro_view;
pub use macro_view::{MacroView, MacroViewConfig};

mod simulator;
pub use simulator::Simulator;

mod recipe_select;
pub use recipe_select::{RecipeSelect, SearchDomain as RecipeSearchDomain};

mod consumable_select;
pub use consumable_select::{FoodSelect, PotionSelect};

mod stats_edit;
pub use stats_edit::StatsEdit;

mod saved_rotations;
pub use saved_rotations::{
Rotation, SavedRotationsConfig, SavedRotationsData, SavedRotationsWidget,
};

#[cfg(any(debug_assertions, feature = "dev-panel"))]
mod render_info;
#[cfg(any(debug_assertions, feature = "dev-panel"))]
pub use render_info::RenderInfo;
Loading
Loading