//! Play-mode status sidebar widget for kiln-tui. //! //! [`StatusSidebarWidget`] draws the player's persistent stats (health + gems) //! in a bordered block on the left of the screen during play. It is purely //! presentational: it reads `health`/`gems` values handed in at construction and //! never mutates game state. use crate::utils::rgba8_to_color; use kiln_core::Archetype; use kiln_core::cp437::tile_to_char; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::{Color, Style}; use ratatui::symbols::merge::MergeStrategy; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Paragraph, Widget}; /// How many hearts the health row always shows (the player's max health). const MAX_HEARTS: u32 = 5; /// A filled heart: bright red. const HEART_FULL: Color = Color::Rgb(255, 0, 0); /// A depleted heart: dark red. const HEART_EMPTY: Color = Color::Rgb(90, 0, 0); /// A ratatui widget that renders the play-mode status sidebar. /// /// Shows a `Health:` label above a row of [`MAX_HEARTS`] heart glyphs (the first /// `health` filled bright red, the rest dark red) and a `Gems: ♦ N` line. pub struct StatusSidebarWidget { /// The player's current health, clamped to [`MAX_HEARTS`] when drawn. health: u32, /// The number of gems collected, shown as a count. gems: u32, } impl StatusSidebarWidget { /// Builds a sidebar widget for the given player stats. pub fn new(health: u32, gems: u32) -> Self { Self { health, gems } } } impl Widget for StatusSidebarWidget { fn render(self, area: Rect, buf: &mut Buffer) { // One bright-red span per filled heart, dark-red for the rest, so a // partly-depleted bar reads at a glance. let filled = self.health.min(MAX_HEARTS); let hearts: Vec = (0..MAX_HEARTS) .map(|i| { let color = if i < filled { HEART_FULL } else { HEART_EMPTY }; Span::styled("♥", Style::default().fg(color)) }) .collect(); // Draw the gem indicator from the gem archetype's default glyph, so the // sidebar matches what gems look like on the board (no hardcoded ♦/color). let gem_glyph = Archetype::Gem.default_glyph(); let gem_char = tile_to_char(gem_glyph.tile).to_string(); let gem_style = Style::default().fg(rgba8_to_color(gem_glyph.fg)); let lines = vec![ Line::from("Health:"), Line::from(hearts), Line::from(""), Line::from(vec![ Span::raw("Gems: "), Span::styled(gem_char, gem_style), Span::raw(format!(" {}", self.gems)), ]), ]; // A bordered block; `merge_borders` joins its right edge with the board // block's left edge where the layout overlaps them by one column. let block = Block::bordered() .title(" Status (tab) ") .merge_borders(MergeStrategy::Exact); Paragraph::new(lines).block(block).render(area, buf); } }