From c2d89772336c52cce0b629f6ffc506eb1f221867 Mon Sep 17 00:00:00 2001 From: Silas Bartha Date: Thu, 21 Nov 2024 12:33:58 -0500 Subject: Thu Nov 21 12:33:58 PM EST 2024 --- Cargo.toml | 10 +- src/display/systems.rs | 1 - src/input/components.rs | 4 + src/input/mod.rs | 3 + src/input/resources.rs | 96 +++++++-------- src/input/systems.rs | 306 +++++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 17 +-- src/widgets/mod.rs | 3 + src/widgets/systems.rs | 6 + 9 files changed, 372 insertions(+), 74 deletions(-) create mode 100644 src/input/components.rs diff --git a/Cargo.toml b/Cargo.toml index 1c434b1..7134c75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_terminal_display" -version = "0.4.3" +version = "0.4.4" edition = "2021" license = "0BSD OR MIT OR Apache-2.0" description = "A plugin for the Bevy game engine which enables rendering to a terminal using unicode braille characters." @@ -8,15 +8,21 @@ repository = "https://github.com/exvacuum/bevy_terminal_display" [dependencies] crossbeam-channel = "0.5" -crossterm = "0.28" downcast-rs = "1.2" once_cell = "1.19" bevy_headless_render = "0.1" bevy_dither_post_process = "0.2" ratatui = "0.28" color-eyre = "0.6" +leafwing-input-manager = "0.15" +serde = "1.0" +smol_str = "0.2" [dependencies.bevy] version = "0.14" default-features = false features = ["bevy_render"] + +[dependencies.crossterm] +version = "0.28" +features = ["serde"] diff --git a/src/display/systems.rs b/src/display/systems.rs index 120272b..706d296 100644 --- a/src/display/systems.rs +++ b/src/display/systems.rs @@ -72,7 +72,6 @@ pub fn print_to_terminal( frame.render_widget( Paragraph::new(string) .white() - .bold() .wrap(Wrap { trim: true }), frame.area(), ); diff --git a/src/input/components.rs b/src/input/components.rs new file mode 100644 index 0000000..b386457 --- /dev/null +++ b/src/input/components.rs @@ -0,0 +1,4 @@ +use bevy::prelude::*; + +#[derive(Debug, Component)] +pub struct DummyWindow; diff --git a/src/input/mod.rs b/src/input/mod.rs index 80c36bc..e582555 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -6,3 +6,6 @@ pub mod resources; /// Systems for this module pub(crate) mod systems; + +/// Components for this module +pub(crate) mod components; diff --git a/src/input/resources.rs b/src/input/resources.rs index a733984..0324593 100644 --- a/src/input/resources.rs +++ b/src/input/resources.rs @@ -2,54 +2,54 @@ use bevy::{prelude::*, utils::HashSet}; use crossterm::event::{Event, KeyCode}; use std::sync::{Arc, Mutex}; -/// Resource containing currently pressed and released keys -#[derive(Resource, Default)] -pub struct TerminalInput { - pressed_keys: HashSet, - just_pressed_keys: HashSet, - just_released_keys: HashSet, -} - -impl TerminalInput { - /// Gets whether the given key is pressed - pub fn is_pressed(&self, code: KeyCode) -> bool { - self.pressed_keys.contains(&code) - } - - /// Gets whether the given key was just pressed - pub fn just_pressed(&self, code: KeyCode) -> bool { - self.just_pressed_keys.contains(&code) - } - - /// Gets whether the given key was just released - pub fn just_released(&self, code: KeyCode) -> bool { - self.just_released_keys.contains(&code) - } - - /// Sets given key to pressed - pub(super) fn press(&mut self, code: KeyCode) { - if !self.pressed_keys.contains(&code) { - self.pressed_keys.insert(code); - self.just_pressed_keys.insert(code); - } - } - - /// Sets given key to released and removes pressed state - pub(super) fn release(&mut self, code: KeyCode) { - self.pressed_keys.remove(&code); - self.just_released_keys.insert(code); - } - - /// Clears all just released keys - pub(super) fn clear_just_released(&mut self) { - self.just_released_keys.clear(); - } - - /// Clears all just pressed keys - pub(super) fn clear_just_pressed(&mut self) { - self.just_pressed_keys.clear(); - } -} +// /// Resource containing currently pressed and released keys +// #[derive(Resource, Default)] +// pub struct TerminalInput { +// pressed_keys: HashSet, +// just_pressed_keys: HashSet, +// just_released_keys: HashSet, +// } +// +// impl TerminalInput { +// /// Gets whether the given key is pressed +// pub fn is_pressed(&self, code: KeyCode) -> bool { +// self.pressed_keys.contains(&code) +// } +// +// /// Gets whether the given key was just pressed +// pub fn just_pressed(&self, code: KeyCode) -> bool { +// self.just_pressed_keys.contains(&code) +// } +// +// /// Gets whether the given key was just released +// pub fn just_released(&self, code: KeyCode) -> bool { +// self.just_released_keys.contains(&code) +// } +// +// /// Sets given key to pressed +// pub(super) fn press(&mut self, code: KeyCode) { +// if !self.pressed_keys.contains(&code) { +// self.pressed_keys.insert(code); +// self.just_pressed_keys.insert(code); +// } +// } +// +// /// Sets given key to released and removes pressed state +// pub(super) fn release(&mut self, code: KeyCode) { +// self.pressed_keys.remove(&code); +// self.just_released_keys.insert(code); +// } +// +// /// Clears all just released keys +// pub(super) fn clear_just_released(&mut self) { +// self.just_released_keys.clear(); +// } +// +// /// Clears all just pressed keys +// pub(super) fn clear_just_pressed(&mut self) { +// self.just_pressed_keys.clear(); +// } +// } /// Event queue for crossterm input event thread #[derive(Resource, Default)] diff --git a/src/input/systems.rs b/src/input/systems.rs index 9be9f25..e851977 100644 --- a/src/input/systems.rs +++ b/src/input/systems.rs @@ -1,12 +1,21 @@ use std::cmp::Ordering; -use bevy::prelude::*; -use crossterm::event::{read, Event, KeyEvent, KeyEventKind}; +use bevy::{ + input::{keyboard::KeyboardInput, ButtonState}, + prelude::*, +}; +use crossterm::event::{read, Event, KeyEvent, KeyEventKind, MediaKeyCode, ModifierKeyCode}; +use smol_str::SmolStr; -use super::{events::TerminalInputEvent, resources::{EventQueue, TerminalInput}}; +use super::{ + components::DummyWindow, + events::TerminalInputEvent, + resources::EventQueue, +}; /// Initializes event queue and thread -pub fn setup_input(event_queue: Res) { +pub fn setup_input(mut commands: Commands, event_queue: Res) { + commands.spawn(DummyWindow); let event_queue = event_queue.0.clone(); std::thread::spawn(move || { loop { @@ -26,30 +35,295 @@ pub fn setup_input(event_queue: Res) { /// Reads events from queue and broadcasts corresponding `TerminalInputEvent`s pub fn input_handling( event_queue: Res, - mut input: ResMut, - mut event_writer: EventWriter, + dummy_window_query: Query>, + mut terminal_event_writer: EventWriter, + mut key_event_writer: EventWriter, ) { - input.clear_just_released(); - input.clear_just_pressed(); let mut event_queue = event_queue.0.lock().unwrap(); let mut key_events = Vec::::new(); while let Some(event) = event_queue.pop() { if let Event::Key(event) = event { key_events.push(event); } - event_writer.send(TerminalInputEvent(event)); + terminal_event_writer.send(TerminalInputEvent(event)); } key_events.sort_by(|&a, &b| a.kind.partial_cmp(&b.kind).unwrap_or(Ordering::Equal)); + let window = dummy_window_query.single(); for event in key_events { - match event.kind { - KeyEventKind::Press => { - input.press(event.code); - } - KeyEventKind::Release => { - input.release(event.code); + if let Some(key_code) = crossterm_keycode_to_bevy_keycode(event.code) { + if let Some(logical_key) = crossterm_keycode_to_bevy_key(event.code) { + match event.kind { + KeyEventKind::Press | KeyEventKind::Repeat => { + // input.press(event.code); + key_event_writer.send(KeyboardInput { + key_code, + logical_key, + state: ButtonState::Pressed, + window, + }); + } + KeyEventKind::Release => { + key_event_writer.send(KeyboardInput { + key_code, + logical_key, + state: ButtonState::Released, + window, + }); + } + } } - _ => (), } } } + +fn crossterm_keycode_to_bevy_keycode( + crossterm_keycode: crossterm::event::KeyCode, +) -> Option { + use bevy::input::keyboard::KeyCode as BKey; + use crossterm::event::KeyCode as CKey; + match crossterm_keycode { + CKey::Backspace => Some(BKey::Backspace), + CKey::Enter => Some(BKey::Enter), + CKey::Left => Some(BKey::ArrowLeft), + CKey::Right => Some(BKey::ArrowRight), + CKey::Up => Some(BKey::ArrowUp), + CKey::Down => Some(BKey::ArrowDown), + CKey::Home => Some(BKey::Home), + CKey::End => Some(BKey::End), + CKey::PageUp => Some(BKey::PageUp), + CKey::PageDown => Some(BKey::PageDown), + CKey::Tab | CKey::BackTab => Some(BKey::Tab), + CKey::Delete => Some(BKey::Delete), + CKey::Insert => Some(BKey::Insert), + CKey::F(num) => match num { + 1 => Some(BKey::F1), + 2 => Some(BKey::F2), + 3 => Some(BKey::F3), + 4 => Some(BKey::F4), + 5 => Some(BKey::F5), + 6 => Some(BKey::F6), + 7 => Some(BKey::F7), + 8 => Some(BKey::F8), + 9 => Some(BKey::F9), + 10 => Some(BKey::F10), + 11 => Some(BKey::F11), + 12 => Some(BKey::F12), + 13 => Some(BKey::F13), + 14 => Some(BKey::F14), + 15 => Some(BKey::F15), + 16 => Some(BKey::F16), + 17 => Some(BKey::F17), + 18 => Some(BKey::F18), + 19 => Some(BKey::F19), + 20 => Some(BKey::F20), + 21 => Some(BKey::F21), + 22 => Some(BKey::F22), + 23 => Some(BKey::F23), + 24 => Some(BKey::F24), + 25 => Some(BKey::F25), + 26 => Some(BKey::F26), + 27 => Some(BKey::F27), + 28 => Some(BKey::F28), + 29 => Some(BKey::F29), + 30 => Some(BKey::F30), + 31 => Some(BKey::F31), + 32 => Some(BKey::F32), + 33 => Some(BKey::F33), + 34 => Some(BKey::F34), + 35 => Some(BKey::F35), + _ => None, + }, + CKey::Char(c) => match c { + '1' | '!' => Some(BKey::Digit1), + '2' | '@' => Some(BKey::Digit2), + '3' | '#' => Some(BKey::Digit3), + '4' | '$' => Some(BKey::Digit4), + '5' | '%' => Some(BKey::Digit5), + '6' | '^' => Some(BKey::Digit5), + '7' | '&' => Some(BKey::Digit7), + '8' | '*' => Some(BKey::Digit8), + '9' | '(' => Some(BKey::Digit9), + '0' | ')' => Some(BKey::Digit0), + '-' | '_' => Some(BKey::Minus), + '=' | '+' => Some(BKey::Equal), + '`' | '~' => Some(BKey::Backquote), + 'q' | 'Q' => Some(BKey::KeyQ), + 'w' | 'W' => Some(BKey::KeyW), + 'e' | 'E' => Some(BKey::KeyE), + 'r' | 'R' => Some(BKey::KeyR), + 't' | 'T' => Some(BKey::KeyT), + 'y' | 'Y' => Some(BKey::KeyY), + 'u' | 'U' => Some(BKey::KeyU), + 'i' | 'I' => Some(BKey::KeyI), + 'o' | 'O' => Some(BKey::KeyO), + 'p' | 'P' => Some(BKey::KeyP), + '[' | '{' => Some(BKey::BracketLeft), + ']' | '}' => Some(BKey::BracketRight), + 'a' | 'A' => Some(BKey::KeyA), + 's' | 'S' => Some(BKey::KeyS), + 'd' | 'D' => Some(BKey::KeyD), + 'f' | 'F' => Some(BKey::KeyF), + 'g' | 'G' => Some(BKey::KeyG), + 'h' | 'H' => Some(BKey::KeyH), + 'j' | 'J' => Some(BKey::KeyJ), + 'k' | 'K' => Some(BKey::KeyK), + 'l' | 'L' => Some(BKey::KeyL), + ';' | ':' => Some(BKey::Semicolon), + '\'' | '"' => Some(BKey::Slash), + 'z' | 'Z' => Some(BKey::KeyZ), + 'x' | 'X' => Some(BKey::KeyX), + 'c' | 'C' => Some(BKey::KeyC), + 'v' | 'V' => Some(BKey::KeyV), + 'b' | 'B' => Some(BKey::KeyB), + 'n' | 'N' => Some(BKey::KeyN), + 'm' | 'M' => Some(BKey::KeyM), + ',' | '<' => Some(BKey::Comma), + '.' | '>' => Some(BKey::Period), + '/' | '?' => Some(BKey::Slash), + ' ' => Some(BKey::Space), + _ => None, + }, + CKey::Null => None, + CKey::Esc => Some(BKey::Escape), + CKey::CapsLock => Some(BKey::CapsLock), + CKey::ScrollLock => Some(BKey::ScrollLock), + CKey::NumLock => Some(BKey::NumLock), + CKey::PrintScreen => Some(BKey::PrintScreen), + CKey::Pause => Some(BKey::Pause), + CKey::Menu => Some(BKey::ContextMenu), + CKey::KeypadBegin => None, + CKey::Media(media) => match media { + MediaKeyCode::Play => Some(BKey::MediaPlayPause), + MediaKeyCode::Pause => Some(BKey::Pause), + MediaKeyCode::PlayPause => Some(BKey::MediaPlayPause), + MediaKeyCode::Reverse => None, + MediaKeyCode::Stop => Some(BKey::MediaStop), + MediaKeyCode::FastForward => Some(BKey::MediaTrackNext), + MediaKeyCode::Rewind => Some(BKey::MediaTrackPrevious), + MediaKeyCode::TrackNext => Some(BKey::MediaTrackNext), + MediaKeyCode::TrackPrevious => Some(BKey::MediaTrackPrevious), + MediaKeyCode::Record => None, + MediaKeyCode::LowerVolume => Some(BKey::AudioVolumeDown), + MediaKeyCode::RaiseVolume => Some(BKey::AudioVolumeUp), + MediaKeyCode::MuteVolume => Some(BKey::AudioVolumeMute), + }, + CKey::Modifier(modifier) => match modifier { + ModifierKeyCode::LeftShift => Some(BKey::ShiftLeft), + ModifierKeyCode::LeftControl => Some(BKey::ControlLeft), + ModifierKeyCode::LeftAlt => Some(BKey::AltLeft), + ModifierKeyCode::LeftSuper => Some(BKey::SuperLeft), + ModifierKeyCode::LeftHyper => Some(BKey::Hyper), + ModifierKeyCode::LeftMeta => Some(BKey::Meta), + ModifierKeyCode::RightShift => Some(BKey::ShiftRight), + ModifierKeyCode::RightControl => Some(BKey::ControlRight), + ModifierKeyCode::RightAlt => Some(BKey::AltLeft), + ModifierKeyCode::RightSuper => Some(BKey::SuperRight), + ModifierKeyCode::RightHyper => Some(BKey::Hyper), + ModifierKeyCode::RightMeta => Some(BKey::Meta), + ModifierKeyCode::IsoLevel3Shift => None, + ModifierKeyCode::IsoLevel5Shift => None, + }, + } +} + +fn crossterm_keycode_to_bevy_key( + crossterm_keycode: crossterm::event::KeyCode, +) -> Option { + use bevy::input::keyboard::Key as BKey; + use crossterm::event::KeyCode as CKey; + match crossterm_keycode { + CKey::Backspace => Some(BKey::Backspace), + CKey::Enter => Some(BKey::Enter), + CKey::Left => Some(BKey::ArrowLeft), + CKey::Right => Some(BKey::ArrowRight), + CKey::Up => Some(BKey::ArrowUp), + CKey::Down => Some(BKey::ArrowDown), + CKey::Home => Some(BKey::Home), + CKey::End => Some(BKey::End), + CKey::PageUp => Some(BKey::PageUp), + CKey::PageDown => Some(BKey::PageDown), + CKey::Tab | CKey::BackTab => Some(BKey::Tab), + CKey::Delete => Some(BKey::Delete), + CKey::Insert => Some(BKey::Insert), + CKey::F(num) => match num { + 1 => Some(BKey::F1), + 2 => Some(BKey::F2), + 3 => Some(BKey::F3), + 4 => Some(BKey::F4), + 5 => Some(BKey::F5), + 6 => Some(BKey::F6), + 7 => Some(BKey::F7), + 8 => Some(BKey::F8), + 9 => Some(BKey::F9), + 10 => Some(BKey::F10), + 11 => Some(BKey::F11), + 12 => Some(BKey::F12), + 13 => Some(BKey::F13), + 14 => Some(BKey::F14), + 15 => Some(BKey::F15), + 16 => Some(BKey::F16), + 17 => Some(BKey::F17), + 18 => Some(BKey::F18), + 19 => Some(BKey::F19), + 20 => Some(BKey::F20), + 21 => Some(BKey::F21), + 22 => Some(BKey::F22), + 23 => Some(BKey::F23), + 24 => Some(BKey::F24), + 25 => Some(BKey::F25), + 26 => Some(BKey::F26), + 27 => Some(BKey::F27), + 28 => Some(BKey::F28), + 29 => Some(BKey::F29), + 30 => Some(BKey::F30), + 31 => Some(BKey::F31), + 32 => Some(BKey::F32), + 33 => Some(BKey::F33), + 34 => Some(BKey::F34), + 35 => Some(BKey::F35), + _ => None, + }, + CKey::Char(c) => Some(BKey::Character(SmolStr::from(c.encode_utf8(&mut [0;4])))), + CKey::Null => None, + CKey::Esc => Some(BKey::Escape), + CKey::CapsLock => Some(BKey::CapsLock), + CKey::ScrollLock => Some(BKey::ScrollLock), + CKey::NumLock => Some(BKey::NumLock), + CKey::PrintScreen => Some(BKey::PrintScreen), + CKey::Pause => Some(BKey::Pause), + CKey::Menu => Some(BKey::ContextMenu), + CKey::KeypadBegin => None, + CKey::Media(media) => match media { + MediaKeyCode::Play => Some(BKey::MediaPlayPause), + MediaKeyCode::Pause => Some(BKey::Pause), + MediaKeyCode::PlayPause => Some(BKey::MediaPlayPause), + MediaKeyCode::Reverse => None, + MediaKeyCode::Stop => Some(BKey::MediaStop), + MediaKeyCode::FastForward => Some(BKey::MediaTrackNext), + MediaKeyCode::Rewind => Some(BKey::MediaTrackPrevious), + MediaKeyCode::TrackNext => Some(BKey::MediaTrackNext), + MediaKeyCode::TrackPrevious => Some(BKey::MediaTrackPrevious), + MediaKeyCode::Record => None, + MediaKeyCode::LowerVolume => Some(BKey::AudioVolumeDown), + MediaKeyCode::RaiseVolume => Some(BKey::AudioVolumeUp), + MediaKeyCode::MuteVolume => Some(BKey::AudioVolumeMute), + }, + CKey::Modifier(modifier) => match modifier { + ModifierKeyCode::LeftShift => Some(BKey::Shift), + ModifierKeyCode::LeftControl => Some(BKey::Control), + ModifierKeyCode::LeftAlt => Some(BKey::Alt), + ModifierKeyCode::LeftSuper => Some(BKey::Super), + ModifierKeyCode::LeftHyper => Some(BKey::Hyper), + ModifierKeyCode::LeftMeta => Some(BKey::Meta), + ModifierKeyCode::RightShift => Some(BKey::Shift), + ModifierKeyCode::RightControl => Some(BKey::Control), + ModifierKeyCode::RightAlt => Some(BKey::Alt), + ModifierKeyCode::RightSuper => Some(BKey::Super), + ModifierKeyCode::RightHyper => Some(BKey::Hyper), + ModifierKeyCode::RightMeta => Some(BKey::Meta), + ModifierKeyCode::IsoLevel3Shift => Some(BKey::AltGraph), + ModifierKeyCode::IsoLevel5Shift => None, + }, + } +} diff --git a/src/lib.rs b/src/lib.rs index 09134f9..e17f271 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! Bevy plugin which allows a camera to render to a terminal window. use std::{ - fs::OpenOptions, io::stdout, path::PathBuf, sync::{Arc, Mutex} + fs::OpenOptions, io::{stdout, Write}, path::PathBuf, sync::{Arc, Mutex} }; use bevy::{ @@ -79,6 +79,7 @@ impl Plugin for TerminalDisplayPlugin { std::panic::set_hook(Box::new(move |info| { let _ = restore_terminal(); + error!("{info}"); panic(info); })); @@ -94,19 +95,21 @@ impl Plugin for TerminalDisplayPlugin { display::systems::resize_handling, display::systems::print_to_terminal, widgets::systems::widget_input_handling, + widgets::systems::update_widgets, ), ) .insert_resource(display::resources::Terminal::default()) .insert_resource(input::resources::EventQueue::default()) - .insert_resource(input::resources::TerminalInput::default()) .add_event::(); } } -fn restore_terminal() { +fn restore_terminal() -> Result<(), Box>{ + disable_raw_mode()?; let mut stdout = stdout(); - let _ = stdout.execute(PopKeyboardEnhancementFlags); - let _ = stdout.execute(DisableMouseCapture); - let _ = stdout.execute(LeaveAlternateScreen); - let _ = disable_raw_mode(); + stdout.execute(PopKeyboardEnhancementFlags)? + .execute(DisableMouseCapture)? + .execute(LeaveAlternateScreen)? + .flush()?; + Ok(()) } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 46bfa20..7751b2d 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -17,5 +17,8 @@ pub trait TerminalWidget: DowncastSync { /// Called when a terminal input event is invoked to update any state accordingly fn handle_events(&mut self, _event: &TerminalInputEvent, _commands: &mut Commands) {} + + /// Called every frame during the Update schedule + fn update(&mut self, _time: &Time, _commands: &mut Commands) {} } impl_downcast!(sync TerminalWidget); diff --git a/src/widgets/systems.rs b/src/widgets/systems.rs index 2626ec6..0638b01 100644 --- a/src/widgets/systems.rs +++ b/src/widgets/systems.rs @@ -16,3 +16,9 @@ pub fn widget_input_handling( } } } + +pub fn update_widgets(mut widgets: Query<&mut Widget>, time: Res