From cf843057c3ed0064506feb7810237e7307189bc2 Mon Sep 17 00:00:00 2001 From: GarandPLG Date: Fri, 24 Apr 2026 11:41:09 +0200 Subject: [PATCH] Add side panel and refactor title helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a `side_panel` flag throughout the skirmish state, board creation, and cell area calculation to enable a detachable side panel. Refactor the title helper to own its strings and use a static separator, updating the single‑title helper accordingly. Add a new `SidePanelWidget` and expose the `skirmish_main_area_layout` helper. Extend keybindings with `Tab` and `ShiftTab` actions under a new `Opener` group. Update structures and units to implement a `get_name` method and adjust related traits and imports. --- src/app/app.rs | 5 +- src/app/helpers/block_title.rs | 89 +++---------------- src/app/helpers/cells_area.rs | 8 +- src/app/keybindings/keybindings.rs | 24 +++++ src/app/keybindings/skirmish.rs | 8 +- src/app/state.rs | 2 + src/app/states/skirmish.rs | 1 + src/app/states/skirmish_states/board.rs | 26 ++++-- .../states/skirmish_states/structures/base.rs | 4 + .../skirmish_states/structures/stone.rs | 4 + .../structures/structures_enum.rs | 50 ++++++++--- .../structures/structures_trait.rs | 1 + .../skirmish_states/structures/tunnel.rs | 4 + src/app/states/skirmish_states/units/miner.rs | 4 + src/app/states/skirmish_states/units/mod.rs | 2 +- .../skirmish_states/units/units_enum.rs | 35 ++++++-- .../skirmish_states/units/units_trait.rs | 5 +- src/app/views/mod.rs | 2 +- src/app/views/skirmish.rs | 48 ++++++++-- src/app/widgets/cell.rs | 17 +++- src/app/widgets/keybindings.rs | 10 ++- src/app/widgets/mod.rs | 2 + src/app/widgets/side_panel.rs | 74 +++++++++++++++ 23 files changed, 302 insertions(+), 123 deletions(-) create mode 100644 src/app/widgets/side_panel.rs diff --git a/src/app/app.rs b/src/app/app.rs index 78a4bee..bf84680 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -60,7 +60,10 @@ impl App { let Some(state) = self.states_mut() else { panic!("State issue") }; - state.skirmish.board.change_resize(&window_area); + state + .skirmish + .board + .change_resize(&window_area, state.skirmish.side_panel); } } } diff --git a/src/app/helpers/block_title.rs b/src/app/helpers/block_title.rs index 515e03d..ab320d1 100644 --- a/src/app/helpers/block_title.rs +++ b/src/app/helpers/block_title.rs @@ -5,53 +5,19 @@ use ratatui::{ /// Creates a styled title `Line` for a UI block. /// -/// This helper builds a `Line` that looks like: -/// ```text -/// [ ... ] -/// ``` -/// where each title fragment is rendered in the color supplied by the caller, -/// and the surrounding brackets and any separator are rendered in gray. -/// -/// # Arguments -/// -/// * `texts` – A slice of tuples. Each tuple contains a title fragment (`&str`) -/// and a `Color` that should be applied to that fragment. The order of the -/// tuples determines the order of the fragments in the final line. -/// -/// * `separator` – An optional string that will be inserted (in gray) between -/// successive title fragments. If `None` the fragments are concatenated -/// directly without any separator. -/// -/// # Returns -/// -/// A `Line<'a>` that starts with a gray “\[ ”, contains the colored title -/// fragments separated (if requested) by a gray separator, and ends with a -/// gray “ \]”. The line can be passed directly to widgets such as `Block::title`. -/// -/// # Example -/// -/// ```rust -/// use war_in_tunnels::app::helpers::block_title::block_title_helper; -/// use ratatui::style::Color; -/// use ratatui::text::Line; -/// -/// let title: Line<'_> = block_title_helper( -/// &[ -/// ("Skirmish", Color::Magenta), -/// ("Map", Color::Green), -/// ], -/// Some(" - ") -/// ); -/// // `title` now represents: [ Skirmish - Map ] with the appropriate colors. -/// ``` -pub fn block_title_helper<'a>(texts: &[(&'a str, Color)], separator: Option<&'a str>) -> Line<'a> { - let mut line: Line<'a> = Line::default(); +/// `texts` is a slice of `(String, Color)` tuples. The `String`s are cloned into +/// the spans, so the returned `Line` owns all its data and does not borrow from +/// the caller. +pub fn block_title_helper( + texts: &[(String, Color)], + separator: Option<&'static str>, +) -> Line<'static> { + let mut line: Line<'static> = Line::default(); line.spans.push(Span::from("[ ").gray()); for (i, (text, color)) in texts.iter().enumerate() { - let span: Span<'a> = Span::styled(*text, Style::new().fg(*color)); - + let span = Span::styled(text.clone(), Style::new().fg(*color)); line.spans.push(span); if let Some(sep) = separator { @@ -66,38 +32,11 @@ pub fn block_title_helper<'a>(texts: &[(&'a str, Color)], separator: Option<&'a line } -/// Convenience wrapper for `block_title_helper` that creates a title line for a -/// single piece of text. +/// Convenience wrapper for a single‑title line. /// -/// This function builds a `Line` that looks like: -/// ```text -/// [ ] -/// ``` -/// where `` is rendered with the supplied color, and the surrounding -/// brackets are rendered in gray. It simply forwards the arguments to -/// `block_title_helper` with `separator` set to `None`. -/// -/// # Arguments -/// -/// * `text` – The title string to display. -/// -/// * `color` – The `Color` that should be applied to the title text. -/// -/// # Returns -/// -/// A `Line<'a>` containing the grey brackets and the coloured title text, -/// ready to be passed to widgets such as `Block::title`. -/// -/// # Example -/// -/// ```rust -/// use war_in_tunnels::app::helpers::block_title::block_single_title_helper; -/// use ratatui::style::Color; -/// use ratatui::text::Line; -/// -/// let title: Line<'_> = block_single_title_helper("Keybindings", Color::Magenta); -/// // `title` now represents: [ Keybindings ] with “Keybindings” coloured magenta. -/// ``` -pub fn block_single_title_helper<'a>(text: &'a str, color: Color) -> Line<'a> { +/// The function builds a slice that contains the supplied `String` and then +/// forwards it to `block_title_helper`. Because `block_title_helper` clones the +/// string, the temporary slice is safe. +pub fn block_single_title_helper(text: String, color: Color) -> Line<'static> { block_title_helper(&[(text, color)], None) } diff --git a/src/app/helpers/cells_area.rs b/src/app/helpers/cells_area.rs index aed40d2..1598c90 100644 --- a/src/app/helpers/cells_area.rs +++ b/src/app/helpers/cells_area.rs @@ -1,13 +1,15 @@ -use crate::app::views::skirmish_layout; +use crate::app::views::{skirmish_layout, skirmish_main_area_layout}; use ratatui::{ layout::{Margin, Rect}, widgets::{Block, Borders}, }; -pub fn cells_area_helper(area: &Rect) -> Rect { +pub fn cells_area_helper(area: &Rect, side_panel: bool) -> Rect { + let board_area: Rect = skirmish_main_area_layout(skirmish_layout(*area)[1], side_panel)[0]; + Block::new() .borders(Borders::LEFT | Borders::TOP | Borders::RIGHT) - .inner(skirmish_layout(*area)[1]) + .inner(board_area) .inner(Margin { horizontal: 1, vertical: 1, diff --git a/src/app/keybindings/keybindings.rs b/src/app/keybindings/keybindings.rs index c93d691..f9c223c 100644 --- a/src/app/keybindings/keybindings.rs +++ b/src/app/keybindings/keybindings.rs @@ -43,6 +43,10 @@ pub enum Action { ZoomIn, /// Zoom the view out. ZoomOut, + /// Open side panel. + Tab, + /// Toggle skirmish board/skills view. + ShiftTab, /// Mute music. Mute, /// Volume up. @@ -71,6 +75,8 @@ pub enum Group { Zoom, /// Music related bindings. Music, + /// Opens/Closes/Toggles elements in current view. + Opener, /// Quit related bindings. Quit, } @@ -282,6 +288,24 @@ static KEYBINDINGS: &[KeyBinding] = &[ symbol: "n", description: "🔉", }, + KeyBinding { + action: Action::Tab, + code: KeyCode::Tab, + kind: KeyEventKind::Press, + modifiers: KeyModifiers::NONE, + group: Group::Opener, + symbol: "Tab", + description: "Toggle side panel", + }, + KeyBinding { + action: Action::ShiftTab, + code: KeyCode::Tab, + kind: KeyEventKind::Press, + modifiers: KeyModifiers::SHIFT, + group: Group::Opener, + symbol: "Shift + Tab", + description: "Toggle Map/Skills window", + }, KeyBinding { action: Action::WildCard('_'), code: KeyCode::Char('_'), diff --git a/src/app/keybindings/skirmish.rs b/src/app/keybindings/skirmish.rs index 8418376..044208f 100644 --- a/src/app/keybindings/skirmish.rs +++ b/src/app/keybindings/skirmish.rs @@ -3,12 +3,14 @@ use crate::app::{ keybindings::{Action, common_keybindings, event_to_action}, states::skirmish_states::{BoardState, MoveFocusedCell, ZoomLevel}, }; -use ratatui::crossterm::event::KeyEvent; +use ratatui::{crossterm::event::KeyEvent, layout::Rect}; pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) { if let Some(action) = event_to_action(&key_event) { common_keybindings(app, action); + let window_area: Rect = app.window_area; + let Some(states) = app.states_mut() else { return; }; @@ -86,6 +88,10 @@ pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) { board.marking_cells = false; board.clear_marked_cells() } + Action::Tab => { + states.skirmish.side_panel = !states.skirmish.side_panel; + board.change_resize(&window_area, states.skirmish.side_panel); + } _ => (), } } diff --git a/src/app/state.rs b/src/app/state.rs index ae98cb8..870a269 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -32,7 +32,9 @@ impl GameStates { args.map_width as usize, args.map_height as usize, args.zoom_level, + false, ), + side_panel: false, }, perk_decks: PerkDecksState { id: 2, diff --git a/src/app/states/skirmish.rs b/src/app/states/skirmish.rs index 41737bd..2bd381b 100644 --- a/src/app/states/skirmish.rs +++ b/src/app/states/skirmish.rs @@ -6,6 +6,7 @@ pub struct SkirmishState { pub id: usize, pub name: &'static str, pub board: BoardState, + pub side_panel: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] diff --git a/src/app/states/skirmish_states/board.rs b/src/app/states/skirmish_states/board.rs index b793a4f..dea56fb 100644 --- a/src/app/states/skirmish_states/board.rs +++ b/src/app/states/skirmish_states/board.rs @@ -33,8 +33,14 @@ pub struct BoardState { } impl BoardState { - pub fn new(area: &Rect, map_width: usize, map_height: usize, zoom_level: ZoomLevel) -> Self { - let cells_area: Rect = cells_area_helper(area); + pub fn new( + area: &Rect, + map_width: usize, + map_height: usize, + zoom_level: ZoomLevel, + side_panel: bool, + ) -> Self { + let cells_area: Rect = cells_area_helper(area, side_panel); let cell_width: usize = zoom_level.get_cell_size(CellSizes::Width); let cell_height: usize = zoom_level.get_cell_size(CellSizes::Height); @@ -114,6 +120,10 @@ impl BoardState { } } + pub fn get_ref_cell(&self, row: usize, col: usize) -> &CellWidget { + &self.cells[row][col] + } + fn get_mut_cell(&mut self, row: usize, col: usize) -> &mut CellWidget { &mut self.cells[row][col] } @@ -188,6 +198,10 @@ impl BoardState { if map_size > size { map_size - size } else { 0 } } + pub fn get_focused_cell(&self) -> &FocusedCell { + &self.focused_cell + } + pub fn is_focused_cell_visible(&self) -> bool { let vertical_offset: usize = self.vertical_offset.get_value(); let horizontal_offset: usize = self.horizontal_offset.get_value(); @@ -209,14 +223,14 @@ impl BoardState { true } - pub fn change_resize(&mut self, area: &Rect) { - self.cells_area = cells_area_helper(area); + pub fn change_resize(&mut self, area: &Rect, side_panel: bool) { + self.cells_area = cells_area_helper(area, side_panel); self.cols = (self.cells_area.width / self.cell_width as u16) as usize; self.rows = (self.cells_area.height / self.cell_height as u16) as usize; - let h_max_offset: usize = Self::max_offset(self.map_height, self.cols); - let v_max_offset: usize = Self::max_offset(self.map_width, self.rows); + let v_max_offset: usize = Self::max_offset(self.map_height, self.rows); + let h_max_offset: usize = Self::max_offset(self.map_width, self.cols); self.horizontal_offset = Offset::new(Some(self.horizontal_offset.get_value()), Some(h_max_offset)); diff --git a/src/app/states/skirmish_states/structures/base.rs b/src/app/states/skirmish_states/structures/base.rs index 4742e58..7ffa4d3 100644 --- a/src/app/states/skirmish_states/structures/base.rs +++ b/src/app/states/skirmish_states/structures/base.rs @@ -47,4 +47,8 @@ impl Structure for BaseBuilding { fn get_stress(&self) -> u8 { self.stress } + + fn get_name(&self) -> &'static str { + "Base" + } } diff --git a/src/app/states/skirmish_states/structures/stone.rs b/src/app/states/skirmish_states/structures/stone.rs index 901889a..27a096c 100644 --- a/src/app/states/skirmish_states/structures/stone.rs +++ b/src/app/states/skirmish_states/structures/stone.rs @@ -36,4 +36,8 @@ impl Structure for Stone { fn get_stress(&self) -> u8 { self.stress } + + fn get_name(&self) -> &'static str { + "Stone" + } } diff --git a/src/app/states/skirmish_states/structures/structures_enum.rs b/src/app/states/skirmish_states/structures/structures_enum.rs index 350440a..c4dc44b 100644 --- a/src/app/states/skirmish_states/structures/structures_enum.rs +++ b/src/app/states/skirmish_states/structures/structures_enum.rs @@ -8,28 +8,52 @@ pub enum Structures { Stone(Stone), } -impl Structures { - pub fn get_color(&self) -> Color { +impl Structure for Structures { + fn get_color(&self) -> Color { match self { - Structures::Base(b) => b.get_color(), - Structures::Tunnel(t) => t.get_color(), - Structures::Stone(s) => s.get_color(), + Self::Base(b) => b.get_color(), + Self::Tunnel(t) => t.get_color(), + Self::Stone(s) => s.get_color(), } } - pub fn get_tag(&self) -> char { + fn get_tag(&self) -> char { match self { - Structures::Base(b) => b.get_tag(), - Structures::Tunnel(t) => t.get_tag(), - Structures::Stone(s) => s.get_tag(), + Self::Base(b) => b.get_tag(), + Self::Tunnel(t) => t.get_tag(), + Self::Stone(s) => s.get_tag(), } } - pub fn get_level(&self) -> char { + fn get_level(&self) -> char { match self { - Structures::Base(b) => b.get_level(), - Structures::Tunnel(t) => t.get_level(), - Structures::Stone(s) => s.get_level(), + Self::Base(b) => b.get_level(), + Self::Tunnel(t) => t.get_level(), + Self::Stone(s) => s.get_level(), + } + } + + fn get_name(&self) -> &'static str { + match self { + Self::Base(b) => b.get_name(), + Self::Tunnel(t) => t.get_name(), + Self::Stone(s) => s.get_name(), + } + } + + fn get_durability(&self) -> u16 { + match self { + Self::Base(b) => b.get_durability(), + Self::Tunnel(t) => t.get_durability(), + Self::Stone(s) => s.get_durability(), + } + } + + fn get_stress(&self) -> u8 { + match self { + Self::Base(b) => b.get_stress(), + Self::Tunnel(t) => t.get_stress(), + Self::Stone(s) => s.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 f911a9f..39d6b2e 100644 --- a/src/app/states/skirmish_states/structures/structures_trait.rs +++ b/src/app/states/skirmish_states/structures/structures_trait.rs @@ -6,4 +6,5 @@ pub trait Structure { fn get_level(&self) -> char; fn get_stress(&self) -> u8; fn get_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 547d1e8..129da67 100644 --- a/src/app/states/skirmish_states/structures/tunnel.rs +++ b/src/app/states/skirmish_states/structures/tunnel.rs @@ -42,4 +42,8 @@ impl Structure for Tunnel { fn get_stress(&self) -> u8 { self.stress } + + fn get_name(&self) -> &'static str { + "Tunnel" + } } diff --git a/src/app/states/skirmish_states/units/miner.rs b/src/app/states/skirmish_states/units/miner.rs index 06f2031..a306e41 100644 --- a/src/app/states/skirmish_states/units/miner.rs +++ b/src/app/states/skirmish_states/units/miner.rs @@ -19,4 +19,8 @@ impl Unit for MinerUnit { fn get_tag(&self) -> char { 'M' } + + fn get_name(&self) -> &'static str { + "Miner" + } } diff --git a/src/app/states/skirmish_states/units/mod.rs b/src/app/states/skirmish_states/units/mod.rs index 491ebe6..fc493ca 100644 --- a/src/app/states/skirmish_states/units/mod.rs +++ b/src/app/states/skirmish_states/units/mod.rs @@ -4,4 +4,4 @@ mod units_trait; pub use miner::MinerUnit; pub use units_enum::Units; -pub use units_trait::{OptionalUnit, Unit}; +pub use units_trait::Unit; diff --git a/src/app/states/skirmish_states/units/units_enum.rs b/src/app/states/skirmish_states/units/units_enum.rs index 951426d..4fef7f1 100644 --- a/src/app/states/skirmish_states/units/units_enum.rs +++ b/src/app/states/skirmish_states/units/units_enum.rs @@ -1,20 +1,43 @@ -use crate::app::states::skirmish_states::units::{MinerUnit, OptionalUnit, Unit}; +use crate::app::states::{ + Players, + skirmish_states::units::{MinerUnit, Unit}, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Units { Miner(MinerUnit), } -impl Units { - pub fn get_tag(&self) -> char { +impl Unit for Units { + fn get_tag(&self) -> char { match self { - Units::Miner(m) => m.get_tag(), + Self::Miner(m) => m.get_tag(), + } + } + + fn get_name(&self) -> &'static str { + match self { + Self::Miner(m) => m.get_name(), + } + } + + fn get_owner(&self) -> Players { + match self { + Self::Miner(m) => m.get_owner(), } } } -impl OptionalUnit for Option { - fn try_get_tag(&self) -> char { +impl Unit for Option { + fn get_tag(&self) -> char { self.map_or(' ', |u| u.get_tag()) } + + fn get_name(&self) -> &'static str { + self.map_or("", |u| u.get_name()) + } + + fn get_owner(&self) -> Players { + self.map_or(Players::Enemy, |u| u.get_owner()) + } } diff --git a/src/app/states/skirmish_states/units/units_trait.rs b/src/app/states/skirmish_states/units/units_trait.rs index 1641fc8..272f57c 100644 --- a/src/app/states/skirmish_states/units/units_trait.rs +++ b/src/app/states/skirmish_states/units/units_trait.rs @@ -3,8 +3,5 @@ use crate::app::states::Players; pub trait Unit { fn get_owner(&self) -> Players; fn get_tag(&self) -> char; -} - -pub trait OptionalUnit { - fn try_get_tag(&self) -> char; + fn get_name(&self) -> &'static str; } diff --git a/src/app/views/mod.rs b/src/app/views/mod.rs index d62764a..9fd4adc 100644 --- a/src/app/views/mod.rs +++ b/src/app/views/mod.rs @@ -4,4 +4,4 @@ mod skirmish; pub use default::default_view; pub use main_menu::main_menu_view; -pub use skirmish::{skirmish_layout, skirmish_view}; +pub use skirmish::{skirmish_layout, skirmish_main_area_layout, skirmish_view}; diff --git a/src/app/views/skirmish.rs b/src/app/views/skirmish.rs index 3f766bb..446261e 100644 --- a/src/app/views/skirmish.rs +++ b/src/app/views/skirmish.rs @@ -2,7 +2,8 @@ use crate::app::{ App, helpers::block_title_helper, keybindings::{Action, count_largest_group}, - widgets::{BoardWidget, KeybindingsWidget}, + states::skirmish_states::{structures::Structure, units::Unit}, + widgets::{BoardWidget, KeybindingsWidget, SidePanelWidget}, }; use ratatui::{ buffer::Buffer, @@ -29,6 +30,7 @@ const ACTIONS: &[Action] = &[ Action::Backspace, Action::Delete, Action::Mute, + Action::Tab, Action::Quit, Action::Quit2, Action::Esc, @@ -43,6 +45,16 @@ pub fn skirmish_layout(area: Rect) -> [Rect; 3] { .areas(area) } +pub fn skirmish_main_area_layout(area: Rect, side_panel: bool) -> [Rect; 2] { + let constraint: [Constraint; 2] = if side_panel { + [Constraint::Percentage(80), Constraint::Percentage(20)] + } else { + [Constraint::Percentage(100), Constraint::Percentage(0)] + }; + + Layout::horizontal(constraint).areas(area) +} + pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) { let Some(states) = app.states() else { return }; @@ -80,16 +92,22 @@ pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) { } { - let board_block: Block = Block::new() + let main_block: Block = Block::new() .gray() .title(block_title_helper( - &[("Skirmish", Color::Magenta), ("Map", Color::Green)], + &[ + ("Skirmish".to_string(), Color::Magenta), + ("Map".to_string(), Color::Green), + ], Some(" - "), )) .borders(Borders::LEFT | Borders::TOP | Borders::RIGHT); - let board_area: Rect = board_block.inner(main_area); - board_block.render(main_area, buf); + let main_inner_area: Rect = main_block.inner(main_area); + main_block.render(main_area, buf); + + let [board_area, side_panel_area] = + skirmish_main_area_layout(main_inner_area, states.skirmish.side_panel); let cells_area: Rect = board_area.inner(Margin { horizontal: 1, @@ -105,6 +123,26 @@ pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) { // ); BoardWidget::new(&states.skirmish.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(); + + SidePanelWidget::new(coords, structure_name, unit_name).render(side_panel_area, buf); + } } { diff --git a/src/app/widgets/cell.rs b/src/app/widgets/cell.rs index fa57896..9619e9f 100644 --- a/src/app/widgets/cell.rs +++ b/src/app/widgets/cell.rs @@ -1,14 +1,14 @@ use crate::app::states::skirmish_states::{ ZoomLevel, - structures::{Stone, Structures}, - units::{OptionalUnit, Units}, + structures::{Stone, Structure, Structures}, + units::{Unit, Units}, }; use ratatui::{ buffer::Buffer, layout::{Alignment, Rect}, style::{Color, Style, Stylize}, text::{Line, Span, ToSpan}, - widgets::{Block, Borders, Paragraph, Widget}, + widgets::{Block, BorderType, Borders, Paragraph, Widget}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -52,6 +52,14 @@ impl CellWidget { self } + pub fn get_structure(&self) -> Structures { + self.structure + } + + pub fn get_unit_option(&self) -> Option { + self.unit + } + pub fn set_structure(&mut self, sctructure: Structures) -> &mut Self { self.structure = sctructure; self @@ -102,7 +110,7 @@ impl CellWidget { self.zoom_level.get_cell_text_area( self.structure.get_tag(), self.structure.get_level(), - self.unit.try_get_tag(), + self.unit.get_tag(), ) } @@ -111,6 +119,7 @@ impl CellWidget { .borders(Borders::ALL) .style(Style::default().fg(self.fg_color())) .title(self.display_coords()) + .border_type(BorderType::Rounded) } } diff --git a/src/app/widgets/keybindings.rs b/src/app/widgets/keybindings.rs index 8760c39..6829b1e 100644 --- a/src/app/widgets/keybindings.rs +++ b/src/app/widgets/keybindings.rs @@ -115,9 +115,13 @@ impl Widget for KeybindingsWidget { return; } - let block: Block<'_> = Block::default() - .borders(Borders::ALL) - .title(block_single_title_helper("Keybindings", Color::Magenta)); + let block: Block<'_> = + Block::default() + .borders(Borders::ALL) + .title(block_single_title_helper( + "Keybindings".to_string(), + Color::Magenta, + )); let inner: Rect = block.inner(area); diff --git a/src/app/widgets/mod.rs b/src/app/widgets/mod.rs index e6a0ef5..e414e61 100644 --- a/src/app/widgets/mod.rs +++ b/src/app/widgets/mod.rs @@ -1,7 +1,9 @@ mod board; mod cell; mod keybindings; +mod side_panel; pub use board::BoardWidget; pub use cell::CellWidget; pub use keybindings::KeybindingsWidget; +pub use side_panel::SidePanelWidget; diff --git a/src/app/widgets/side_panel.rs b/src/app/widgets/side_panel.rs new file mode 100644 index 0000000..938829f --- /dev/null +++ b/src/app/widgets/side_panel.rs @@ -0,0 +1,74 @@ +use ratatui::{ + buffer::Buffer, + layout::{Alignment, Rect}, + style::Color, + text::Line, + widgets::{Block, BorderType, Borders, Paragraph, Widget}, +}; + +use crate::app::helpers::block_title_helper; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SidePanelWidget { + coords: (usize, usize), + structure_name: &'static str, + unit_name: &'static str, +} + +impl SidePanelWidget { + pub fn new( + coords: (usize, usize), + structure_name: &'static str, + unit_name: &'static str, + ) -> Self { + Self { + coords, + structure_name, + unit_name, + } + } + + fn col_to_letters(&self) -> String { + let mut col: usize = self.coords.1 + 1; + let mut letters: Vec = Vec::new(); + + while col > 0 { + letters.push((b'A' + ((col - 1) % 26) as u8) as char); + col = (col - 1) / 26; + } + + 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<'_> { + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Double) + .title(self.get_title()) + .title_alignment(Alignment::Center) + } +} + +impl Widget for SidePanelWidget { + fn render(self, area: Rect, buf: &mut Buffer) { + Paragraph::default() + .alignment(Alignment::Center) + .block(self.get_block()) + .render(area, buf); + } +}