Added ratatui integration + logger redirect
This commit is contained in:
parent
646db83286
commit
6834194b9e
@ -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
README.md
10
README.md
@ -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
|
||||

|
||||
@ -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,
|
||||
))
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 47 KiB |
@ -1,3 +1,4 @@
|
||||
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::render_resource::{
|
||||
@ -70,3 +71,4 @@ impl TerminalDisplayBundle {
|
||||
self.image_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
44
src/lib.rs
44
src/lib.rs
@ -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,7 +27,24 @@ pub struct TerminalDisplayPlugin;
|
||||
|
||||
impl Plugin for TerminalDisplayPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((DitherPostProcessPlugin, FramebufferExtractPlugin))
|
||||
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,
|
||||
@ -26,10 +52,13 @@ impl Plugin for TerminalDisplayPlugin {
|
||||
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>();
|
||||
}
|
||||
}
|
||||
@ -37,7 +66,8 @@ impl Plugin for TerminalDisplayPlugin {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user