From f405c3cde1edcb5c357c631f3d316fe4fe8a7488 Mon Sep 17 00:00:00 2001 From: GarandPLG Date: Wed, 1 Apr 2026 20:27:50 +0200 Subject: [PATCH] Track window area and refactor keybinding APIs - Track terminal size in `App::window_area` and update on resize - Accept action slices in `count_largest_group` and `KeybindingsWidget::new` - Add ACTIONS slices and layout helpers for default, main_menu, skirmish - Add `main_menu_option_helper` for formatting menu strings and export it --- src/app/app.rs | 13 +++++++- src/app/helpers/main_menu_option.rs | 13 ++++++++ src/app/helpers/mod.rs | 2 ++ src/app/keybindings/keybindings.rs | 2 +- src/app/views/default.rs | 17 ++++++----- src/app/views/main_menu.rs | 46 +++++++++++------------------ src/app/views/skirmish.rs | 45 +++++++++++++++------------- src/app/widgets/keybindings.rs | 4 +-- 8 files changed, 82 insertions(+), 60 deletions(-) create mode 100644 src/app/helpers/main_menu_option.rs diff --git a/src/app/app.rs b/src/app/app.rs index 55ea8d8..2ebb20f 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -5,6 +5,7 @@ use crate::{ use ratatui::{ DefaultTerminal, Frame, crossterm::event::{self, KeyEvent}, + layout::Rect, }; use std::{ io::Result, @@ -20,6 +21,7 @@ pub enum Event { pub struct App { pub exit: bool, pub view: View, + pub window_area: Rect, pub states: GameStates, } @@ -31,6 +33,7 @@ impl App { Self { exit: false, view: args.view, + window_area: Rect::default(), states, } } @@ -56,7 +59,15 @@ impl App { } fn draw(&mut self, frame: &mut Frame<'_>) { - frame.render_widget(self, frame.area()); + let window_area: Rect = frame.area(); + + if window_area.width != self.window_area.width + || window_area.height != self.window_area.height + { + self.window_area = window_area; + } + + frame.render_widget(self, window_area); } fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> { diff --git a/src/app/helpers/main_menu_option.rs b/src/app/helpers/main_menu_option.rs new file mode 100644 index 0000000..85311aa --- /dev/null +++ b/src/app/helpers/main_menu_option.rs @@ -0,0 +1,13 @@ +pub fn main_menu_option_helper(s: String) -> String { + if let Some(pos) = s + .char_indices() + .filter(|(_, c)| c.is_ascii_uppercase()) + .nth(1) + .map(|(i, _)| i) + { + let (first, second) = s.split_at(pos); + format!("{first} {second}") + } else { + s + } +} diff --git a/src/app/helpers/mod.rs b/src/app/helpers/mod.rs index a71b4fc..66e0930 100644 --- a/src/app/helpers/mod.rs +++ b/src/app/helpers/mod.rs @@ -1,5 +1,7 @@ pub mod block_title; +pub mod main_menu_option; pub mod zoom_level; pub use block_title::{block_single_title_helper, block_title_helper}; +pub use main_menu_option::main_menu_option_helper; pub use zoom_level::{CellSizes, cell_size_helper}; diff --git a/src/app/keybindings/keybindings.rs b/src/app/keybindings/keybindings.rs index 6f7191f..d8851d9 100644 --- a/src/app/keybindings/keybindings.rs +++ b/src/app/keybindings/keybindings.rs @@ -284,7 +284,7 @@ pub fn event_to_action(event: &KeyEvent) -> Option { /// It counts how many times each group appears in the supplied actions and /// returns the highest count. This can be used, for example, to size UI /// elements that display groups of bindings. -pub fn count_largest_group(actions: &Vec) -> u16 { +pub fn count_largest_group(actions: &[Action]) -> u16 { let mut group_counts: HashMap = HashMap::new(); for action in actions { diff --git a/src/app/views/default.rs b/src/app/views/default.rs index dd3fd44..57c0a05 100644 --- a/src/app/views/default.rs +++ b/src/app/views/default.rs @@ -9,15 +9,18 @@ use ratatui::{ widgets::{Block, Borders, Paragraph, Widget}, }; -pub fn default_view(area: Rect, buf: &mut Buffer) { - let actions: Vec = vec![Action::Quit, Action::Quit2, Action::Esc]; +const ACTIONS: &[Action] = &[Action::Quit, Action::Quit2, Action::Esc]; - let vertical_layout: Layout = Layout::vertical([ +fn default_view_layout(area: Rect) -> [Rect; 2] { + Layout::vertical([ Constraint::Fill(1), - Constraint::Length(count_largest_group(&actions) + 2), - ]); + Constraint::Length(count_largest_group(&ACTIONS) + 2), + ]) + .areas(area) +} - let [main_area, keybindings_area] = vertical_layout.areas(area); +pub fn default_view(area: Rect, buf: &mut Buffer) { + let [main_area, keybindings_area] = default_view_layout(area); { Paragraph::new("Work in progress") @@ -32,6 +35,6 @@ pub fn default_view(area: Rect, buf: &mut Buffer) { } { - KeybindingsWidget::new(actions).render(keybindings_area, buf); + KeybindingsWidget::new(ACTIONS).render(keybindings_area, buf); } } diff --git a/src/app/views/main_menu.rs b/src/app/views/main_menu.rs index f674c90..bdf9879 100644 --- a/src/app/views/main_menu.rs +++ b/src/app/views/main_menu.rs @@ -1,5 +1,6 @@ use crate::app::{ App, View, + helpers::main_menu_option_helper, keybindings::{Action, count_largest_group}, widgets::KeybindingsWidget, }; @@ -12,35 +13,24 @@ use ratatui::{ widgets::{Block, Borders, Paragraph, Widget}, }; -fn format_view_string(s: String) -> String { - if let Some(pos) = s - .char_indices() - .filter(|(_, c)| c.is_ascii_uppercase()) - .nth(1) - .map(|(i, _)| i) - { - let (first, second) = s.split_at(pos); - format!("{first} {second}") - } else { - s - } +const ACTIONS: &[Action] = &[ + Action::Up, + Action::Down, + Action::Space, + Action::Quit, + Action::Quit2, +]; + +fn main_menu_layout(area: Rect) -> [Rect; 2] { + Layout::vertical([ + Constraint::Fill(1), + Constraint::Length(count_largest_group(&ACTIONS) + 2), + ]) + .areas(area) } pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) { - let actions: Vec = vec![ - Action::Up, - Action::Down, - Action::Space, - Action::Quit, - Action::Quit2, - ]; - - let vertical_layout: Layout = Layout::vertical([ - Constraint::Fill(1), - Constraint::Length(count_largest_group(&actions) + 2), - ]); - - let [main_menu_area, keybindings_area] = vertical_layout.areas(area); + let [main_menu_area, keybindings_area] = main_menu_layout(area); Block::new() .borders(Borders::LEFT | Borders::TOP | Borders::RIGHT) @@ -78,7 +68,7 @@ pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) { .enumerate() .filter(|(_, v)| v != &&View::MainMenu) .map(|(i, view)| { - let view_string: String = format_view_string(format!("{:?}", view)); + let view_string: String = main_menu_option_helper(format!("{:?}", view)); let styled: Line<'_> = if app.states.main_menu.selected_view == i { Line::from_iter(["> ".cyan(), view_string.yellow()]).yellow() @@ -98,6 +88,6 @@ pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) { } { - KeybindingsWidget::new(actions).render(keybindings_area, buf); + KeybindingsWidget::new(ACTIONS).render(keybindings_area, buf); } } diff --git a/src/app/views/skirmish.rs b/src/app/views/skirmish.rs index 60cb2a7..3b00ae9 100644 --- a/src/app/views/skirmish.rs +++ b/src/app/views/skirmish.rs @@ -12,30 +12,33 @@ use ratatui::{ widgets::{Block, Borders, Padding, Paragraph, Widget}, }; -pub fn skirmish_view(app: &mut App, area: Rect, buf: &mut Buffer) { - let actions: Vec = vec![ - Action::Up, - Action::Down, - Action::Left, - Action::Right, - Action::ScrollUp, - Action::ScrollDown, - Action::ScrollLeft, - Action::ScrollRight, - Action::ZoomIn, - Action::ZoomOut, - Action::Quit, - Action::Quit2, - Action::Esc, - ]; +const ACTIONS: &[Action] = &[ + Action::Up, + Action::Down, + Action::Left, + Action::Right, + Action::ScrollUp, + Action::ScrollDown, + Action::ScrollLeft, + Action::ScrollRight, + Action::ZoomIn, + Action::ZoomOut, + Action::Quit, + Action::Quit2, + Action::Esc, +]; - let vertical_layout: Layout = Layout::vertical([ +fn skirmish_layout(area: Rect) -> [Rect; 3] { + Layout::vertical([ Constraint::Length(4), Constraint::Fill(1), - Constraint::Length(count_largest_group(&actions) + 2), - ]); + Constraint::Length(count_largest_group(&ACTIONS) + 2), + ]) + .areas(area) +} - let [title_area, main_area, keybindings_area] = vertical_layout.areas(area); +pub fn skirmish_view(app: &mut App, area: Rect, buf: &mut Buffer) { + let [title_area, main_area, keybindings_area] = skirmish_layout(area); { let lines: Vec> = Vec::from_iter([ @@ -92,6 +95,6 @@ pub fn skirmish_view(app: &mut App, area: Rect, buf: &mut Buffer) { } { - KeybindingsWidget::new(actions).render(keybindings_area, buf); + KeybindingsWidget::new(ACTIONS).render(keybindings_area, buf); } } diff --git a/src/app/widgets/keybindings.rs b/src/app/widgets/keybindings.rs index e655fe1..9dc1f6b 100644 --- a/src/app/widgets/keybindings.rs +++ b/src/app/widgets/keybindings.rs @@ -32,12 +32,12 @@ impl KeybindingsWidget { /// /// # Parameters /// - /// * `actions` – A vector of actions for which keybindings should be shown. + /// * `actions` – A slice of actions for which keybindings should be shown. /// /// # Returns /// /// A fully‑initialised `KeybindingsWidget` ready for rendering. - pub fn new(actions: Vec) -> Self { + pub fn new(actions: &[Action]) -> Self { let keybindings: Vec> = actions.iter().map(|a| binding_for(*a)).collect();