ui integration
Guide for ui integration
UI Integration
File: src/systems/ui/mod.rs
This document describes how the weapon interface system is integrated into the main UI layout.
System Parameters Update
Added GameData to UiParams
Before:
#[derive(bevy::ecs::system::SystemParam)]
pub struct UiParams<'w, 's> {
pub ui_assets: Res<'w, UiAssets>,
pub interactables: Query<'w, 's, &'static crate::systems::api::Interactable>,
pub player_q: Query<'w, 's, (Entity, &'static crate::systems::player::skills::Skills, Option<&'static crate::systems::skills::magic::ActiveSpell>), With<crate::systems::player::Player>>,
pub skills_q: Query<'w, 's, &'static crate::systems::player::skills::Skills>,
pub inventory_q: Query<'w, 's, &'static crate::systems::player::inventory::Inventory>,
pub equipment_q: Query<'w, 's, &'static crate::systems::player::inventory::Equipment>,
pub skills_data: Res<'w, crate::systems::data::SkillsData>,
pub spell_db: Res<'w, crate::systems::magic::SpellDatabase>,
}After:
#[derive(bevy::ecs::system::SystemParam)]
pub struct UiParams<'w, 's> {
pub ui_assets: Res<'w, UiAssets>,
pub interactables: Query<'w, 's, &'static crate::systems::api::Interactable>,
pub player_q: Query<'w, 's, (Entity, &'static crate::systems::player::skills::Skills, Option<&'static crate::systems::skills::magic::ActiveSpell>), With<crate::systems::player::Player>>,
pub skills_q: Query<'w, 's, &'static crate::systems::player::skills::Skills>,
pub inventory_q: Query<'w, 's, &'static crate::systems::player::inventory::Inventory>,
pub equipment_q: Query<'w, 's, &'static crate::systems::player::inventory::Equipment>,
pub skills_data: Res<'w, crate::systems::data::SkillsData>,
pub game_data: Res<'w, crate::systems::data::GameData>, // NEW
pub spell_db: Res<'w, crate::systems::magic::SpellDatabase>,
}Purpose: Access to
GameData allows looking up ItemDefinition for equipped weapons.Combat Tab Rendering
Old Implementation (Hardcoded)
UiTab::Combat => {
ui.heading("Combat");
ui.separator();
let mut combat_level = 3;
if let Ok((player_entity, _, _)) = player_q.get_single() {
if let Ok(skills) = skills_q.get(player_entity) {
combat_level = skills.get_combat_level();
}
}
ui.label(format!("Level: {}", combat_level));
ui.add_space(10.0);
// HARDCODED STYLES
let styles = [
("Accurate", AttackStyle::Accurate),
("Aggressive", AttackStyle::Aggressive),
("Defensive", AttackStyle::Defensive),
("Controlled", AttackStyle::Controlled),
];
ui.columns(2, |columns| {
for (i, (name, style)) in styles.iter().enumerate() {
let is_selected = ui_state.selected_attack_style == *style;
let btn = egui::Button::new(*name).selected(is_selected).min_size(egui::Vec2::new(80.0, 30.0));
if columns[i % 2].add(btn).clicked() {
ui_state.selected_attack_style = *style;
combat_style_writer.send(CombatStyleEvent(*style));
}
}
});
ui.add_space(10.0);
ui.label("Auto Retaliate: On");
},New Implementation (Dynamic)
UiTab::Combat => {
ui.heading("Combat");
ui.separator();
// Get Combat Level from Player
let mut combat_level = 3;
if let Ok((player_entity, _, _)) = player_q.get_single() {
if let Ok(skills) = skills_q.get(player_entity) {
combat_level = skills.get_combat_level();
}
}
ui.label(format!("Level: {}", combat_level));
ui.add_space(10.0);
// STEP 1: Get Equipped Weapon
let mut equipped_weapon_def = None;
if let Ok(equipment) = equipment_q.get(player_entity) {
if let Some(slot) = &equipment.weapon {
equipped_weapon_def = params.game_data.items.get(&slot.id.0);
}
}
// STEP 2: Get Weapon Interface
let interface = crate::systems::combat::weapon_styles::get_weapon_interface(equipped_weapon_def);
// STEP 3: Display Weapon Name
ui.label(egui::RichText::new(&interface.name).strong().color(egui::Color32::WHITE));
ui.add_space(10.0);
let btn_size = egui::Vec2::new(90.0, 40.0);
// STEP 4: Render Dynamic Buttons
ui.columns(2, |columns| {
for (i, option) in interface.styles.iter().enumerate() {
// Check if this option matches current selected style
let is_selected = ui_state.selected_attack_style == option.style;
let btn = egui::Button::new(egui::RichText::new(&option.name).size(14.0))
.selected(is_selected)
.min_size(btn_size);
if columns[i % 2].add(btn).on_hover_text(&option.description).clicked() {
ui_state.selected_attack_style = option.style;
combat_style_writer.send(CombatStyleEvent(option.style));
// TODO: Update combat type in backend if needed
}
}
});
ui.add_space(15.0);
// STEP 5: Auto-Engage Toggle Button
let toggle_text = if true { "ON" } else { "OFF" }; // TODO: Read actual state
let retaliate_color = if true { egui::Color32::GREEN } else { egui::Color32::RED };
if ui.add(egui::Button::new(
egui::RichText::new(format!("Auto-Engage: {}", toggle_text))
.color(retaliate_color)
).min_size(egui::Vec2::new(180.0, 30.0))).clicked() {
// TODO: Toggle auto retaliate component
}
},Key Changes
1. Weapon Lookup
let mut equipped_weapon_def = None;
if let Ok(equipment) = equipment_q.get(player_entity) {
if let Some(slot) = &equipment.weapon {
equipped_weapon_def = params.game_data.items.get(&slot.id.0);
}
}Purpose: Retrieves the
ItemDefinition for the equipped weapon from GameData.Fallback: If no weapon is equipped,
equipped_weapon_def remains None, which triggers the unarmed interface.2. Interface Retrieval
let interface = crate::systems::combat::weapon_styles::get_weapon_interface(equipped_weapon_def);Purpose: Gets the appropriate
WeaponInterface based on weapon type.Returns:
WeaponInterface struct with weapon name and available combat styles.3. Weapon Name Display
ui.label(egui::RichText::new(&interface.name).strong().color(egui::Color32::WHITE));Purpose: Shows "Unarmed", "Sword", "Bow", etc. to inform the player.
Styling: Bold white text for visibility.
4. Dynamic Button Rendering
ui.columns(2, |columns| {
for (i, option) in interface.styles.iter().enumerate() {
let is_selected = ui_state.selected_attack_style == option.style;
let btn = egui::Button::new(egui::RichText::new(&option.name).size(14.0))
.selected(is_selected)
.min_size(btn_size);
if columns[i % 2].add(btn).on_hover_text(&option.description).clicked() {
ui_state.selected_attack_style = option.style;
combat_style_writer.send(CombatStyleEvent(option.style));
}
}
});Key Features:
- Dynamic Count: Renders 3 or 4 buttons depending on weapon type
- Hover Tooltips: Shows style descriptions on hover
- Selection State: Highlights currently selected style
- Event Emission: Sends
CombatStyleEventto update combat system
5. Auto-Engage Button
let toggle_text = if true { "ON" } else { "OFF" }; // TODO: Read actual state
let retaliate_color = if true { egui::Color32::GREEN } else { egui::Color32::RED };
if ui.add(egui::Button::new(
egui::RichText::new(format!("Auto-Engage: {}", toggle_text))
.color(retaliate_color)
).min_size(egui::Vec2::new(180.0, 30.0))).clicked() {
// TODO: Toggle auto retaliate component
}Features:
- Renamed: "Auto Retaliate" → "Auto-Engage"
- Visual Feedback: Green for ON, Red for OFF
- Full-Width Button: 180px wide for prominence
- TODO: Implement actual toggle logic
Data Flow
User Equips Weapon
│
▼
Equipment Component Updated
│
▼
UI System Queries Equipment
│
▼
Lookup ItemDefinition in GameData
│
▼
Call get_weapon_interface(item_def)
│
▼
Weapon Type Detection (string matching)
│
▼
Return WeaponInterface
│
▼
Render Dynamic Buttons
│
▼
User Clicks Style Button
│
▼
Send CombatStyleEvent
│
▼
Combat System Updates AttackStyle ComponentStyle Selection Persistence
The selected style is stored in
UiState:#[derive(Resource, Default)]
pub struct UiState {
pub active_tab: UiTab,
pub selected_attack_style: AttackStyle, // Persisted selection
pub active_menu: Option<ActiveMenu>,
}Behavior:
- Selection persists when switching weapons
- If new weapon doesn't support current style, it remains selected but may not be optimal
- Future Enhancement: Auto-select first available style when weapon changes
Event Handling
CombatStyleEvent
#[derive(Event)]
pub struct CombatStyleEvent(pub AttackStyle);Sent When: User clicks a combat style button
Handled By: Combat system (updates
AttackStyle component on player entity)Purpose: Synchronizes UI selection with combat logic
TODO Items
- Auto-Engage State Management:
- Add
AutoRetaliatecomponent to player entity - Read actual state instead of hardcoded
true - Implement toggle logic in event handler
- Add
- Combat Type Propagation:
- Currently only
Styleis sent via event - May need to also update
CombatTypewhen style changes - Consider adding
CombatTypetoCombatStyleEvent
- Currently only
- Style Validation:
- Prevent selecting styles not available for current weapon
- Auto-select first available style on weapon change
- Visual Polish:
- Add weapon icon next to weapon name
- Animate button selection
- Add sound effects for style changes
Testing Scenarios
- Weapon Switching:
- Equip sword → verify 4 buttons (Chop, Slash, Lunge, Block)
- Equip bow → verify 3 buttons (Precision, Burst, Snipe)
- Unequip weapon → verify 3 buttons (Punch, Kick, Block)
- Style Selection:
- Click each button → verify selection highlight
- Hover over buttons → verify tooltips appear
- Switch weapons → verify selection persists
- Auto-Engage:
- Click button → verify color changes (when implemented)
- Verify combat behavior matches toggle state
- Edge Cases:
- Unknown weapon type → verify generic interface
- Corrupted item data → verify graceful fallback
- Rapid weapon switching → verify no UI glitches