generated from GarandPLG/rust-flake-template
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
This commit is contained in:
+12
-1
@@ -5,6 +5,7 @@ use crate::{
|
|||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame,
|
||||||
crossterm::event::{self, KeyEvent},
|
crossterm::event::{self, KeyEvent},
|
||||||
|
layout::Rect,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::Result,
|
io::Result,
|
||||||
@@ -20,6 +21,7 @@ pub enum Event {
|
|||||||
pub struct App {
|
pub struct App {
|
||||||
pub exit: bool,
|
pub exit: bool,
|
||||||
pub view: View,
|
pub view: View,
|
||||||
|
pub window_area: Rect,
|
||||||
pub states: GameStates,
|
pub states: GameStates,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ impl App {
|
|||||||
Self {
|
Self {
|
||||||
exit: false,
|
exit: false,
|
||||||
view: args.view,
|
view: args.view,
|
||||||
|
window_area: Rect::default(),
|
||||||
states,
|
states,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +59,15 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, frame: &mut Frame<'_>) {
|
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<()> {
|
fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
pub mod block_title;
|
pub mod block_title;
|
||||||
|
pub mod main_menu_option;
|
||||||
pub mod zoom_level;
|
pub mod zoom_level;
|
||||||
|
|
||||||
pub use block_title::{block_single_title_helper, block_title_helper};
|
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};
|
pub use zoom_level::{CellSizes, cell_size_helper};
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ pub fn event_to_action(event: &KeyEvent) -> Option<Action> {
|
|||||||
/// It counts how many times each group appears in the supplied actions and
|
/// 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
|
/// returns the highest count. This can be used, for example, to size UI
|
||||||
/// elements that display groups of bindings.
|
/// elements that display groups of bindings.
|
||||||
pub fn count_largest_group(actions: &Vec<Action>) -> u16 {
|
pub fn count_largest_group(actions: &[Action]) -> u16 {
|
||||||
let mut group_counts: HashMap<Group, u16> = HashMap::new();
|
let mut group_counts: HashMap<Group, u16> = HashMap::new();
|
||||||
|
|
||||||
for action in actions {
|
for action in actions {
|
||||||
|
|||||||
@@ -9,15 +9,18 @@ use ratatui::{
|
|||||||
widgets::{Block, Borders, Paragraph, Widget},
|
widgets::{Block, Borders, Paragraph, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn default_view(area: Rect, buf: &mut Buffer) {
|
const ACTIONS: &[Action] = &[Action::Quit, Action::Quit2, Action::Esc];
|
||||||
let actions: Vec<Action> = vec![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::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")
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-23
@@ -1,5 +1,6 @@
|
|||||||
use crate::app::{
|
use crate::app::{
|
||||||
App, View,
|
App, View,
|
||||||
|
helpers::main_menu_option_helper,
|
||||||
keybindings::{Action, count_largest_group},
|
keybindings::{Action, count_largest_group},
|
||||||
widgets::KeybindingsWidget,
|
widgets::KeybindingsWidget,
|
||||||
};
|
};
|
||||||
@@ -12,35 +13,24 @@ use ratatui::{
|
|||||||
widgets::{Block, Borders, Paragraph, Widget},
|
widgets::{Block, Borders, Paragraph, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn format_view_string(s: String) -> String {
|
const ACTIONS: &[Action] = &[
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) {
|
|
||||||
let actions: Vec<Action> = vec![
|
|
||||||
Action::Up,
|
Action::Up,
|
||||||
Action::Down,
|
Action::Down,
|
||||||
Action::Space,
|
Action::Space,
|
||||||
Action::Quit,
|
Action::Quit,
|
||||||
Action::Quit2,
|
Action::Quit2,
|
||||||
];
|
];
|
||||||
|
|
||||||
let vertical_layout: Layout = Layout::vertical([
|
fn main_menu_layout(area: Rect) -> [Rect; 2] {
|
||||||
|
Layout::vertical([
|
||||||
Constraint::Fill(1),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(count_largest_group(&actions) + 2),
|
Constraint::Length(count_largest_group(&ACTIONS) + 2),
|
||||||
]);
|
])
|
||||||
|
.areas(area)
|
||||||
|
}
|
||||||
|
|
||||||
let [main_menu_area, keybindings_area] = vertical_layout.areas(area);
|
pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) {
|
||||||
|
let [main_menu_area, keybindings_area] = main_menu_layout(area);
|
||||||
|
|
||||||
Block::new()
|
Block::new()
|
||||||
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT)
|
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT)
|
||||||
@@ -78,7 +68,7 @@ pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, v)| v != &&View::MainMenu)
|
.filter(|(_, v)| v != &&View::MainMenu)
|
||||||
.map(|(i, view)| {
|
.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 {
|
let styled: Line<'_> = if app.states.main_menu.selected_view == i {
|
||||||
Line::from_iter(["> ".cyan(), view_string.yellow()]).yellow()
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ use ratatui::{
|
|||||||
widgets::{Block, Borders, Padding, Paragraph, Widget},
|
widgets::{Block, Borders, Padding, Paragraph, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn skirmish_view(app: &mut App, area: Rect, buf: &mut Buffer) {
|
const ACTIONS: &[Action] = &[
|
||||||
let actions: Vec<Action> = vec![
|
|
||||||
Action::Up,
|
Action::Up,
|
||||||
Action::Down,
|
Action::Down,
|
||||||
Action::Left,
|
Action::Left,
|
||||||
@@ -27,15 +26,19 @@ pub fn skirmish_view(app: &mut App, area: Rect, buf: &mut Buffer) {
|
|||||||
Action::Quit,
|
Action::Quit,
|
||||||
Action::Quit2,
|
Action::Quit2,
|
||||||
Action::Esc,
|
Action::Esc,
|
||||||
];
|
];
|
||||||
|
|
||||||
let vertical_layout: Layout = Layout::vertical([
|
fn skirmish_layout(area: Rect) -> [Rect; 3] {
|
||||||
|
Layout::vertical([
|
||||||
Constraint::Length(4),
|
Constraint::Length(4),
|
||||||
Constraint::Fill(1),
|
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<Line<'_>> = Vec::from_iter([
|
let lines: Vec<Line<'_>> = 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ impl KeybindingsWidget {
|
|||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
///
|
///
|
||||||
/// * `actions` – A vector of actions for which keybindings should be shown.
|
/// * `actions` – A slice of actions for which keybindings should be shown.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// A fully‑initialised `KeybindingsWidget` ready for rendering.
|
/// A fully‑initialised `KeybindingsWidget` ready for rendering.
|
||||||
pub fn new(actions: Vec<Action>) -> Self {
|
pub fn new(actions: &[Action]) -> Self {
|
||||||
let keybindings: Vec<Option<&'static KeyBinding>> =
|
let keybindings: Vec<Option<&'static KeyBinding>> =
|
||||||
actions.iter().map(|a| binding_for(*a)).collect();
|
actions.iter().map(|a| binding_for(*a)).collect();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user