From f7c1795f29daefb3086b6604c914e9d6f10a6cc7 Mon Sep 17 00:00:00 2001 From: GarandPLG Date: Sun, 26 Apr 2026 21:04:19 +0200 Subject: [PATCH] Add max durability and enhance side panel UI - Introduce `max_durability` fields to BaseBuilding, Stone, and Tunnel structures. - Extend `Structure` trait with `get_max_durability` and implement it in all structures. - Update `Structures` enum to forward `get_max_durability`. - Add color utilities and `get_span` method to `Players` enum for styled text. - Refactor `CellWidget` method names (`get_option_unit`, `set_structure`). - Revise side panel widget to accept references to `Structures` and `Option`, render colored blocks and detailed stats. - Simplify skirmish view to use `BoardState` directly and adapt side panel rendering. --- src/app/states/skirmish.rs | 27 ++++ .../states/skirmish_states/structures/base.rs | 6 + .../skirmish_states/structures/stone.rs | 6 + .../structures/structures_enum.rs | 4 + .../structures/structures_trait.rs | 1 + .../skirmish_states/structures/tunnel.rs | 6 + src/app/views/skirmish.rs | 33 ++--- src/app/widgets/cell.rs | 6 +- src/app/widgets/side_panel.rs | 138 +++++++++++++----- 9 files changed, 165 insertions(+), 62 deletions(-) diff --git a/src/app/states/skirmish.rs b/src/app/states/skirmish.rs index 2bd381b..d37eef1 100644 --- a/src/app/states/skirmish.rs +++ b/src/app/states/skirmish.rs @@ -1,5 +1,9 @@ use crate::app::states::skirmish_states::BoardState; use clap::ValueEnum; +use ratatui::{ + style::{Color, Stylize}, + text::Span, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SkirmishState { @@ -20,3 +24,26 @@ pub enum Players { Player, Enemy, } + +impl Players { + pub fn get_text(&self) -> &'static str { + match self { + Self::Player => "Player", + Self::Enemy => "Enemy", + } + } + + pub fn get_color(&self) -> Color { + match self { + Self::Player => Color::LightBlue, + Self::Enemy => Color::LightRed, + } + } + + pub fn get_span(&self) -> Span<'static> { + match self { + Self::Player => self.get_text().fg(self.get_color()), + Self::Enemy => self.get_text().fg(self.get_color()), + } + } +} diff --git a/src/app/states/skirmish_states/structures/base.rs b/src/app/states/skirmish_states/structures/base.rs index 7ffa4d3..4df161f 100644 --- a/src/app/states/skirmish_states/structures/base.rs +++ b/src/app/states/skirmish_states/structures/base.rs @@ -4,6 +4,7 @@ use ratatui::style::Color; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BaseBuilding { durability: u16, + max_durability: u16, stress: u8, owner: Players, level: u8, @@ -13,6 +14,7 @@ impl BaseBuilding { pub fn new(owner: Players) -> Self { Self { durability: 1500, + max_durability: 1500, stress: 0, owner, level: b'1', @@ -44,6 +46,10 @@ impl Structure for BaseBuilding { self.durability } + fn get_max_durability(&self) -> u16 { + self.max_durability + } + fn get_stress(&self) -> u8 { self.stress } diff --git a/src/app/states/skirmish_states/structures/stone.rs b/src/app/states/skirmish_states/structures/stone.rs index 27a096c..6e18bd8 100644 --- a/src/app/states/skirmish_states/structures/stone.rs +++ b/src/app/states/skirmish_states/structures/stone.rs @@ -4,6 +4,7 @@ use ratatui::style::Color; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Stone { durability: u16, + max_durability: u16, stress: u8, } @@ -11,6 +12,7 @@ impl Stone { pub fn new() -> Self { Self { durability: 1000, + max_durability: 1000, stress: 0, } } @@ -33,6 +35,10 @@ impl Structure for Stone { self.durability } + fn get_max_durability(&self) -> u16 { + self.max_durability + } + fn get_stress(&self) -> u8 { self.stress } diff --git a/src/app/states/skirmish_states/structures/structures_enum.rs b/src/app/states/skirmish_states/structures/structures_enum.rs index 8add3bc..d805c27 100644 --- a/src/app/states/skirmish_states/structures/structures_enum.rs +++ b/src/app/states/skirmish_states/structures/structures_enum.rs @@ -40,6 +40,10 @@ impl Structure for Structures { self.structure().get_durability() } + fn get_max_durability(&self) -> u16 { + self.structure().get_max_durability() + } + fn get_stress(&self) -> u8 { self.structure().get_stress() } diff --git a/src/app/states/skirmish_states/structures/structures_trait.rs b/src/app/states/skirmish_states/structures/structures_trait.rs index 39d6b2e..4acf425 100644 --- a/src/app/states/skirmish_states/structures/structures_trait.rs +++ b/src/app/states/skirmish_states/structures/structures_trait.rs @@ -6,5 +6,6 @@ pub trait Structure { fn get_level(&self) -> char; fn get_stress(&self) -> u8; fn get_durability(&self) -> u16; + fn get_max_durability(&self) -> u16; fn get_name(&self) -> &'static str; } diff --git a/src/app/states/skirmish_states/structures/tunnel.rs b/src/app/states/skirmish_states/structures/tunnel.rs index 129da67..685e4af 100644 --- a/src/app/states/skirmish_states/structures/tunnel.rs +++ b/src/app/states/skirmish_states/structures/tunnel.rs @@ -4,6 +4,7 @@ use ratatui::style::Color; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Tunnel { durability: u16, + max_durability: u16, stress: u8, roof_support: bool, rail: bool, @@ -14,6 +15,7 @@ impl Tunnel { pub fn new() -> Self { Self { durability: 500, + max_durability: 500, stress: 25, roof_support: false, rail: false, @@ -39,6 +41,10 @@ impl Structure for Tunnel { self.durability } + fn get_max_durability(&self) -> u16 { + self.max_durability + } + fn get_stress(&self) -> u8 { self.stress } diff --git a/src/app/views/skirmish.rs b/src/app/views/skirmish.rs index 446261e..299875f 100644 --- a/src/app/views/skirmish.rs +++ b/src/app/views/skirmish.rs @@ -2,7 +2,7 @@ use crate::app::{ App, helpers::block_title_helper, keybindings::{Action, count_largest_group}, - states::skirmish_states::{structures::Structure, units::Unit}, + states::skirmish_states::BoardState, widgets::{BoardWidget, KeybindingsWidget, SidePanelWidget}, }; use ratatui::{ @@ -57,6 +57,7 @@ pub fn skirmish_main_area_layout(area: Rect, side_panel: bool) -> [Rect; 2] { pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) { let Some(states) = app.states() else { return }; + let board: &BoardState = &states.skirmish.board; let [title_area, main_area, keybindings_area] = skirmish_layout(area); @@ -115,33 +116,25 @@ pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) { }); // .centered( // Constraint::Length( - // (states.skirmish.board.cell_width * states.skirmish.board.cols) as u16, + // (board.cell_width * board.cols) as u16, // ), // Constraint::Length( - // (states.skirmish.board.cell_height * states.skirmish.board.rows) as u16, + // (board.cell_height * board.rows) as u16, // ), // ); - BoardWidget::new(&states.skirmish.board).render(cells_area, buf); + BoardWidget::new(&board).render(cells_area, buf); if states.skirmish.side_panel { - let row: usize = states.skirmish.board.get_focused_cell().get_row(); - let col: usize = states.skirmish.board.get_focused_cell().get_col(); - let coords: (usize, usize) = (row, col); - let structure_name: &str = states - .skirmish - .board - .get_ref_cell(row, col) - .get_structure() - .get_name(); - let unit_name: &str = states - .skirmish - .board - .get_ref_cell(row, col) - .get_unit_option() - .get_name(); + let row: usize = board.get_focused_cell().get_row(); + let col: usize = board.get_focused_cell().get_col(); - SidePanelWidget::new(coords, structure_name, unit_name).render(side_panel_area, buf); + SidePanelWidget::new( + (row, col), + &board.get_ref_cell(row, col).get_structure(), + &board.get_ref_cell(row, col).get_option_unit(), + ) + .render(side_panel_area, buf); } } diff --git a/src/app/widgets/cell.rs b/src/app/widgets/cell.rs index 9619e9f..a19248e 100644 --- a/src/app/widgets/cell.rs +++ b/src/app/widgets/cell.rs @@ -56,12 +56,12 @@ impl CellWidget { self.structure } - pub fn get_unit_option(&self) -> Option { + pub fn get_option_unit(&self) -> Option { self.unit } - pub fn set_structure(&mut self, sctructure: Structures) -> &mut Self { - self.structure = sctructure; + pub fn set_structure(&mut self, structure: Structures) -> &mut Self { + self.structure = structure; self } diff --git a/src/app/widgets/side_panel.rs b/src/app/widgets/side_panel.rs index 938829f..5dc8f8f 100644 --- a/src/app/widgets/side_panel.rs +++ b/src/app/widgets/side_panel.rs @@ -1,30 +1,31 @@ +use crate::app::{ + helpers::block_single_title_helper, + states::skirmish_states::{ + structures::{Structure, Structures}, + units::{Unit, Units}, + }, +}; use ratatui::{ buffer::Buffer, - layout::{Alignment, Rect}, - style::Color, - text::Line, - widgets::{Block, BorderType, Borders, Paragraph, Widget}, + layout::{Alignment, Constraint, Layout, Margin, Rect}, + style::{Color, Stylize}, + text::{Line, Span}, + widgets::{Block, BorderType, Borders, Padding, Paragraph, Widget}, }; -use crate::app::helpers::block_title_helper; - #[derive(Debug, Clone, PartialEq, Eq)] -pub struct SidePanelWidget { +pub struct SidePanelWidget<'a> { coords: (usize, usize), - structure_name: &'static str, - unit_name: &'static str, + structure: &'a Structures, + unit: &'a Option, } -impl SidePanelWidget { - pub fn new( - coords: (usize, usize), - structure_name: &'static str, - unit_name: &'static str, - ) -> Self { +impl<'a> SidePanelWidget<'a> { + pub fn new(coords: (usize, usize), structure: &'a Structures, unit: &'a Option) -> Self { Self { coords, - structure_name, - unit_name, + structure, + unit, } } @@ -40,35 +41,94 @@ impl SidePanelWidget { letters.iter().rev().collect() } - fn get_title(&self) -> Line<'_> { - let cell_coords: String = format!("{}{}", self.col_to_letters(), self.coords.0); - - let mut texts: Vec<(String, Color)> = Vec::with_capacity(3); - - texts.push((cell_coords, Color::Yellow)); - texts.push((self.structure_name.to_string(), Color::Cyan)); - - if !self.unit_name.is_empty() { - texts.push((self.unit_name.to_string(), Color::Green)); - } - - block_title_helper(&texts, Some(" - ")) - } - - fn get_block(&self) -> Block<'_> { + fn get_outer_block(&self) -> Block<'_> { Block::default() .borders(Borders::ALL) .border_type(BorderType::Double) - .title(self.get_title()) + .title(block_single_title_helper( + format!("{}{}", self.col_to_letters(), self.coords.0), + Color::Yellow, + )) .title_alignment(Alignment::Center) } + + fn get_inner_block<'b>(&self, title: String, color: Color) -> Block<'b> { + Block::default() + .borders(Borders::LEFT | Borders::TOP) + .title(block_single_title_helper(title, color)) + .title_alignment(Alignment::Center) + .padding(Padding::symmetric(1, 1)) + } + + fn get_area_constraints(&self) -> [Constraint; 2] { + if !self.unit.get_name().is_empty() { + [Constraint::Percentage(50), Constraint::Percentage(50)] + } else { + [Constraint::Percentage(100), Constraint::Percentage(0)] + } + } + + fn structure_text(&self) -> Vec> { + let s: &Structures = self.structure; + let durability: u16 = s.get_durability(); + let max_durability: u16 = s.get_max_durability(); + let durability_percent: u16 = durability * 100 / max_durability; + let stress: u8 = s.get_stress(); + let level: char = s.get_level(); + + let mut lines: Vec> = vec![ + Line::from_iter([ + "Durability: ".gray(), + durability.to_string().cyan(), + "/".gray(), + max_durability.to_string().cyan(), + " ( ".gray(), + durability_percent.to_string().cyan(), + "% )".gray(), + ]), + Line::from_iter(["Stress: ".gray(), stress.to_string().cyan(), "%".gray()]), + ]; + + if level != ' ' { + lines.push(Line::from_iter([ + "Level: ".gray(), + level.to_string().cyan(), + ])); + } + + lines + } + + fn unit_text(&self) -> Vec> { + let u: &Option = self.unit; + let owner: Span<'_> = u.get_owner().get_span(); + + vec![Line::from_iter(vec!["Owner: ".gray(), owner])] + } } -impl Widget for SidePanelWidget { +impl Widget for SidePanelWidget<'_> { fn render(self, area: Rect, buf: &mut Buffer) { - Paragraph::default() - .alignment(Alignment::Center) - .block(self.get_block()) - .render(area, buf); + let block: Block<'_> = self.get_outer_block(); + let inner_area: Rect = block.inner(area).inner(Margin { + horizontal: 2, + vertical: 1, + }); + block.render(area, buf); + + let [structure_area, unit_area] = + Layout::vertical(self.get_area_constraints()).areas(inner_area); + + Paragraph::new(self.structure_text()) + .alignment(Alignment::Left) + .block(self.get_inner_block(self.structure.get_name().to_string(), Color::Cyan)) + .render(structure_area, buf); + + if !self.unit.get_name().is_empty() { + Paragraph::new(self.unit_text()) + .alignment(Alignment::Left) + .block(self.get_inner_block(self.unit.get_name().to_string(), Color::Green)) + .render(unit_area, buf); + } } }