skills ui revamp

Guide for skills ui revamp

Skills Tab UI Revamp

Purpose

This document describes the redesign of the Skills tab UI from a large-tile scrollable layout to a compact OSRS-style grid layout with detailed tooltips.

Problem Statement

Original Issues

  1. Oversized Tiles: Skill cells were too large, requiring a scrollbar
  2. Inefficient Space Usage: Only 3 skills visible at once in a 210px wide panel
  3. Redundant Text: Skill names displayed on every tile wasted space
  4. Missing XP Details: No way to see XP progress without opening a separate menu
  5. Incorrect Icons: Crafting and Fletching used placeholder icons instead of proper assets

User Requirements

  • Compact grid layout matching OSRS style
  • Show "Level/Level" format (current/base) instead of "Lvl X"
  • Remove skill names from tiles, show in tooltips instead
  • Tooltips should show: skill name, current XP, XP to next level, XP to target (future)
  • Remove Agility and Thieving (not in this game)
  • Rename "Prayer" to "Tapasya" (Indian mythology branding)
  • Use correct icons for Crafting and Fletching

Implementation

File: src/systems/ui/skills_ui.rs

Old Layout (Scrollable, Large Tiles)

egui::ScrollArea::vertical().show(ui, |ui| {
    egui::Grid::new("stats_grid")
        .spacing(egui::vec2(10.0, 10.0))
        .min_col_width(60.0)
        .show(ui, |ui| {
            for (i, (skill, name, _icon_handle)) in skill_list_grid.iter().enumerate() {
                let level = skills.get_level(*skill);
                let xp = skills.get_xp(*skill);
                
                render_skill_cell(ui, name, level as u8, xp as i32, tex_id);
                
                if (i + 1) % 3 == 0 {
                    ui.end_row();
                }
            }
        });
});

fn render_skill_cell(ui: &mut egui::Ui, name: &str, level: u8, xp: i32, tex_id: egui::TextureId) {
    let frame = egui::Frame::none()
        .inner_margin(4.0)
        .fill(egui::Color32::from_rgb(50, 45, 40))
        .rounding(4.0)
        .stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(80, 75, 70)));
        
    frame.show(ui, |ui| {
        ui.vertical_centered(|ui| {
            ui.image(egui::load::SizedTexture::new(tex_id, [24.0, 24.0]));
            ui.label(egui::RichText::new(format!("Lvl {}", level)).strong().color(egui::Color32::YELLOW));
            ui.label(egui::RichText::new(name).size(10.0)); // Wasted space
        }).response.on_hover_text(format!("XP: {}", xp)); // Minimal tooltip
    });
}
Problems:
  • Vertical layout with large icon + text + skill name
  • Simple tooltip with only XP
  • ScrollArea required for 18 skills

New Layout (Compact, No Scroll)

// Main Stats Container - No Scroll Area needed if it fits
// OSRS style: Black background, tight grid
egui::Frame::none()
    .fill(egui::Color32::from_rgb(20, 20, 20)) 
    .inner_margin(4.0)
    .show(ui, |ui| {
        
        ui.horizontal(|ui| {
             ui.heading("Skills");
             ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                 let total_level: i64 = skill_list_grid.iter().map(|(s, _, _)| skills.get_level(*s)).sum();
                 ui.label(egui::RichText::new(format!("Total Level: {}", total_level)).color(egui::Color32::GOLD));
             });
        });
        ui.separator();
        ui.add_space(4.0);

        let cell_size = egui::vec2(62.0, 30.0); // Compact size

        egui::Grid::new("stats_grid_compact")
            .spacing(egui::vec2(4.0, 4.0))
            .show(ui, |ui| {
                for (i, (skill, name, _)) in skill_list_grid.iter().enumerate() {
                    let level = skills.get_level(*skill);
                    let xp = skills.get_xp(*skill);
                    let boosted_level = level; // TODO: Get boosted level when implemented
                    
                    let tex_id = skill_textures.get(skill).copied().unwrap_or(egui::TextureId::default());

                    render_compact_skill_cell(ui, name, boosted_level as u8, level as u8, xp, tex_id, cell_size);

                    if (i + 1) % 3 == 0 {
                        ui.end_row();
                    }
                }
            });
    });

