use bevy::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, States, Hash)]
pub enum AppTab {
#[default]
DiceRoller,
CharacterSheet,
DndInfo,
Contributors,
}
#[derive(Resource, Default)]
pub struct UiState {
pub active_tab: AppTab,
pub character_list_open: bool,
pub selected_character_index: Option<usize>,
pub editing_field: Option<EditingField>,
pub show_save_confirmation: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EditingField {
CharacterName,
CharacterClass,
CharacterRace,
CharacterLevel,
AttributeStrength,
AttributeDexterity,
AttributeConstitution,
AttributeIntelligence,
AttributeWisdom,
AttributeCharisma,
ArmorClass,
Initiative,
Speed,
HitPointsCurrent,
HitPointsMaximum,
ProficiencyBonus,
Skill(String),
SavingThrow(String),
SkillLabel(String), SavingThrowLabel(String), }
#[derive(Component)]
pub struct TabBar;
#[derive(Component)]
pub struct TabButton {
pub tab: AppTab,
}
#[derive(Component)]
pub struct CharacterScreenRoot;
#[derive(Component)]
pub struct DndInfoScreenRoot;
#[derive(Component)]
pub struct InfoScrollContent;
#[derive(Component)]
pub struct CharacterListPanel;
#[derive(Component)]
pub struct CharacterListItem {
pub index: usize,
}
#[derive(Component)]
pub struct CharacterStatsPanel;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum GroupType {
BasicInfo,
Attributes,
Combat,
SavingThrows,
Skills,
}
#[derive(Component)]
pub struct StatGroup {
pub name: String,
pub group_type: GroupType,
}
#[derive(Component)]
pub struct GroupEditButton {
pub group_type: GroupType,
}
#[derive(Component)]
pub struct GroupAddButton {
pub group_type: GroupType,
}
#[derive(Component)]
pub struct DeleteEntryButton {
pub group_type: GroupType,
pub entry_id: String, }
#[derive(Component)]
pub struct EditableLabel {
pub group_type: GroupType,
pub label_id: String, }
#[derive(Resource, Default)]
pub struct GroupEditState {
pub editing_groups: std::collections::HashSet<GroupType>,
}
#[derive(Resource, Default)]
pub struct AddingEntryState {
pub adding_to: Option<GroupType>,
pub new_entry_name: String,
}
#[derive(Component)]
pub struct NewEntryInput {
pub group_type: GroupType,
}
#[derive(Component)]
pub struct NewEntryConfirmButton {
pub group_type: GroupType,
}
#[derive(Component)]
pub struct NewEntryCancelButton {
pub group_type: GroupType,
}
#[derive(Component)]
pub struct EditableLabelButton {
pub field: EditingField,
pub current_name: String,
}
#[derive(Component)]
pub struct EditableLabelText {
pub field: EditingField,
}
#[derive(Component)]
pub struct StatField {
pub field: EditingField,
pub is_numeric: bool,
}
#[derive(Component)]
pub struct StatFieldValue {
pub field: EditingField,
}
#[derive(Component)]
pub struct SaveButton;
#[derive(Component)]
pub struct NewCharacterButton;
#[derive(Component)]
pub struct SkillRow {
pub skill_name: String,
}
#[derive(Component)]
pub struct ProficiencyCheckbox {
pub target: ProficiencyTarget,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProficiencyTarget {
Skill(String),
SavingThrow(String),
}
#[derive(Component)]
pub struct ExpertiseCheckbox {
pub skill_name: String,
}
#[derive(Component)]
pub struct SavingThrowRow {
pub ability: String,
}
#[derive(Component)]
pub struct CharacterListItemText {
pub index: usize,
pub base_name: String,
}
#[derive(Component)]
pub struct ScrollableContent;
#[derive(Resource, Default)]
pub struct TextInputState {
pub active_field: Option<EditingField>,
pub current_text: String,
pub cursor_position: usize,
pub modified_fields: std::collections::HashSet<EditingField>,
}
#[derive(Component)]
pub struct ResultsText;
#[derive(Component)]
pub struct CommandInputText;
#[derive(Resource, Default)]
pub struct CommandInput {
pub text: String,
pub active: bool,
}
#[derive(Component)]
pub struct CommandHistoryList;
#[derive(Component)]
pub struct CommandHistoryItem {
pub index: usize,
}
#[derive(Resource, Default)]
pub struct CommandHistory {
pub commands: Vec<String>,
pub selected_index: Option<usize>,
}
impl CommandHistory {
pub fn add_command(&mut self, cmd: String) {
if !cmd.trim().is_empty() && !self.commands.contains(&cmd) {
self.commands.push(cmd);
}
}
}
#[derive(Resource)]
pub struct ZoomState {
pub level: f32, pub min_distance: f32,
pub max_distance: f32,
}
impl Default for ZoomState {
fn default() -> Self {
Self {
level: 0.3, min_distance: 4.0, max_distance: 25.0,
}
}
}
impl ZoomState {
pub fn get_distance(&self) -> f32 {
self.min_distance + self.level * (self.max_distance - self.min_distance)
}
}
#[derive(Component)]
pub struct ZoomSliderContainer;
#[derive(Component)]
pub struct ZoomSliderHandle;
#[derive(Component)]
pub struct ZoomSliderTrack;
#[derive(Component)]
pub struct DiceRollerRoot;
#[derive(Component)]
pub struct QuickRollPanel;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QuickRollType {
Skill(String),
AbilityCheck(String),
SavingThrow(String),
}
#[derive(Component)]
pub struct QuickRollButton {
pub roll_type: QuickRollType,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_history_add() {
let mut history = CommandHistory::default();
assert!(history.commands.is_empty());
history.add_command("--dice 2d6".to_string());
assert_eq!(history.commands.len(), 1);
assert_eq!(history.commands[0], "--dice 2d6");
history.add_command("--dice 2d6".to_string());
assert_eq!(history.commands.len(), 1);
history.add_command("--dice 1d20".to_string());
assert_eq!(history.commands.len(), 2);
history.add_command("".to_string());
history.add_command(" ".to_string());
assert_eq!(history.commands.len(), 2);
}
#[test]
fn test_command_input_default() {
let input = CommandInput::default();
assert!(input.text.is_empty());
assert!(!input.active);
}
#[test]
fn test_zoom_state_default() {
let zoom = ZoomState::default();
assert_eq!(zoom.level, 0.3);
assert_eq!(zoom.min_distance, 4.0);
assert_eq!(zoom.max_distance, 25.0);
}
#[test]
fn test_zoom_state_get_distance() {
let zoom = ZoomState::default();
assert!((zoom.get_distance() - 10.3).abs() < 0.01);
let zoom_min = ZoomState {
level: 0.0,
min_distance: 4.0,
max_distance: 25.0,
};
assert_eq!(zoom_min.get_distance(), 4.0);
let zoom_max = ZoomState {
level: 1.0,
min_distance: 4.0,
max_distance: 25.0,
};
assert_eq!(zoom_max.get_distance(), 25.0);
}
}