iv-tools/src/ui.rs

419 lines
15 KiB
Rust

use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPlugin};
use lazy_static::lazy_static;
use std::collections::HashMap;
use crate::{database::Database, inspector::InspectorPokemon, pokemon::*};
const INPUT_LABEL_WIDTH: f32 = 100.0;
const INPUT_TEXT_WIDTH: f32 = 150.0;
const BAR_HEIGHT: f32 = 12.0;
lazy_static! {
static ref BASE_STAT_COLOR_RANGES: Vec<(u8, egui::Color32)> = vec![
(0, egui::Color32::from_rgb(255, 0, 0)),
(30, egui::Color32::from_rgb(255, 128, 0)),
(60, egui::Color32::from_rgb(255, 255, 0)),
(90, egui::Color32::from_rgb(128, 255, 0)),
(120, egui::Color32::from_rgb(0, 255, 0)),
(150, egui::Color32::from_rgb(0, 255, 128)),
(180, egui::Color32::from_rgb(0, 255, 255)),
];
static ref IV_COLOR_RANGES: Vec<(u8, egui::Color32)> = vec![
(0, egui::Color32::from_rgb(255, 0, 0)),
(16, egui::Color32::from_rgb(255, 128, 0)),
(22, egui::Color32::from_rgb(255, 255, 0)),
(27, egui::Color32::from_rgb(128, 255, 0)),
(30, egui::Color32::from_rgb(0, 255, 0)),
];
static ref COLUMNS: Vec<f32> = vec![
8.0, // Nature modifier
25.0, // Base stat
255.0, // Base stat bar
60.0, // Stat name
40.0, // Derived stat input
30.0, // possible IV range
];
}
pub struct UiPlugin;
impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(EguiPlugin)
.insert_resource(UiAssets::default())
.insert_resource(UiState::default())
.add_startup_system(load_assets)
.add_system(handle_state)
.add_system(ui_system);
}
}
#[derive(Resource, Default)]
pub struct UiAssets {
pub bar_handle: Handle<Image>,
pub sprite_map: HashMap<String, (Handle<Image>, egui::TextureId)>,
}
#[derive(Resource, Default)]
pub struct UiState {
pub name: String,
pub level: String,
pub derived_stats: HashMap<BaseStat, String>,
}
fn load_assets(mut ui_assets: ResMut<UiAssets>, assets: Res<AssetServer>) {
ui_assets.bar_handle = assets.load("ui/bar.png");
}
fn handle_state(
ui_state: Res<UiState>,
pokemon: Res<Database<Pokemon>>,
mut inspector: ResMut<InspectorPokemon>,
) {
if ui_state.is_changed() {
if let Some(pokemon) = pokemon.map.get(ui_state.name.to_lowercase().as_str()) {
inspector.pokemon = Some(pokemon.clone());
}
}
}
fn ui_system(
mut contexts: EguiContexts,
mut inspector: ResMut<InspectorPokemon>,
ui_assets: Res<UiAssets>,
mut ui_state: ResMut<UiState>,
mut rendered_texture_id: Local<egui::TextureId>,
mut is_initialized: Local<bool>,
characteristics: Res<Database<Characteristic>>,
natures: Res<Database<Nature>>,
) {
if !*is_initialized {
*is_initialized = true;
*rendered_texture_id = contexts.add_image(ui_assets.bar_handle.clone_weak());
}
egui::CentralPanel::default().show(contexts.ctx_mut(), |ui| {
let sprite_size = egui::Vec2::new(128., 128.);
let pokemon = inspector.pokemon.clone();
let pokemon = pokemon.as_ref();
let nature = inspector.nature.clone();
let nature = nature.as_ref();
let characteristic = inspector.characteristic.clone();
let characteristic = characteristic.as_ref();
// Pokemon
ui.horizontal(|ui| {
ui.horizontal(|ui| {
ui.set_width(INPUT_LABEL_WIDTH);
ui.label("pokemon:");
});
ui.horizontal(|ui| {
ui.set_width(INPUT_TEXT_WIDTH);
let input = ui.text_edit_singleline(&mut ui_state.name);
if input.gained_focus() {
ui_state.name = String::from("");
}
});
});
ui.add_space(2.);
// Nature
ui.horizontal(|ui| {
ui.horizontal(|ui| {
ui.set_width(INPUT_LABEL_WIDTH);
ui.label("nature:");
});
egui::ComboBox::new("nature", "")
.selected_text(format!("{}", nature.map_or("-", |c| &c.name)))
.show_ui(ui, |ui| {
ui.selectable_value(&mut inspector.nature, None, "-");
let mut chars: Vec<_> = natures.map.values().collect();
chars.sort_by_key(|c| c.name.clone());
for char in chars {
ui.selectable_value(&mut inspector.nature, Some(char.clone()), &char.name);
}
});
});
ui.add_space(2.);
// Characteristics
ui.horizontal(|ui| {
ui.horizontal(|ui| {
ui.set_width(INPUT_LABEL_WIDTH);
ui.label("characteristic:");
});
egui::ComboBox::new("characteristic", "")
.selected_text(format!("{}", characteristic.map_or("-", |c| &c.name)))
.show_ui(ui, |ui| {
ui.selectable_value(&mut inspector.characteristic, None, "-");
let mut chars: Vec<_> = characteristics.map.values().collect();
chars.sort_by_key(|c| c.name.clone());
for char in chars {
ui.selectable_value(
&mut inspector.characteristic,
Some(char.clone()),
&char.name,
);
}
});
});
ui.add_space(4.);
ui.horizontal(|ui| {
// Sprite
ui.horizontal(|ui| {
ui.set_min_size(sprite_size);
if let Some((_, rendered_texture_id)) =
pokemon.map_or(None, |pkmn| ui_assets.sprite_map.get(&pkmn.key()))
{
ui.add(egui::Image::new(*rendered_texture_id, sprite_size));
}
});
// if let Some(pokemon) = pokemon {
ui.vertical(|ui| {
ui.add_space(sprite_size.y / 2. - 24.);
// Name
ui.heading(
egui::RichText::new(pokemon.map_or("-", |pkmn| &pkmn.full_name))
.color(egui::Color32::LIGHT_GRAY),
);
// Types
ui.label(format!(
"{} / {}",
pokemon.map_or("-", |pkmn| pkmn
.type1
.as_ref()
.map_or("-", |t| t.name.as_str())),
pokemon.map_or("-", |pkmn| pkmn
.type2
.as_ref()
.map_or("-", |t| t.name.as_str()))
));
});
// };
});
ui.add_space(4.);
ui.horizontal(|ui| {
for (i, width) in COLUMNS.iter().enumerate() {
ui.horizontal(|ui| {
ui.set_width(*width);
match i {
4 => {
let value = &mut ui_state.level;
let input = ui.add(egui::TextEdit::singleline(value));
if input.changed() {
*value = digit_filter(value);
if let Ok(value) = value.parse::<StatValue>() {
inspector.derived_stats.level = Some(value);
}
}
if input.gained_focus() {
*value = String::from("");
}
}
_ => (),
}
});
}
});
ui.horizontal(|ui| {
for (i, width) in COLUMNS.iter().enumerate() {
ui.horizontal(|ui| {
ui.set_width(*width);
match i {
4 => {
ui.label(egui::RichText::new("Stat"));
}
5 => {
ui.label(egui::RichText::new("IV range"));
}
_ => (),
}
});
}
});
// Stats
for stat in vec![
BaseStat::Hp,
BaseStat::Attack,
BaseStat::Defense,
BaseStat::SpecialAttack,
BaseStat::SpecialDefense,
BaseStat::Speed,
] {
let mut column_size = COLUMNS.iter();
ui.horizontal(|ui| {
// Nature modifier
ui.horizontal(|ui| {
if let Some(width) = column_size.next() {
ui.set_width(*width);
}
let symbol = nature.map_or("", |n| {
if n.increased == Some(stat) {
"+"
} else if n.decreased == Some(stat) {
"-"
} else {
""
}
});
ui.label(symbol);
});
// Base stat number
ui.horizontal(|ui| {
if let Some(width) = column_size.next() {
ui.set_width(*width);
}
let base_stat = &pokemon
.map_or(String::from("-"), |pkmn| pkmn.base_value(stat).to_string());
ui.label(egui::RichText::new(base_stat));
});
// Base stat bar
ui.horizontal(|ui| {
if let Some(width) = column_size.next() {
ui.set_width(*width);
}
let base_value = pokemon.map_or(0, |pkmn| pkmn.base_value(stat));
let bar_length = base_value as f32;
let mut color = BASE_STAT_COLOR_RANGES.first().unwrap().1;
for (treshold, c) in BASE_STAT_COLOR_RANGES.iter() {
if base_value >= *treshold {
color = c.clone();
} else {
break;
}
}
let image = egui::Image::new(*rendered_texture_id, [bar_length, BAR_HEIGHT])
.tint(color);
ui.add(image);
});
// Stat name
ui.horizontal(|ui| {
if let Some(width) = column_size.next() {
ui.set_width(*width);
}
ui.label(
egui::RichText::new(format!("{stat}")).color(nature_color(stat, nature)),
);
});
// Derived stat input
ui.horizontal(|ui| {
if let Some(width) = column_size.next() {
ui.set_width(*width);
}
if !ui_state.derived_stats.contains_key(&stat) {
ui_state.derived_stats.insert(stat, "".to_string());
}
let value = ui_state.derived_stats.get_mut(&stat).unwrap();
let input = ui.add(egui::TextEdit::singleline(value));
if input.changed() {
*value = digit_filter(value);
if let (Ok(value), derived) = (
value.parse::<StatValue>(),
inspector.derived_stats.value_mut(stat),
) {
*derived = Some(value);
}
}
if input.gained_focus() {
*value = String::from("");
}
});
let possible_ivs = if let (Some(pokemon), Some(level), Some(derived_stat)) = (
pokemon,
inspector.derived_stats.level,
inspector.derived_stats.value(stat),
) {
calculate_possible_ivs(
stat,
pokemon.base_value(stat),
derived_stat,
0,
level,
nature,
characteristic,
)
} else {
Ok(vec![])
};
// Possible IV range
ui.horizontal(|ui| match possible_ivs {
Ok(possible_ivs) => {
if possible_ivs.len() == 0 {
ui.label(egui::RichText::new("?"));
} else if possible_ivs.len() <= 10 {
for iv in possible_ivs.iter() {
ui.label(
egui::RichText::new(format!("{}", iv))
.color(iv_color(*iv as u8)),
);
}
} else {
let first = possible_ivs.first().unwrap();
let last = possible_ivs.last().unwrap();
ui.label(
egui::RichText::new(format!("{}", first))
.color(iv_color(*first as u8)),
);
ui.label(egui::RichText::new("-"));
ui.label(
egui::RichText::new(format!("{}", last))
.color(iv_color(*last as u8)),
);
}
}
Err(reason) => {
ui.label(egui::RichText::new(reason).color(egui::Color32::RED));
}
});
});
}
});
}
fn nature_color(stat: BaseStat, nature: Option<&Nature>) -> egui::Color32 {
if let Some(nature) = nature {
if nature.increased == Some(stat) {
egui::Color32::from_rgb(255, 64, 192)
} else if nature.decreased == Some(stat) {
egui::Color32::from_rgb(64, 192, 255)
} else {
egui::Color32::LIGHT_GRAY
}
} else {
egui::Color32::LIGHT_GRAY
}
}
fn iv_color(iv: u8) -> egui::Color32 {
for (treshold, color) in IV_COLOR_RANGES.iter().rev() {
if iv >= *treshold {
return color.clone();
}
}
IV_COLOR_RANGES
.first()
.map_or(egui::Color32::LIGHT_GRAY, |(_, color)| color.clone())
}
fn digit_filter(string: &str) -> String {
string.chars().filter(|v| v.is_digit(10)).collect()
}