fn render_compact_skill_cell(
    ui: &mut egui::Ui, 
    minified_name: &str, 
    boosted: u8, 
    base: u8, 
    xp: i64, 
    tex_id: egui::TextureId,
    size: egui::Vec2
) {
    let frame = egui::Frame::none()
        .fill(egui::Color32::from_rgb(45, 40, 35))
        .stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(0, 0, 0))) // Black border
        .rounding(2.0)
        .inner_margin(2.0);

    let response = frame.show(ui, |ui| {
        ui.set_min_size(size);
        ui.horizontal(|ui| {
            ui.spacing_mut().item_spacing = egui::vec2(2.0, 0.0);
            
            // Left: Icon
            ui.image(egui::load::SizedTexture::new(tex_id, [20.0, 20.0]));
            
            // Right: Level/Level
            ui.vertical(|ui| {
                ui.label(
                    egui::RichText::new(format!("{}/{}", boosted, base))
                        .color(egui::Color32::YELLOW)
                        .size(12.0)
                );
            });
        });
    }).response;

    // Rich Tooltip
    if response.hovered() {
        response.on_hover_ui(|ui| {
            ui.heading(minified_name);
            ui.separator();
            ui.label(format!("Current XP: {}", xp));
            
            let next_lvl_xp = Skills::get_xp_for_level((base as i64) + 1);
            if base < 99 {
                ui.label(format!("Next Level at: {}", next_lvl_xp));
                ui.label(format!("Remaining: {}", next_lvl_xp - xp));
            } else {
                ui.label("Max Level Reached");
            }
        });
    }
}
Improvements:
  • Horizontal layout: Icon + Level/Level (62x30px cells)
  • No skill name on tile (shown in tooltip)
  • Rich tooltip with XP progress details
  • No ScrollArea needed (all skills fit)
  • Dark background (OSRS style)

Skill Grid Layout

Old Grid (18 skills with Agility/Thieving)

Attack      Hitpoints   Mining
Strength    Agility     Smithing
Defence     Ayurveda    Fishing
Ranged      Thieving    Cooking
Prayer      Crafting    Farming
Magic       Fletching   Woodcutting

New Grid (18 skills, removed Agility/Thieving, added Firemaking/Slayer)

Attack      Hitpoints   Mining
Strength    Smithing    Fishing
Defence     Ayurveda    Cooking
Ranged      Crafting    Firemaking
Tapasya     Farming     Magic
Fletching   Woodcutting Slayer
Changes:
  • Removed: Agility, Thieving
  • Renamed: Prayer → Tapasya
  • Added: Firemaking, Slayer (to fill grid)

Tooltip Implementation

Incorrect Approach (Compilation Error)

// WRONG: show_tooltip_text signature changed in egui 0.28
egui::show_tooltip_text(ui.ctx(), egui::Id::new(minified_name), |ui| {
    ui.heading(minified_name);
    // ...
});
Error:
error[E0061]: this function takes 4 arguments but 3 arguments were supplied

Correct Approach

// CORRECT: Use response.on_hover_ui
if response.hovered() {
    response.on_hover_ui(|ui| {
        ui.heading(minified_name);
        ui.separator();
        ui.label(format!("Current XP: {}", xp));
        
        let next_lvl_xp = Skills::get_xp_for_level((base as i64) + 1);
        if base < 99 {
            ui.label(format!("Next Level at: {}", next_lvl_xp));
            ui.label(format!("Remaining: {}", next_lvl_xp - xp));
        } else {
            ui.label("Max Level Reached");
        }
    });
}
Why this works:
  • response.on_hover_ui is the idiomatic egui 0.28 pattern
  • Automatically handles tooltip positioning and lifecycle
  • Closure receives &mut Ui for custom content

Icon Loading

File: src/systems/ui/mod.rs

Added Fields to UiAssets:
pub struct UiAssets {
    // ... existing fields
    pub crafting_icon: Handle<Image>,  // NEW
    pub fletching_icon: Handle<Image>, // NEW
}
Load Proper Icons:
fn load_ui_assets(mut ui_assets: ResMut<UiAssets>, asset_server: Res<AssetServer>) {
    // ... existing loads
    
    // [FIX] Proper icons
    ui_assets.crafting_icon = asset_server.load("logos/Crafting_icon.png");
    ui_assets.fletching_icon = asset_server.load("logos/Fletching_icon.png");
}
Use in Texture Map:
// OLD: Placeholder
skill_textures.insert(SkillType::Crafting, stats_tex); // Placeholder
skill_textures.insert(SkillType::Fletching, stats_tex); // Placeholder

