321 lines
9.3 KiB
Rust
321 lines
9.3 KiB
Rust
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);
|
|
}
|
|
}
|