aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml7
-rw-r--r--README.md10
-rw-r--r--doc/screenshot.pngbin20289 -> 48326 bytes
-rw-r--r--src/components.rs2
-rw-r--r--src/lib.rs68
-rw-r--r--src/resources.rs58
-rw-r--r--src/systems.rs63
7 files changed, 155 insertions, 53 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a271dde..81d1710 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"]
+
diff --git a/README.md b/README.md
index a8e1687..64ce199 100644
--- a/README.md
+++ b/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
![](./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
index d5c9de3..dd91418 100644
--- a/doc/screenshot.png
+++ b/doc/screenshot.png
Binary files differ
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()
}
}
+
diff --git a/src/lib.rs b/src/lib.rs
index 3ad98f4..ac093de 100644
--- a/src/lib.rs
+++ b/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,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>,