// NEW: Proper icons
skill_textures.insert(SkillType::Crafting, contexts.add_image(ui_assets.crafting_icon.clone())); 
skill_textures.insert(SkillType::Fletching, contexts.add_image(ui_assets.fletching_icon.clone()));

Type Unification

Problem: Duplicate AttackStyle Enum

The UI module had its own AttackStyle enum that duplicated combat::Style:
// src/systems/ui/mod.rs (OLD)
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Resource)]
pub enum AttackStyle {
    #[default]
    Accurate,
    Aggressive,
    Defensive,
    Controlled,
}
This caused type mismatches when the Combat UI tried to use combat::Style.

Solution: Type Alias

// src/systems/ui/mod.rs (NEW)
// Use Combat System Style instead of local enum
pub use crate::systems::combat::Style as AttackStyle; // Alias for compatibility
Benefits:
  • Single source of truth for combat styles
  • No type conversion needed
  • Easier to extend with new styles (e.g., Longrange, Rapid)

Compilation Issues Encountered

Issue 1: Derive on Type Alias

#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Resource)]
pub use crate::systems::combat::Style as AttackStyle;
Error:
error[E0774]: `derive` may only be applied to `struct`s, `enum`s and `union`s
Fix: Remove #[derive] from type alias (traits come from original Style enum)

Issue 2: Missing Default Implementation

#[derive(Resource, Default)]
pub struct UiState {
    pub selected_attack_style: AttackStyle, // = combat::Style
}
Error:
error[E0277]: the trait bound `Style: Default` is not satisfied
Fix: Add #[derive(Default)] to combat::Style enum:
// src/systems/combat/mod.rs
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Style {
    #[default]
    Accurate,
    Aggressive,
    Defensive,
    Controlled,
    Longrange,
}

Issue 3: Color32::black() Not Found

.stroke(egui::Stroke::new(1.0, egui::Color32::black()))
Error:
error[E0599]: no function or associated item named `black` found for struct `Color32`
Fix: Use Color32::from_rgb(0, 0, 0) instead:
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(0, 0, 0)))

Design Rationale

Why Compact Layout?

  • Space Efficiency: 18 skills fit in 210px width without scrolling
  • OSRS Familiarity: Players expect this layout from RuneScape
  • Information Density: More skills visible at once

Why Level/Level Format?

  • Boosted Levels: Prepares for future potion/prayer boosts
  • Visual Clarity: Immediately shows if level is boosted (e.g., "105/99")
  • OSRS Standard: Matches player expectations

Why Tooltips for Skill Names?

  • Space Savings: Removes 10-15px of vertical space per cell
  • Cleaner Look: Icons + numbers are more compact
  • Rich Information: Tooltips can show much more than just the name

Why Remove Agility/Thieving?

  • Game Design: These skills don't fit the Indian mythology theme
  • Scope Management: Fewer skills = more focused gameplay

Why Rename Prayer to Tapasya?

  • Cultural Authenticity: Tapasya is the Sanskrit term for spiritual discipline
  • Branding: Differentiates from OSRS while maintaining similar mechanics

Testing Checklist

  • All 18 skills display in 3-column grid
  • No scrollbar appears
  • Level/Level format shows correctly
  • Tooltips appear on hover
  • Tooltips show: skill name, current XP, XP to next level
  • "Tapasya" appears instead of "Prayer"
  • Agility and Thieving are not displayed
  • Crafting icon loads correctly (not placeholder)
  • Fletching icon loads correctly (not placeholder)
  • Total Level displays in top-right corner
  • Grid spacing is tight (4px)
  • Cell size is 62x30px
  • Background is dark (OSRS style)

Future Enhancements

  1. Boosted Levels: Implement actual boosted level tracking for potions/prayers
  2. XP Target: Allow players to set target levels, show "XP to Target" in tooltip
  3. Skill Ordering: Make grid order configurable (player preference)
  4. Color Coding: Highlight boosted levels in green, debuffed in red
  5. Progress Bar: Show XP progress bar in tooltip
  6. Skill Icons: Create custom icons for all skills (remove placeholders)
  7. Virtual Levels: Show levels beyond 99 (e.g., "120/99" for 200M XP)
  • src/systems/ui/skills_ui.rs: Skills tab rendering
  • src/systems/ui/mod.rs: Icon loading and type aliases
  • src/systems/player/skills.rs: Skills component and XP calculations
  • assets/logos/*.png: Skill icons