generated from GarandPLG/rust-flake-template
Add side panel and refactor title helpers
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.
This commit is contained in:
+4
-1
@@ -60,7 +60,10 @@ impl App {
|
|||||||
let Some(state) = self.states_mut() else {
|
let Some(state) = self.states_mut() else {
|
||||||
panic!("State issue")
|
panic!("State issue")
|
||||||
};
|
};
|
||||||
state.skirmish.board.change_resize(&window_area);
|
state
|
||||||
|
.skirmish
|
||||||
|
.board
|
||||||
|
.change_resize(&window_area, state.skirmish.side_panel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,53 +5,19 @@ use ratatui::{
|
|||||||
|
|
||||||
/// Creates a styled title `Line` for a UI block.
|
/// Creates a styled title `Line` for a UI block.
|
||||||
///
|
///
|
||||||
/// This helper builds a `Line` that looks like:
|
/// `texts` is a slice of `(String, Color)` tuples. The `String`s are cloned into
|
||||||
/// ```text
|
/// the spans, so the returned `Line` owns all its data and does not borrow from
|
||||||
/// [ <title‑1> <separator> <title‑2> ... ]
|
/// the caller.
|
||||||
/// ```
|
pub fn block_title_helper(
|
||||||
/// where each title fragment is rendered in the color supplied by the caller,
|
texts: &[(String, Color)],
|
||||||
/// and the surrounding brackets and any separator are rendered in gray.
|
separator: Option<&'static str>,
|
||||||
///
|
) -> Line<'static> {
|
||||||
/// # Arguments
|
let mut line: Line<'static> = Line::default();
|
||||||
///
|
|
||||||
/// * `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();
|
|
||||||
|
|
||||||
line.spans.push(Span::from("[ ").gray());
|
line.spans.push(Span::from("[ ").gray());
|
||||||
|
|
||||||
for (i, (text, color)) in texts.iter().enumerate() {
|
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);
|
line.spans.push(span);
|
||||||
|
|
||||||
if let Some(sep) = separator {
|
if let Some(sep) = separator {
|
||||||
@@ -66,38 +32,11 @@ pub fn block_title_helper<'a>(texts: &[(&'a str, Color)], separator: Option<&'a
|
|||||||
line
|
line
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience wrapper for `block_title_helper` that creates a title line for a
|
/// Convenience wrapper for a single‑title line.
|
||||||
/// single piece of text.
|
|
||||||
///
|
///
|
||||||
/// This function builds a `Line` that looks like:
|
/// The function builds a slice that contains the supplied `String` and then
|
||||||
/// ```text
|
/// forwards it to `block_title_helper`. Because `block_title_helper` clones the
|
||||||
/// [ <text> ]
|
/// string, the temporary slice is safe.
|
||||||
/// ```
|
pub fn block_single_title_helper(text: String, color: Color) -> Line<'static> {
|
||||||
/// where `<text>` 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> {
|
|
||||||
block_title_helper(&[(text, color)], None)
|
block_title_helper(&[(text, color)], None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use crate::app::views::skirmish_layout;
|
use crate::app::views::{skirmish_layout, skirmish_main_area_layout};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Margin, Rect},
|
layout::{Margin, Rect},
|
||||||
widgets::{Block, Borders},
|
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()
|
Block::new()
|
||||||
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT)
|
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT)
|
||||||
.inner(skirmish_layout(*area)[1])
|
.inner(board_area)
|
||||||
.inner(Margin {
|
.inner(Margin {
|
||||||
horizontal: 1,
|
horizontal: 1,
|
||||||
vertical: 1,
|
vertical: 1,
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ pub enum Action {
|
|||||||
ZoomIn,
|
ZoomIn,
|
||||||
/// Zoom the view out.
|
/// Zoom the view out.
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
|
/// Open side panel.
|
||||||
|
Tab,
|
||||||
|
/// Toggle skirmish board/skills view.
|
||||||
|
ShiftTab,
|
||||||
/// Mute music.
|
/// Mute music.
|
||||||
Mute,
|
Mute,
|
||||||
/// Volume up.
|
/// Volume up.
|
||||||
@@ -71,6 +75,8 @@ pub enum Group {
|
|||||||
Zoom,
|
Zoom,
|
||||||
/// Music related bindings.
|
/// Music related bindings.
|
||||||
Music,
|
Music,
|
||||||
|
/// Opens/Closes/Toggles elements in current view.
|
||||||
|
Opener,
|
||||||
/// Quit related bindings.
|
/// Quit related bindings.
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
@@ -282,6 +288,24 @@ static KEYBINDINGS: &[KeyBinding] = &[
|
|||||||
symbol: "n",
|
symbol: "n",
|
||||||
description: "🔉",
|
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 {
|
KeyBinding {
|
||||||
action: Action::WildCard('_'),
|
action: Action::WildCard('_'),
|
||||||
code: KeyCode::Char('_'),
|
code: KeyCode::Char('_'),
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ use crate::app::{
|
|||||||
keybindings::{Action, common_keybindings, event_to_action},
|
keybindings::{Action, common_keybindings, event_to_action},
|
||||||
states::skirmish_states::{BoardState, MoveFocusedCell, ZoomLevel},
|
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) {
|
pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) {
|
||||||
if let Some(action) = event_to_action(&key_event) {
|
if let Some(action) = event_to_action(&key_event) {
|
||||||
common_keybindings(app, action);
|
common_keybindings(app, action);
|
||||||
|
|
||||||
|
let window_area: Rect = app.window_area;
|
||||||
|
|
||||||
let Some(states) = app.states_mut() else {
|
let Some(states) = app.states_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -86,6 +88,10 @@ pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) {
|
|||||||
board.marking_cells = false;
|
board.marking_cells = false;
|
||||||
board.clear_marked_cells()
|
board.clear_marked_cells()
|
||||||
}
|
}
|
||||||
|
Action::Tab => {
|
||||||
|
states.skirmish.side_panel = !states.skirmish.side_panel;
|
||||||
|
board.change_resize(&window_area, states.skirmish.side_panel);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ impl GameStates {
|
|||||||
args.map_width as usize,
|
args.map_width as usize,
|
||||||
args.map_height as usize,
|
args.map_height as usize,
|
||||||
args.zoom_level,
|
args.zoom_level,
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
|
side_panel: false,
|
||||||
},
|
},
|
||||||
perk_decks: PerkDecksState {
|
perk_decks: PerkDecksState {
|
||||||
id: 2,
|
id: 2,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub struct SkirmishState {
|
|||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub board: BoardState,
|
pub board: BoardState,
|
||||||
|
pub side_panel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
|
||||||
|
|||||||
@@ -33,8 +33,14 @@ pub struct BoardState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BoardState {
|
impl BoardState {
|
||||||
pub fn new(area: &Rect, map_width: usize, map_height: usize, zoom_level: ZoomLevel) -> Self {
|
pub fn new(
|
||||||
let cells_area: Rect = cells_area_helper(area);
|
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_width: usize = zoom_level.get_cell_size(CellSizes::Width);
|
||||||
let cell_height: usize = zoom_level.get_cell_size(CellSizes::Height);
|
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 {
|
fn get_mut_cell(&mut self, row: usize, col: usize) -> &mut CellWidget {
|
||||||
&mut self.cells[row][col]
|
&mut self.cells[row][col]
|
||||||
}
|
}
|
||||||
@@ -188,6 +198,10 @@ impl BoardState {
|
|||||||
if map_size > size { map_size - size } else { 0 }
|
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 {
|
pub fn is_focused_cell_visible(&self) -> bool {
|
||||||
let vertical_offset: usize = self.vertical_offset.get_value();
|
let vertical_offset: usize = self.vertical_offset.get_value();
|
||||||
let horizontal_offset: usize = self.horizontal_offset.get_value();
|
let horizontal_offset: usize = self.horizontal_offset.get_value();
|
||||||
@@ -209,14 +223,14 @@ impl BoardState {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_resize(&mut self, area: &Rect) {
|
pub fn change_resize(&mut self, area: &Rect, side_panel: bool) {
|
||||||
self.cells_area = cells_area_helper(area);
|
self.cells_area = cells_area_helper(area, side_panel);
|
||||||
|
|
||||||
self.cols = (self.cells_area.width / self.cell_width as u16) as usize;
|
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;
|
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_height, self.rows);
|
||||||
let v_max_offset: usize = Self::max_offset(self.map_width, self.rows);
|
let h_max_offset: usize = Self::max_offset(self.map_width, self.cols);
|
||||||
|
|
||||||
self.horizontal_offset =
|
self.horizontal_offset =
|
||||||
Offset::new(Some(self.horizontal_offset.get_value()), Some(h_max_offset));
|
Offset::new(Some(self.horizontal_offset.get_value()), Some(h_max_offset));
|
||||||
|
|||||||
@@ -47,4 +47,8 @@ impl Structure for BaseBuilding {
|
|||||||
fn get_stress(&self) -> u8 {
|
fn get_stress(&self) -> u8 {
|
||||||
self.stress
|
self.stress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> &'static str {
|
||||||
|
"Base"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,4 +36,8 @@ impl Structure for Stone {
|
|||||||
fn get_stress(&self) -> u8 {
|
fn get_stress(&self) -> u8 {
|
||||||
self.stress
|
self.stress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> &'static str {
|
||||||
|
"Stone"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,28 +8,52 @@ pub enum Structures {
|
|||||||
Stone(Stone),
|
Stone(Stone),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Structures {
|
impl Structure for Structures {
|
||||||
pub fn get_color(&self) -> Color {
|
fn get_color(&self) -> Color {
|
||||||
match self {
|
match self {
|
||||||
Structures::Base(b) => b.get_color(),
|
Self::Base(b) => b.get_color(),
|
||||||
Structures::Tunnel(t) => t.get_color(),
|
Self::Tunnel(t) => t.get_color(),
|
||||||
Structures::Stone(s) => s.get_color(),
|
Self::Stone(s) => s.get_color(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tag(&self) -> char {
|
fn get_tag(&self) -> char {
|
||||||
match self {
|
match self {
|
||||||
Structures::Base(b) => b.get_tag(),
|
Self::Base(b) => b.get_tag(),
|
||||||
Structures::Tunnel(t) => t.get_tag(),
|
Self::Tunnel(t) => t.get_tag(),
|
||||||
Structures::Stone(s) => s.get_tag(),
|
Self::Stone(s) => s.get_tag(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_level(&self) -> char {
|
fn get_level(&self) -> char {
|
||||||
match self {
|
match self {
|
||||||
Structures::Base(b) => b.get_level(),
|
Self::Base(b) => b.get_level(),
|
||||||
Structures::Tunnel(t) => t.get_level(),
|
Self::Tunnel(t) => t.get_level(),
|
||||||
Structures::Stone(s) => s.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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ pub trait Structure {
|
|||||||
fn get_level(&self) -> char;
|
fn get_level(&self) -> char;
|
||||||
fn get_stress(&self) -> u8;
|
fn get_stress(&self) -> u8;
|
||||||
fn get_durability(&self) -> u16;
|
fn get_durability(&self) -> u16;
|
||||||
|
fn get_name(&self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,4 +42,8 @@ impl Structure for Tunnel {
|
|||||||
fn get_stress(&self) -> u8 {
|
fn get_stress(&self) -> u8 {
|
||||||
self.stress
|
self.stress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> &'static str {
|
||||||
|
"Tunnel"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,8 @@ impl Unit for MinerUnit {
|
|||||||
fn get_tag(&self) -> char {
|
fn get_tag(&self) -> char {
|
||||||
'M'
|
'M'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> &'static str {
|
||||||
|
"Miner"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ mod units_trait;
|
|||||||
|
|
||||||
pub use miner::MinerUnit;
|
pub use miner::MinerUnit;
|
||||||
pub use units_enum::Units;
|
pub use units_enum::Units;
|
||||||
pub use units_trait::{OptionalUnit, Unit};
|
pub use units_trait::Unit;
|
||||||
|
|||||||
@@ -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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Units {
|
pub enum Units {
|
||||||
Miner(MinerUnit),
|
Miner(MinerUnit),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Units {
|
impl Unit for Units {
|
||||||
pub fn get_tag(&self) -> char {
|
fn get_tag(&self) -> char {
|
||||||
match self {
|
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<Units> {
|
impl Unit for Option<Units> {
|
||||||
fn try_get_tag(&self) -> char {
|
fn get_tag(&self) -> char {
|
||||||
self.map_or(' ', |u| u.get_tag())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,5 @@ use crate::app::states::Players;
|
|||||||
pub trait Unit {
|
pub trait Unit {
|
||||||
fn get_owner(&self) -> Players;
|
fn get_owner(&self) -> Players;
|
||||||
fn get_tag(&self) -> char;
|
fn get_tag(&self) -> char;
|
||||||
}
|
fn get_name(&self) -> &'static str;
|
||||||
|
|
||||||
pub trait OptionalUnit {
|
|
||||||
fn try_get_tag(&self) -> char;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ mod skirmish;
|
|||||||
|
|
||||||
pub use default::default_view;
|
pub use default::default_view;
|
||||||
pub use main_menu::main_menu_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};
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use crate::app::{
|
|||||||
App,
|
App,
|
||||||
helpers::block_title_helper,
|
helpers::block_title_helper,
|
||||||
keybindings::{Action, count_largest_group},
|
keybindings::{Action, count_largest_group},
|
||||||
widgets::{BoardWidget, KeybindingsWidget},
|
states::skirmish_states::{structures::Structure, units::Unit},
|
||||||
|
widgets::{BoardWidget, KeybindingsWidget, SidePanelWidget},
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
@@ -29,6 +30,7 @@ const ACTIONS: &[Action] = &[
|
|||||||
Action::Backspace,
|
Action::Backspace,
|
||||||
Action::Delete,
|
Action::Delete,
|
||||||
Action::Mute,
|
Action::Mute,
|
||||||
|
Action::Tab,
|
||||||
Action::Quit,
|
Action::Quit,
|
||||||
Action::Quit2,
|
Action::Quit2,
|
||||||
Action::Esc,
|
Action::Esc,
|
||||||
@@ -43,6 +45,16 @@ pub fn skirmish_layout(area: Rect) -> [Rect; 3] {
|
|||||||
.areas(area)
|
.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) {
|
pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) {
|
||||||
let Some(states) = app.states() else { return };
|
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()
|
.gray()
|
||||||
.title(block_title_helper(
|
.title(block_title_helper(
|
||||||
&[("Skirmish", Color::Magenta), ("Map", Color::Green)],
|
&[
|
||||||
|
("Skirmish".to_string(), Color::Magenta),
|
||||||
|
("Map".to_string(), Color::Green),
|
||||||
|
],
|
||||||
Some(" - "),
|
Some(" - "),
|
||||||
))
|
))
|
||||||
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT);
|
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT);
|
||||||
|
|
||||||
let board_area: Rect = board_block.inner(main_area);
|
let main_inner_area: Rect = main_block.inner(main_area);
|
||||||
board_block.render(main_area, buf);
|
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 {
|
let cells_area: Rect = board_area.inner(Margin {
|
||||||
horizontal: 1,
|
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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
+13
-4
@@ -1,14 +1,14 @@
|
|||||||
use crate::app::states::skirmish_states::{
|
use crate::app::states::skirmish_states::{
|
||||||
ZoomLevel,
|
ZoomLevel,
|
||||||
structures::{Stone, Structures},
|
structures::{Stone, Structure, Structures},
|
||||||
units::{OptionalUnit, Units},
|
units::{Unit, Units},
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
style::{Color, Style, Stylize},
|
style::{Color, Style, Stylize},
|
||||||
text::{Line, Span, ToSpan},
|
text::{Line, Span, ToSpan},
|
||||||
widgets::{Block, Borders, Paragraph, Widget},
|
widgets::{Block, BorderType, Borders, Paragraph, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -52,6 +52,14 @@ impl CellWidget {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_structure(&self) -> Structures {
|
||||||
|
self.structure
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_unit_option(&self) -> Option<Units> {
|
||||||
|
self.unit
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_structure(&mut self, sctructure: Structures) -> &mut Self {
|
pub fn set_structure(&mut self, sctructure: Structures) -> &mut Self {
|
||||||
self.structure = sctructure;
|
self.structure = sctructure;
|
||||||
self
|
self
|
||||||
@@ -102,7 +110,7 @@ impl CellWidget {
|
|||||||
self.zoom_level.get_cell_text_area(
|
self.zoom_level.get_cell_text_area(
|
||||||
self.structure.get_tag(),
|
self.structure.get_tag(),
|
||||||
self.structure.get_level(),
|
self.structure.get_level(),
|
||||||
self.unit.try_get_tag(),
|
self.unit.get_tag(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +119,7 @@ impl CellWidget {
|
|||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.style(Style::default().fg(self.fg_color()))
|
.style(Style::default().fg(self.fg_color()))
|
||||||
.title(self.display_coords())
|
.title(self.display_coords())
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,9 +115,13 @@ impl Widget for KeybindingsWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let block: Block<'_> = Block::default()
|
let block: Block<'_> =
|
||||||
.borders(Borders::ALL)
|
Block::default()
|
||||||
.title(block_single_title_helper("Keybindings", Color::Magenta));
|
.borders(Borders::ALL)
|
||||||
|
.title(block_single_title_helper(
|
||||||
|
"Keybindings".to_string(),
|
||||||
|
Color::Magenta,
|
||||||
|
));
|
||||||
|
|
||||||
let inner: Rect = block.inner(area);
|
let inner: Rect = block.inner(area);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
mod board;
|
mod board;
|
||||||
mod cell;
|
mod cell;
|
||||||
mod keybindings;
|
mod keybindings;
|
||||||
|
mod side_panel;
|
||||||
|
|
||||||
pub use board::BoardWidget;
|
pub use board::BoardWidget;
|
||||||
pub use cell::CellWidget;
|
pub use cell::CellWidget;
|
||||||
pub use keybindings::KeybindingsWidget;
|
pub use keybindings::KeybindingsWidget;
|
||||||
|
pub use side_panel::SidePanelWidget;
|
||||||
|
|||||||
@@ -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<char> = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user