From baf64181ec96d76e9235b3a6b70de1cc6c271a80 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Fri, 31 Mar 2023 20:18:41 +0300 Subject: [PATCH] feat: ivpeek ui --- src/database.rs | 18 ++++ src/ivcalc/calculator.rs | 19 ---- src/ivpeek.rs | 1 + src/ivpeek/inspector.rs | 77 ++++++++++++++- src/ivpeek/ui.rs | 201 +++++++++++++++++++++++++++++++++------ 5 files changed, 267 insertions(+), 49 deletions(-) diff --git a/src/database.rs b/src/database.rs index 4e1f816..aa34e96 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,4 +1,8 @@ use std::collections::HashMap; +use std::{ + fs, + path::{Path, PathBuf}, +}; use bevy::prelude::*; use json::JsonValue; @@ -49,3 +53,17 @@ fn database_setup( pokemon_database.populate_from_json(&read_pokedex()); characteristic_database.populate_from_json(&read_characteristics()); } + +pub fn load_url_asset( + url: String, + path: PathBuf, + assets: &Res, +) -> Result, Box> { + let system_path = Path::new("assets").join(&path); + if Path::exists(&path) { + return Ok(assets.load(path)); + } + let data = reqwest::blocking::get(&url)?.bytes()?; + fs::write(&system_path, data).unwrap(); + Ok(assets.load(path)) +} diff --git a/src/ivcalc/calculator.rs b/src/ivcalc/calculator.rs index d0c0e40..fa9fb52 100644 --- a/src/ivcalc/calculator.rs +++ b/src/ivcalc/calculator.rs @@ -1,8 +1,3 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; - use bevy::prelude::*; use bevy_egui::EguiContexts; @@ -112,17 +107,3 @@ fn request_handler( } } } - -pub fn load_url_asset( - url: String, - path: PathBuf, - assets: &Res, -) -> Result, Box> { - let system_path = Path::new("assets").join(&path); - if Path::exists(&path) { - return Ok(assets.load(path)); - } - let data = reqwest::blocking::get(&url)?.bytes()?; - fs::write(&system_path, data).unwrap(); - Ok(assets.load(path)) -} diff --git a/src/ivpeek.rs b/src/ivpeek.rs index 75b3f1a..b622eb0 100644 --- a/src/ivpeek.rs +++ b/src/ivpeek.rs @@ -19,6 +19,7 @@ pub fn run() { .set(ImagePlugin::default_nearest()), ) .add_plugin(crate::database::DatabasePlugin) + .add_plugin(inspector::InspectorPlugin) .add_plugin(ui::UiPlugin) .run() } diff --git a/src/ivpeek/inspector.rs b/src/ivpeek/inspector.rs index e040521..9021def 100644 --- a/src/ivpeek/inspector.rs +++ b/src/ivpeek/inspector.rs @@ -1,14 +1,22 @@ use std::collections::HashMap; use bevy::prelude::*; +use bevy_egui::EguiContexts; -use crate::pokemon::*; +use crate::{ + database::{load_url_asset, Database}, + pokemon::*, +}; + +use super::ui::UiAssets; pub struct InspectorPlugin; impl Plugin for InspectorPlugin { fn build(&self, app: &mut App) { - app.insert_resource(Inspector::default()); + app.insert_resource(Inspector::default()) + .add_system(state_changed) + .add_startup_system(test_init); } } @@ -32,3 +40,68 @@ pub struct PokemonInstance { pub ivs: HashMap, pub evs: HashMap, } + +fn test_init(mut inspector: ResMut, pokemon_database: Res>) { + for name in vec![ + "baltoy", + "claydol", + "regirock", + "flygon", + "shuckle", + "swampert", + "forretress", + "shedinja", + "blissey", + ] + .iter() + { + inspector.enemies.push(PokemonInstance { + pokemon: pokemon_database.map.get(*name).unwrap().clone(), + ivs: { + let mut result = HashMap::new(); + for stat in BaseStat::all().iter() { + result.insert( + *stat, + match stat { + BaseStat::Hp => 31, + BaseStat::Attack => 27, + BaseStat::Defense => 26, + BaseStat::SpecialAttack => 0, + BaseStat::SpecialDefense => 21, + BaseStat::Speed => 15, + }, + ); + } + result + }, + ..default() + }); + } +} + +fn state_changed( + inspector: Res, + assets: Res, + mut ui_assets: ResMut, + mut contexts: EguiContexts, +) { + if inspector.is_changed() { + for instance in inspector.enemies.iter().chain(inspector.allies.iter()) { + let pokemon = instance.pokemon.clone(); + + if !ui_assets.sprite_map.contains_key(&pokemon.key()) { + let handle = load_url_asset( + pokemon.sprite_url(), + format!("cache/{name}.png", name = pokemon.key()).into(), + &assets, + ); + if let Ok(handle) = handle { + let weak = handle.clone_weak(); + ui_assets + .sprite_map + .insert(pokemon.key(), (handle, contexts.add_image(weak))); + } + } + } + } +} diff --git a/src/ivpeek/ui.rs b/src/ivpeek/ui.rs index bb0c848..73495ac 100644 --- a/src/ivpeek/ui.rs +++ b/src/ivpeek/ui.rs @@ -3,14 +3,14 @@ use bevy_egui::{egui, EguiContexts, EguiPlugin}; use lazy_static::lazy_static; use std::collections::HashMap; -use crate::{database::Database, pokemon::*}; +use crate::pokemon::*; -use super::inspector::{Inspector, PokemonInstance}; +use super::inspector::Inspector; const BAR_HEIGHT: f32 = 12.0; -const BASE_STAT_WIDTH_MULT: f32 = 1.0; +const BASE_STAT_WIDTH_MULT: f32 = 0.8; const IV_WIDTH_MULT: f32 = 4.0; -const EV_WIDTH_MULT: f32 = 1.0; +// const EV_WIDTH_MULT: f32 = 1.0; lazy_static! { static ref BASE_STAT_COLOR_RANGES: Vec<(u8, egui::Color32)> = vec![ @@ -33,10 +33,10 @@ lazy_static! { 25.0, // Base stat 255.0 * BASE_STAT_WIDTH_MULT, // Base stat bar 60.0, // Stat name - 40.0, // IV value + 25.0, // IV value 32.0 * IV_WIDTH_MULT, // IV bar - 40.0, // EV value - 255.0 * EV_WIDTH_MULT, // EV bar + // 40.0, // EV value + // 255.0 * EV_WIDTH_MULT, // EV bar ]; } @@ -45,21 +45,12 @@ pub struct UiPlugin; impl Plugin for UiPlugin { fn build(&self, app: &mut App) { app.add_plugin(EguiPlugin) - .insert_resource(Inspector::default()) .insert_resource(UiAssets::default()) .add_startup_system(load_assets) - .add_system(ui_system) - .add_startup_system(test_init); + .add_system(ui_system); } } -fn test_init(mut inspector: ResMut, pokemon_database: Res>) { - inspector.enemies.push(PokemonInstance { - pokemon: pokemon_database.map.get("baltoy").unwrap().clone(), - ..default() - }); -} - #[derive(Resource, Default)] pub struct UiAssets { pub bar_handle: Handle, @@ -84,20 +75,163 @@ fn ui_system( egui::CentralPanel::default().show(contexts.ctx_mut(), |ui| { let sprite_size = egui::Vec2::new(128., 128.); - for instance in inspector.enemies.iter().chain(inspector.allies.iter()) { - let pokemon = instance.pokemon.clone(); - // Sprite - ui.horizontal(|ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + for instance in inspector.enemies.iter().chain(inspector.allies.iter()) { + let pokemon = instance.pokemon.clone(); + let nature = instance.nature.clone(); + ui.horizontal(|ui| { - ui.set_min_size(sprite_size); - if let Some((_, rendered_texture_id)) = ui_assets.sprite_map.get(&pokemon.key()) - { - ui.add(egui::Image::new(*rendered_texture_id, sprite_size)); - } + ui.horizontal(|ui| { + ui.set_min_width(sprite_size.x + 32.); + ui.set_max_width(sprite_size.x + 32.); + // Name + ui.label( + egui::RichText::new(&pokemon.full_name) + .color(egui::Color32::LIGHT_GRAY), + ); + + // Types + ui.label(format!( + "{} / {}", + pokemon.type1.as_ref().map_or("-", |t| t.name.as_str()), + pokemon.type2.as_ref().map_or("-", |t| t.name.as_str()) + )); + }); + + // Headings + ui.horizontal(|ui| { + for (i, width) in COLUMNS.iter().enumerate() { + ui.horizontal(|ui| { + ui.set_width(*width); + match i { + 1 => { + ui.label(egui::RichText::new("Base stat")); + } + 3 => { + ui.label(egui::RichText::new("IV")); + } + 4 => { + ui.label(egui::RichText::new("IV range")); + } + _ => (), + } + }); + } + }); }); - }); - } + + ui.horizontal(|ui| { + // Sprite + ui.horizontal(|ui| { + ui.set_min_size(sprite_size); + ui.set_max_size(sprite_size); + if let Some((_, rendered_texture_id)) = + ui_assets.sprite_map.get(&pokemon.key()) + { + ui.add(egui::Image::new(*rendered_texture_id, sprite_size)); + } + }); + + ui.add_space(32.); + + ui.vertical(|ui| { + for stat in BaseStat::all() { + ui.horizontal(|ui| { + let mut column_size = COLUMNS.iter(); + + // Base stat number + ui.horizontal(|ui| { + if let Some(width) = column_size.next() { + ui.set_width(*width); + } + let base_stat = &pokemon.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.base_value(stat); + let bar_length = base_value as f32 * BASE_STAT_WIDTH_MULT; + let color = base_stat_color(base_value); + + 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.as_ref())), + ); + }); + + // IV stat + ui.horizontal(|ui| { + if let Some(width) = column_size.next() { + ui.set_width(*width); + } + if let Some(iv) = instance.ivs.get(&stat) { + ui.label( + egui::RichText::new(format!("{iv}")) + .color(iv_color(*iv)), + ); + } else { + ui.label("-"); + } + }); + + // IV stat bar + ui.horizontal(|ui| { + if let Some(width) = column_size.next() { + ui.set_width(*width); + } + if let Some(iv) = instance.ivs.get(&stat) { + let bar_length = *iv as f32 * IV_WIDTH_MULT; + let track_length = 31. * IV_WIDTH_MULT - bar_length; + let color = iv_color(*iv); + + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing = egui::Vec2::ZERO; + + // Bar + ui.add( + egui::Image::new( + *rendered_texture_id, + [bar_length, BAR_HEIGHT], + ) + .tint(color), + ); + // Track + ui.add( + egui::Image::new( + *rendered_texture_id, + [track_length, BAR_HEIGHT], + ) + .tint(egui::Color32::from_rgb(64, 64, 64)), + ); + }); + } + }); + }); + } + }); + }); + + ui.separator(); + } + }); }); } @@ -115,6 +249,17 @@ fn nature_color(stat: BaseStat, nature: Option<&Nature>) -> egui::Color32 { } } +fn base_stat_color(base_stat: u8) -> egui::Color32 { + for (treshold, color) in BASE_STAT_COLOR_RANGES.iter().rev() { + if base_stat >= *treshold { + return color.clone(); + } + } + BASE_STAT_COLOR_RANGES + .first() + .map_or(egui::Color32::LIGHT_GRAY, |(_, color)| color.clone()) +} + fn iv_color(iv: u8) -> egui::Color32 { for (treshold, color) in IV_COLOR_RANGES.iter().rev() { if iv >= *treshold {