gamo/src/app.rs

217 lines
5.6 KiB
Rust

use std::time::{Duration, Instant};
use color_eyre::{owo_colors::OwoColorize, Result};
use gilrs::{Gamepad, Gilrs};
use ratatui::{
buffer::Buffer,
crossterm::event::{self, Event, KeyCode, KeyEventKind},
layout::{Alignment, Constraint, Flex, Layout, Rect},
style::{palette::tailwind, Color, Style, Stylize},
symbols::Marker,
widgets::{
canvas::{Canvas, Circle, Shape},
Block, BorderType, Gauge, Padding, Paragraph, Widget,
},
DefaultTerminal,
};
use Constraint::{Fill, Length, Min, Percentage};
use crate::gamepad_manager::GamepadManager;
pub struct App {
pub manager: GamepadManager,
state: AppState,
tick: u64,
}
#[derive(Default, PartialEq, Eq)]
pub enum AppState {
#[default]
Running,
Quitting,
}
impl App {
pub fn new(manager: GamepadManager) -> Self {
App {
manager,
state: AppState::Running,
tick: 0,
}
}
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(20);
let mut last_tick = Instant::now();
while self.state == AppState::Running {
terminal.draw(|frame| frame.render_widget(&self, frame.area()))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if event::poll(timeout)? {
self.handle_events()?;
}
self.handle_gamepad_inputs()?;
if last_tick.elapsed() >= tick_rate {
self.update();
last_tick = Instant::now();
}
}
Ok(())
}
fn handle_gamepad_inputs(&mut self) -> Result<()> {
while let Some(gilrs::Event { id, .. }) = self.manager.gilrs.next_event() {
self.manager.set_active_gamepad(id);
}
Ok(())
}
fn handle_events(&mut self) -> Result<()> {
if let ratatui::crossterm::event::Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Char('r') => {
self.manager.scan_gamepads();
return Ok(());
}
KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => self.quit(),
_ => {}
}
}
}
Ok(())
}
fn update(&mut self) {
self.tick = self.tick.wrapping_add(1);
}
fn quit(&mut self) {
self.state = AppState::Quitting;
}
fn render_title(area: Rect, buf: &mut Buffer) {
Paragraph::new("App Example Title")
.block(
Block::bordered()
.border_type(BorderType::Rounded)
.padding(Padding::top(1)),
)
.alignment(Alignment::Center)
.render(area, buf);
}
fn render_connected_gamepad(&self, area: Rect, buf: &mut Buffer) {
Paragraph::new(format!(
"{} connected gamepads",
self.manager.connected_gamepads()
))
.block(
Block::bordered()
.border_type(BorderType::Rounded)
.padding(Padding::top(1)),
)
.alignment(Alignment::Center)
.bold()
.render(area, buf);
}
fn render_right_buttons(&self, area: Rect, buf: &mut Buffer) {
let north_button = self.create_action_button_ui(gilrs::Button::North);
let east_button = self.create_action_button_ui(gilrs::Button::East);
let south_button = self.create_action_button_ui(gilrs::Button::South);
let west_button = self.create_action_button_ui(gilrs::Button::West);
let layers = Layout::vertical([Fill(1), Fill(1), Fill(1)]);
let [top, mid, bot] = layers.areas(area);
let top_layer = Layout::horizontal([Fill(1), Fill(1), Fill(1)]);
let [_, north, _] = top_layer.areas(top);
let mid_layer = Layout::horizontal([Fill(1), Fill(1), Fill(1)]);
let [west, _, east] = mid_layer.areas(mid);
let bot_layer = Layout::horizontal([Fill(1), Fill(1), Fill(1)]);
let [_, south, _] = bot_layer.areas(bot);
north_button.render(north, buf);
east_button.render(east, buf);
south_button.render(south, buf);
west_button.render(west, buf);
}
fn create_action_button_ui(&self, dir: gilrs::Button) -> Block {
let color = match self.manager.active_gamepad() {
Ok(gamepad) => match gamepad.is_pressed(dir) {
true => Color::Green,
false => Color::Gray,
},
Err(_) => Color::Gray,
};
Block::new().style(Style::default().bg(color))
}
}
impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) {
let layout = Layout::vertical([Length(5), Min(0), Length(5)]);
let [header_area, inner_area, footer_area] = layout.areas(area);
let header_layout = Layout::horizontal([Fill(1), Fill(4)]);
let [tabs_area, title_area] = header_layout.areas(header_area);
let gamepad_layout = Layout::vertical([
Percentage(10),
Percentage(50),
Percentage(30),
Percentage(10),
]);
let [_, buttons, joysticks, _] = gamepad_layout.areas(inner_area);
let buttons_layout = Layout::horizontal([Fill(1), Fill(2), Fill(2), Fill(2), Fill(1)]);
let [_, left_buttons, mid_buttons, right_buttons, _] = buttons_layout.areas(buttons);
// let details_layout = Layout::horizontal([Fill(2), Fill(2)]);
// let [right_details_area, left_details_area] = details_layout.areas(details_area);
App::render_title(title_area, buf);
self.render_connected_gamepad(tabs_area, buf);
Block::bordered().render(inner_area, buf);
// Block::bordered()
// .style(Style::default().bg(tailwind::GREEN.c900))
// .render(right_details_area, buf);
// Block::new()
// .style(Style::default().bg(tailwind::YELLOW.c300))
// .render(details_area, buf);
self.render_right_buttons(right_buttons, buf);
// Block::bordered()
// .style(Style::default().bg(tailwind::TEAL.c300))
// .render(left_details_area, buf);
// let gauge_progress: u16 = self
// .tick
// .wrapping_div(100)
// .clamp(0, 100)
// .try_into()
// .unwrap();
// Gauge::default()
// .gauge_style(tailwind::BLUE.c900)
// .percent(gauge_progress)
// .render(right_details_area, buf);
// self.render_title(title_area, buf);
// self.render_tabs(tabs_area, buf);
// self.selected_tab.render(inner_area, buf);
// render_footer(footer_area, buf);
}
}