Skill tree integration + tons of random fixes
This commit is contained in:
133
src/ui/components/branch_progress_list.rs
Normal file
133
src/ui/components/branch_progress_list.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Paragraph, Widget};
|
||||
|
||||
use crate::engine::skill_tree::{BranchId, DrillScope, SkillTree, get_branch_definition};
|
||||
use crate::ui::theme::Theme;
|
||||
|
||||
pub struct BranchProgressList<'a> {
|
||||
pub skill_tree: &'a SkillTree,
|
||||
pub key_stats: &'a crate::engine::key_stats::KeyStatsStore,
|
||||
pub drill_scope: DrillScope,
|
||||
pub active_branches: &'a [BranchId],
|
||||
pub theme: &'a Theme,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl Widget for BranchProgressList<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let colors = &self.theme.colors;
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
|
||||
let drill_branch = match self.drill_scope {
|
||||
DrillScope::Branch(id) => Some(id),
|
||||
DrillScope::Global => None,
|
||||
};
|
||||
|
||||
let show_all = self.height > 2;
|
||||
|
||||
if show_all {
|
||||
for &branch_id in self.active_branches {
|
||||
if lines.len() as u16 >= self.height.saturating_sub(1) {
|
||||
break;
|
||||
}
|
||||
let def = get_branch_definition(branch_id);
|
||||
let total = SkillTree::branch_total_keys(branch_id);
|
||||
let unlocked = self.skill_tree.branch_unlocked_count(branch_id);
|
||||
let mastered = self
|
||||
.skill_tree
|
||||
.branch_confident_keys(branch_id, self.key_stats);
|
||||
let is_active = drill_branch == Some(branch_id);
|
||||
let prefix = if is_active {
|
||||
" \u{25b6} "
|
||||
} else {
|
||||
" \u{00b7} "
|
||||
};
|
||||
let (m_bar, u_bar, e_bar) = compact_dual_bar_parts(mastered, unlocked, total, 12);
|
||||
let name = format!("{:<14}", def.name);
|
||||
let label_color = if is_active {
|
||||
colors.accent()
|
||||
} else {
|
||||
colors.text_pending()
|
||||
};
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(prefix, Style::default().fg(label_color)),
|
||||
Span::styled(name, Style::default().fg(label_color)),
|
||||
Span::styled(m_bar, Style::default().fg(colors.text_correct())),
|
||||
Span::styled(u_bar, Style::default().fg(colors.accent())),
|
||||
Span::styled(e_bar, Style::default().fg(colors.text_pending())),
|
||||
Span::styled(
|
||||
format!(" {unlocked}/{total}"),
|
||||
Style::default().fg(colors.text_pending()),
|
||||
),
|
||||
]));
|
||||
}
|
||||
} else if let Some(branch_id) = drill_branch {
|
||||
let def = get_branch_definition(branch_id);
|
||||
let total = SkillTree::branch_total_keys(branch_id);
|
||||
let unlocked = self.skill_tree.branch_unlocked_count(branch_id);
|
||||
let mastered = self
|
||||
.skill_tree
|
||||
.branch_confident_keys(branch_id, self.key_stats);
|
||||
let (m_bar, u_bar, e_bar) = compact_dual_bar_parts(mastered, unlocked, total, 12);
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(
|
||||
format!(" \u{25b6} {:<14}", def.name),
|
||||
Style::default().fg(colors.accent()),
|
||||
),
|
||||
Span::styled(m_bar, Style::default().fg(colors.text_correct())),
|
||||
Span::styled(u_bar, Style::default().fg(colors.accent())),
|
||||
Span::styled(e_bar, Style::default().fg(colors.text_pending())),
|
||||
Span::styled(
|
||||
format!(" {unlocked}/{total}"),
|
||||
Style::default().fg(colors.text_pending()),
|
||||
),
|
||||
]));
|
||||
}
|
||||
|
||||
// Overall line
|
||||
if lines.len() < self.height as usize {
|
||||
let total = self.skill_tree.total_unique_keys;
|
||||
let unlocked = self.skill_tree.total_unlocked_count();
|
||||
let mastered = self.skill_tree.total_confident_keys(self.key_stats);
|
||||
let (m_bar, u_bar, e_bar) = compact_dual_bar_parts(mastered, unlocked, total, 12);
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(
|
||||
format!(" {:<14}", "Overall"),
|
||||
Style::default().fg(colors.fg()),
|
||||
),
|
||||
Span::styled(m_bar, Style::default().fg(colors.text_correct())),
|
||||
Span::styled(u_bar, Style::default().fg(colors.accent())),
|
||||
Span::styled(e_bar, Style::default().fg(colors.text_pending())),
|
||||
Span::styled(
|
||||
format!(" {unlocked}/{total}"),
|
||||
Style::default().fg(colors.text_pending()),
|
||||
),
|
||||
]));
|
||||
}
|
||||
|
||||
let paragraph = Paragraph::new(lines);
|
||||
paragraph.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn compact_dual_bar_parts(
|
||||
mastered: usize,
|
||||
unlocked: usize,
|
||||
total: usize,
|
||||
width: usize,
|
||||
) -> (String, String, String) {
|
||||
if total == 0 {
|
||||
return (String::new(), String::new(), "\u{2591}".repeat(width));
|
||||
}
|
||||
let mastered_cells = mastered * width / total;
|
||||
let unlocked_cells = (unlocked * width / total).max(mastered_cells);
|
||||
let empty_cells = width - unlocked_cells;
|
||||
(
|
||||
"\u{2588}".repeat(mastered_cells),
|
||||
"\u{2593}".repeat(unlocked_cells - mastered_cells),
|
||||
"\u{2591}".repeat(empty_cells),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user