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 = 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, pub sprite_map: HashMap, egui::TextureId)>, } #[derive(Resource, Default)] pub struct UiState { pub name: String, pub level: String, pub derived_stats: HashMap, } fn load_assets(mut ui_assets: ResMut, assets: Res) { ui_assets.bar_handle = assets.load("ui/bar.png"); } fn handle_state( ui_state: Res, pokemon: Res>, mut inspector: ResMut, ) { 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, ui_assets: Res, mut ui_state: ResMut, mut rendered_texture_id: Local, mut is_initialized: Local, characteristics: Res>, natures: Res>, ) { 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::() { 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::(), 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() }