diff options
author | Silas Bartha <[email protected]> | 2024-05-02 02:53:51 -0400 |
---|---|---|
committer | Silas Bartha <[email protected]> | 2024-05-02 02:53:51 -0400 |
commit | 6834194b9e16a7b5ee68b539ceb1a99b8f1ff90b (patch) | |
tree | bac5d0ff699f1c3c4f58d47c2f127f5d3432a528 | |
parent | 646db8328611f21a5850cc9834b6c72bfdf0c829 (diff) |
Added ratatui integration + logger redirectv0.1.1
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | doc/screenshot.png | bin | 20289 -> 48326 bytes | |||
-rw-r--r-- | src/components.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 68 | ||||
-rw-r--r-- | src/resources.rs | 58 | ||||
-rw-r--r-- | src/systems.rs | 63 |
7 files changed, 155 insertions, 53 deletions
@@ -1,6 +1,6 @@ [package] name = "grex_terminal_display" -version = "0.1.0" +version = "0.1.1" edition = "2021" [dependencies] @@ -17,3 +17,8 @@ tag = "v0.1.0" [dependencies.grex_dither_post_process] git = "https://github.com/exvacuum/grex_dither_post_process" tag = "v0.1.2" + +[dependencies.ratatui] +version = "0.26.2" +features = ["unstable-widget-ref"] + @@ -10,11 +10,9 @@ Features Include: - Post-process dithers colors to pure black and white, which are then printed as braille characters to the terminal - Responsiveness to terminal window resizing - `TerminalInput` resource which keeps track of pressed & released keys -- Keyboard input enhancements using kitty protocol - -Future Goals: -- Find a way to integrate into a TUI library like ratatui for more interaction options. -- Move kitty enhancements to a feature maybe +- `TerminalUI` resource for rendering ratatui TUI widgets +- `TerminalWidget` trait for creating custom TUI widget components +- Logging redirected to `output.log` ## Screenshots ![](./doc/screenshot.png) @@ -42,7 +40,7 @@ use grex_terminal_display; fn main() { App::new() .add_plugins(( - DefaultPlugins.build().disable::<WinitPlugin>(), + DefaultPlugins.build().disable::<WinitPlugin>().disable::<LogPlugin>, ScheduleRunnerPlugin::run_loop(Duration::from_secs_f32(1.0 / 60.0)), grex_terminal_display::TerminalDisplayPlugin, )) diff --git a/doc/screenshot.png b/doc/screenshot.png Binary files differindex d5c9de3..dd91418 100644 --- a/doc/screenshot.png +++ b/doc/screenshot.png diff --git a/src/components.rs b/src/components.rs index 560ad89..3ebf507 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,3 +1,4 @@ + use bevy::{ prelude::*, render::render_resource::{ @@ -70,3 +71,4 @@ impl TerminalDisplayBundle { self.image_handle.clone() } } + @@ -1,13 +1,22 @@ -use std::io::stdout; +use std::{io::stdout, fs::OpenOptions}; -use bevy::prelude::*; +use bevy::{ + log::{ + tracing_subscriber::{self, Registry, prelude::*}, + LogPlugin, Level, + }, + prelude::*, utils::tracing::level_filters::LevelFilter, +}; use crossterm::{ - event::PopKeyboardEnhancementFlags, terminal::disable_raw_mode, ExecutableCommand, + event::DisableMouseCapture, + terminal::{disable_raw_mode, LeaveAlternateScreen}, + ExecutableCommand, }; use grex_dither_post_process::DitherPostProcessPlugin; use grex_framebuffer_extract::FramebufferExtractPlugin; -pub use crossterm::event::KeyCode; +pub use crossterm; +pub use ratatui; pub mod components; pub mod events; @@ -18,26 +27,47 @@ pub struct TerminalDisplayPlugin; impl Plugin for TerminalDisplayPlugin { fn build(&self, app: &mut App) { - app.add_plugins((DitherPostProcessPlugin, FramebufferExtractPlugin)) - .add_systems(Startup, systems::setup) - .add_systems( - Update, - ( - systems::input_handling, - systems::resize_handling, - systems::print_to_terminal, - ), - ) - .insert_resource(resources::EventQueue::default()) - .insert_resource(resources::TerminalInput::default()) - .add_event::<events::TerminalInputEvent>(); + app.add_plugins(( + DitherPostProcessPlugin, + FramebufferExtractPlugin, + LogPlugin { + update_subscriber: Some(|_| { + let log_file = OpenOptions::new() + .write(true) + .create(true) + .open("debug.log") + .unwrap(); + let file_layer = tracing_subscriber::fmt::Layer::new() + .with_writer(log_file) + .with_filter(LevelFilter::from_level(Level::INFO)); + Box::new(Registry::default().with(file_layer)) + }), + ..Default::default() + }, + )) + .add_systems(Startup, systems::setup) + .add_systems( + Update, + ( + systems::input_handling, + systems::resize_handling, + systems::print_to_terminal, + systems::widget_input_handling, + ), + ) + .insert_resource(resources::Terminal::default()) + .insert_resource(resources::EventQueue::default()) + .insert_resource(resources::TerminalInput::default()) + .insert_resource(resources::TerminalUI::default()) + .add_event::<events::TerminalInputEvent>(); } } impl Drop for TerminalDisplayPlugin { fn drop(&mut self) { let mut stdout = stdout(); - stdout.execute(PopKeyboardEnhancementFlags).unwrap(); - disable_raw_mode().unwrap(); + let _ = stdout.execute(DisableMouseCapture); + let _ = stdout.execute(LeaveAlternateScreen); + let _ = disable_raw_mode(); } } diff --git a/src/resources.rs b/src/resources.rs index 343967d..e8fb946 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -1,7 +1,10 @@ -use std::sync::{Arc, Mutex}; +use std::{sync::{Arc, Mutex}, io::{stdout, Stdout}, fs::{File, OpenOptions}}; -use bevy::{prelude::*, utils::HashSet}; -use crossterm::event::{Event, KeyCode}; +use bevy::{prelude::*, utils::{HashSet, Uuid, HashMap, tracing::{subscriber, level_filters::LevelFilter}}, log::tracing_subscriber}; +use crossterm::{event::{Event, KeyCode, EnableMouseCapture}, terminal::{EnterAlternateScreen, enable_raw_mode}, ExecutableCommand}; +use ratatui::{backend::CrosstermBackend, Frame, layout::Rect}; + +use crate::events::TerminalInputEvent; #[derive(Resource, Default)] pub struct TerminalInput { @@ -36,3 +39,52 @@ impl TerminalInput { #[derive(Resource, Default)] pub(super) struct EventQueue(pub(super) Arc<Mutex<Vec<Event>>>); + +#[derive(Resource)] +pub struct Terminal(pub ratatui::Terminal<CrosstermBackend<Stdout>>); + +impl Default for Terminal { + fn default() -> Self { + stdout().execute(EnterAlternateScreen).unwrap(); + stdout().execute(EnableMouseCapture).unwrap(); + enable_raw_mode().unwrap(); + let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout())).expect("Failed to create terminal"); + terminal.clear().expect("Failed to clear terminal"); + Self(terminal) + } +} + +#[derive(Resource, Default)] +pub struct TerminalUI { + widgets: HashMap<Uuid, Box<dyn TerminalWidget + Sync + Send>> +} + +impl TerminalUI { + pub fn insert_widget(&mut self, widget: Box<dyn TerminalWidget + Sync + Send>) -> Uuid { + let id = Uuid::new_v4(); + self.widgets.insert(id, widget); + id + } + + pub fn get_widget(&mut self, id: Uuid) -> Option<&mut Box<dyn TerminalWidget + Sync + Send>> { + self.widgets.get_mut(&id) + } + + pub fn destroy_widget(&mut self, id: Uuid) { + self.widgets.remove(&id); + } + + pub fn widgets(&mut self) -> Vec<&mut Box<dyn TerminalWidget + Sync + Send>> { + let mut vec = self.widgets.values_mut().collect::<Vec<_>>(); + vec.sort_by(|a, b| { a.depth().cmp(&b.depth()).reverse() }); + vec + } +} + +pub trait TerminalWidget { + fn init(&mut self) {} + fn update(&mut self) {} + fn render(&mut self, frame: &mut Frame, rect: Rect); + fn handle_events(&mut self, _event: &TerminalInputEvent) {} + fn depth(&self) -> u32 { 0 } +} diff --git a/src/systems.rs b/src/systems.rs index 77b8d11..e1a9bef 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,25 +1,20 @@ -use std::{ - io::{stdout, Write}, - usize, -}; - use bevy::{ prelude::*, render::render_resource::{Extent3d, TextureFormat}, }; -use crossterm::{ - cursor::{self, MoveTo}, - event::{read, Event, KeyEventKind, KeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, - terminal::enable_raw_mode, - ExecutableCommand, QueueableCommand, -}; +use crossterm::event::{read, Event, KeyEventKind}; use grex_framebuffer_extract::{ components::FramebufferExtractDestination, render_assets::FramebufferExtractSource, }; use crate::{ events::TerminalInputEvent, - resources::{EventQueue, TerminalInput}, + resources::{EventQueue, Terminal, TerminalInput, TerminalUI}, +}; + +use ratatui::{ + prelude::*, + widgets::{Paragraph, Wrap}, }; const BRAILLE_CODE_MIN: u16 = 0x2800; @@ -41,16 +36,13 @@ pub fn setup(event_queue: Res<EventQueue>) { } } }); - - let mut stdout = stdout(); - enable_raw_mode().expect("Failed to put terminal into raw mode"); - let _ = stdout.execute(PushKeyboardEnhancementFlags( - KeyboardEnhancementFlags::REPORT_EVENT_TYPES, - )); - let _ = stdout.execute(cursor::Hide); } -pub fn print_to_terminal(image_exports: Query<&FramebufferExtractDestination>) { +pub fn print_to_terminal( + mut terminal: ResMut<Terminal>, + mut terminal_ui: ResMut<TerminalUI>, + image_exports: Query<&FramebufferExtractDestination>, +) { for image_export in image_exports.iter() { let mut image = image_export .0 @@ -93,10 +85,22 @@ pub fn print_to_terminal(image_exports: Query<&FramebufferExtractDestination>) { } let string = output_buffer.into_iter().collect::<String>(); - let mut stdout = stdout(); - stdout.queue(MoveTo(0, 0)).unwrap(); - stdout.write_all(string.as_bytes()).unwrap(); - stdout.flush().unwrap(); + terminal + .0 + .draw(|frame| { + let area = frame.size(); + frame.render_widget( + Paragraph::new(string) + .white() + .bold() + .wrap(Wrap { trim: true }), + area, + ); + for widget in terminal_ui.widgets().iter_mut() { + widget.render(frame, area); + } + }) + .expect("Failed to draw terminal frame"); } } @@ -112,6 +116,17 @@ fn braille_char(mask: u8) -> char { } } +pub fn widget_input_handling( + mut terminal_ui: ResMut<TerminalUI>, + mut event_reader: EventReader<TerminalInputEvent>, +) { + for event in event_reader.read() { + for widget in terminal_ui.widgets().iter_mut() { + widget.handle_events(event); + } + } +} + pub fn input_handling( event_queue: Res<EventQueue>, mut input: ResMut<TerminalInput>, |