use std::time::{Duration, Instant}; use color_eyre::{eyre::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('v') => self.manager.test_weak_ff(), KeyCode::Char('V') => self.manager.test_strong_ff(), 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_action_buttons(&self, area: Rect, buf: &mut Buffer) { Block::bordered().render(area, buf); 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 render_dpad_buttons(&self, area: Rect, buf: &mut Buffer) { Block::bordered().render(area, buf); let up_button = self.create_action_button_ui(gilrs::Button::DPadUp); let right_button = self.create_action_button_ui(gilrs::Button::DPadRight); let down_button = self.create_action_button_ui(gilrs::Button::DPadDown); let left_button = self.create_action_button_ui(gilrs::Button::DPadLeft); 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 [_, up, _] = top_layer.areas(top); let mid_layer = Layout::horizontal([Fill(1), Fill(1), Fill(1)]); let [left, _, right] = mid_layer.areas(mid); let bot_layer = Layout::horizontal([Fill(1), Fill(1), Fill(1)]); let [_, down, _] = bot_layer.areas(bot); up_button.render(up, buf); right_button.render(right, buf); down_button.render(down, buf); left_button.render(left, buf); } fn render_setting_buttons(&self, area: Rect, buf: &mut Buffer) { Block::bordered().render(area, buf); let select_button = self.create_action_button_ui(gilrs::Button::Select); let start_button = self.create_action_button_ui(gilrs::Button::Start); let layers = Layout::horizontal([Fill(1), Fill(1), Fill(1), Fill(1), Fill(1)]); let [_, select_area, _, start_area, _] = layers.areas(area); let select_layer = Layout::vertical([Fill(1), Fill(1), Fill(1), Fill(1)]); let [_, select, _, _] = select_layer.areas(select_area); let start_layer = Layout::vertical([Fill(1), Fill(1), Fill(1), Fill(1)]); let [_, start, _, _] = start_layer.areas(start_area); select_button.render(select, buf); start_button.render(start, buf); } fn render_trigger_buttons(&self, area: Rect, buf: &mut Buffer) { Block::bordered().render(area, buf); let r1_button = self.create_action_button_ui(gilrs::Button::RightTrigger); let r2_button = self.create_action_button_ui(gilrs::Button::RightTrigger2); let l1_button = self.create_action_button_ui(gilrs::Button::LeftTrigger); let l2_button = self.create_action_button_ui(gilrs::Button::LeftTrigger2); let layers = Layout::horizontal([ Fill(2), Fill(1), Fill(1), Fill(1), Fill(4), Fill(1), Fill(1), Fill(1), Fill(2), ]); let [_, l2_area, _, l1_area, _, r1_area, _, r2_area, _] = layers.areas(area); let r1_layer = Layout::vertical([Fill(2), Fill(1), Fill(2)]); let [_, r1, _] = r1_layer.areas(r1_area); let r2_layer = Layout::vertical([Fill(1), Fill(3), Fill(1)]); let [_, r2, _] = r2_layer.areas(r2_area); let l1_layer = Layout::vertical([Fill(2), Fill(1), Fill(2)]); let [_, l1, _] = l1_layer.areas(l1_area); let l2_layer = Layout::vertical([Fill(1), Fill(3), Fill(1)]); let [_, l2, _] = l2_layer.areas(l2_area); r1_button.render(r1, buf); r2_button.render(r2, buf); l1_button.render(l1, buf); l2_button.render(l2, buf); //TODO: For PS4+ controllers doesn't have dedicated trigger push force // let gauge_percentage = match self.manager.active_gamepad() { // Ok(gamepad) => match gamepad.axis_data(gilrs::Axis::Unknown) { // Some(ad) => ad.value(), // None => 0.0, // }, // Err(_) => 0.0, // }; // let gauge_percentage = 0; // Gauge::default() // .gauge_style(Color::Green) // .bg(Color::DarkGray) // .ratio(gauge_percentage.into()) // .render(r2, 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::DarkGray, }, Err(_) => Color::DarkGray, }; Block::new().style(Style::default().bg(color)) } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { let layout = Layout::vertical([Percentage(100)]); let [inner_area] = layout.areas(area); let gamepad_layout = Layout::vertical([Fill(2), Fill(4), Fill(3)]); let [triggers, buttons, joysticks] = gamepad_layout.areas(inner_area); let buttons_layout = Layout::horizontal([Fill(2), Fill(2), Fill(2)]); 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); let mut title_label = Block::bordered(); title_label = match self.manager.active_gamepad() { Ok(gp) => title_label .title(gp.name().to_string()) .title_style(Color::Rgb(255, 165, 35)), Err(_) => title_label.title("No active device".to_string()), }; title_label.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_dpad_buttons(left_buttons, buf); self.render_setting_buttons(mid_buttons, buf); self.render_action_buttons(right_buttons, buf); self.render_trigger_buttons(triggers, 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); } }