Implemented most buttons and threaded force feedback

master
Wynd 2024-10-19 14:46:46 +03:00
parent 4b87e6c013
commit fe162b60ff
5 changed files with 219 additions and 191 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
/logs

View File

@ -1,110 +0,0 @@
2024-10-18T22:01:48.001029Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.001150Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.474890Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.474947Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.495298Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.495345Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.618136Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.659732Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.659792Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.743791Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:48.743851Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:49.058470Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:49.058527Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:49.099680Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:49.099736Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:49.579664Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:49.579721Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:50.259909Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:50.259973Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:50.259992Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:50.657504Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)
2024-10-18T22:01:50.657561Z INFO src/gamepad_manager.rs:33: Selected gamepad changed to: Some(
GamepadId(
0,
),
)

View File

@ -1,6 +1,6 @@
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use color_eyre::{owo_colors::OwoColorize, Result}; use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Result};
use gilrs::{Gamepad, Gilrs}; use gilrs::{Gamepad, Gilrs};
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
@ -78,6 +78,8 @@ impl App {
self.manager.scan_gamepads(); self.manager.scan_gamepads();
return Ok(()); 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(), KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => self.quit(),
_ => {} _ => {}
} }
@ -94,33 +96,35 @@ impl App {
self.state = AppState::Quitting; self.state = AppState::Quitting;
} }
fn render_title(area: Rect, buf: &mut Buffer) { // fn render_title(area: Rect, buf: &mut Buffer) {
Paragraph::new("App Example Title") // Paragraph::new("App Example Title")
.block( // .block(
Block::bordered() // Block::bordered()
.border_type(BorderType::Rounded) // .border_type(BorderType::Rounded)
.padding(Padding::top(1)), // .padding(Padding::top(1)),
) // )
.alignment(Alignment::Center) // .alignment(Alignment::Center)
.render(area, buf); // .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_connected_gamepad(&self, area: Rect, buf: &mut Buffer) { fn render_action_buttons(&self, area: Rect, buf: &mut Buffer) {
Paragraph::new(format!( Block::bordered().render(area, buf);
"{} 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 north_button = self.create_action_button_ui(gilrs::Button::North);
let east_button = self.create_action_button_ui(gilrs::Button::East); let east_button = self.create_action_button_ui(gilrs::Button::East);
let south_button = self.create_action_button_ui(gilrs::Button::South); let south_button = self.create_action_button_ui(gilrs::Button::South);
@ -144,13 +148,113 @@ impl App {
west_button.render(west, 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 { fn create_action_button_ui(&self, dir: gilrs::Button) -> Block {
let color = match self.manager.active_gamepad() { let color = match self.manager.active_gamepad() {
Ok(gamepad) => match gamepad.is_pressed(dir) { Ok(gamepad) => match gamepad.is_pressed(dir) {
true => Color::Green, true => Color::Green,
false => Color::Gray, false => Color::DarkGray,
}, },
Err(_) => Color::Gray, Err(_) => Color::DarkGray,
}; };
Block::new().style(Style::default().bg(color)) Block::new().style(Style::default().bg(color))
@ -159,29 +263,26 @@ impl App {
impl Widget for &App { impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let layout = Layout::vertical([Length(5), Min(0), Length(5)]); let layout = Layout::vertical([Percentage(100)]);
let [header_area, inner_area, footer_area] = layout.areas(area); let [inner_area] = layout.areas(area);
let header_layout = Layout::horizontal([Fill(1), Fill(4)]); let gamepad_layout = Layout::vertical([Fill(2), Fill(4), Fill(3)]);
let [tabs_area, title_area] = header_layout.areas(header_area); let [triggers, buttons, joysticks] = gamepad_layout.areas(inner_area);
let gamepad_layout = Layout::vertical([ let buttons_layout = Layout::horizontal([Fill(2), Fill(2), Fill(2)]);
Percentage(10), let [left_buttons, mid_buttons, right_buttons] = buttons_layout.areas(buttons);
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 details_layout = Layout::horizontal([Fill(2), Fill(2)]);
// let [right_details_area, left_details_area] = details_layout.areas(details_area); // let [right_details_area, left_details_area] = details_layout.areas(details_area);
App::render_title(title_area, buf); let mut title_label = Block::bordered();
self.render_connected_gamepad(tabs_area, buf); title_label = match self.manager.active_gamepad() {
Block::bordered().render(inner_area, buf); 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() // Block::bordered()
// .style(Style::default().bg(tailwind::GREEN.c900)) // .style(Style::default().bg(tailwind::GREEN.c900))
@ -191,7 +292,10 @@ impl Widget for &App {
// .style(Style::default().bg(tailwind::YELLOW.c300)) // .style(Style::default().bg(tailwind::YELLOW.c300))
// .render(details_area, buf); // .render(details_area, buf);
self.render_right_buttons(right_buttons, 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() // Block::bordered()
// .style(Style::default().bg(tailwind::TEAL.c300)) // .style(Style::default().bg(tailwind::TEAL.c300))

View File

@ -1,3 +1,5 @@
use std::{thread, time::Duration};
use color_eyre::{eyre::eyre, Result}; use color_eyre::{eyre::eyre, Result};
use gilrs::{ use gilrs::{
ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks}, ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks},
@ -24,13 +26,9 @@ impl GamepadManager {
}) })
} }
pub fn select_gamepad(&mut self, id: impl Into<Option<GamepadId>>) { pub fn set_active_gamepad(&mut self, id: impl Into<Option<GamepadId>>) {
self.active_gamepad = id.into(); self.active_gamepad = id.into();
} // tracing::info!("Selected gamepad changed to: {:#?}", self.active_gamepad);
pub fn set_active_gamepad(&mut self, id: GamepadId) {
self.active_gamepad = Some(id);
tracing::info!("Selected gamepad changed to: {:#?}", self.active_gamepad);
} }
pub fn active_gamepad(&self) -> Result<Gamepad<'_>> { pub fn active_gamepad(&self) -> Result<Gamepad<'_>> {
@ -52,7 +50,7 @@ impl GamepadManager {
let mut gamepads = vec![]; let mut gamepads = vec![];
// Iterate over all connected gamepads // Iterate over all connected gamepads
for (id, gamepad) in self.gilrs.gamepads() { for (id, _) in self.gilrs.gamepads() {
// println!("{} is {:?}", gamepad.name(), gamepad.power_info()); // println!("{} is {:?}", gamepad.name(), gamepad.power_info());
// println!("Has Force Feedback Enabled ? {}", gamepad.is_ff_supported()); // println!("Has Force Feedback Enabled ? {}", gamepad.is_ff_supported());
// if gamepad.is_ff_supported() { // if gamepad.is_ff_supported() {
@ -65,34 +63,69 @@ impl GamepadManager {
self.gamepads = gamepads; self.gamepads = gamepads;
} }
pub fn test_ff(&mut self, gamepad_id: GamepadId) { pub fn test_weak_ff(&mut self) {
let gamepad = self.gilrs.gamepad(gamepad_id); let Some(id) = self.active_gamepad
else {
tracing::warn!("No active gamepad found!");
return;
};
let duration: u32 = 1000; thread::spawn(move || {
let ff_play_ticks = Ticks::from_ms(duration); let mut gilrs = Gilrs::new().unwrap();
let effect = EffectBuilder::new() let gamepad = gilrs.gamepad(id);
.add_effect(BaseEffect {
kind: BaseEffectType::Strong { magnitude: 40_000 },
scheduling: Replay {
play_for: ff_play_ticks,
with_delay: ff_play_ticks * 2,
..Default::default()
},
envelope: Default::default(),
})
.add_effect(BaseEffect {
kind: BaseEffectType::Weak { magnitude: 40_000 },
scheduling: Replay {
after: ff_play_ticks,
play_for: ff_play_ticks,
..Default::default()
},
envelope: Default::default(),
})
.add_gamepad(&gamepad)
.finish(&mut self.gilrs)
.unwrap();
effect.play().unwrap(); let duration = Ticks::from_ms(1000);
let delay = Ticks::from_ms(500);
let effect = EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Weak { magnitude: 40_000 },
scheduling: Replay {
play_for: duration,
with_delay: delay,
..Default::default()
},
envelope: Default::default(),
})
.add_gamepad(&gamepad)
.finish(&mut gilrs)
.unwrap();
effect.play().unwrap();
thread::sleep(Duration::from_millis(4500));
});
}
pub fn test_strong_ff(&mut self) {
let Some(id) = self.active_gamepad
else {
tracing::warn!("No active gamepad found!");
return;
};
thread::spawn(move || {
let mut gilrs = Gilrs::new().unwrap();
let gamepad = gilrs.gamepad(id);
let duration = Ticks::from_ms(1000);
let delay = Ticks::from_ms(500);
let effect = EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Strong { magnitude: 40_000 },
scheduling: Replay {
play_for: duration,
with_delay: delay,
..Default::default()
},
envelope: Default::default(),
})
.add_gamepad(&gamepad)
.finish(&mut gilrs)
.unwrap();
effect.play().unwrap();
thread::sleep(Duration::from_millis(4500));
});
} }
} }

View File

@ -62,4 +62,4 @@ fn init_tracing() {
); );
tracing::subscriber::set_global_default(sub).unwrap(); tracing::subscriber::set_global_default(sub).unwrap();
} }