aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLibravatar Silas Bartha <[email protected]>2024-06-04 15:00:16 -0400
committerLibravatar Silas Bartha <[email protected]>2024-06-04 15:00:16 -0400
commita002e4d738535e6ca779c71231f7b84864b9a8d0 (patch)
tree8ec521b535f3820328f9ea6f2511ca29c630b82f /src
parent56aafda8495243fa939bdce01f36d4adbf4ec556 (diff)
Refactored + Renamed + Added Docs
Diffstat (limited to 'src')
-rw-r--r--src/display/components.rs (renamed from src/components.rs)35
-rw-r--r--src/display/mod.rs8
-rw-r--r--src/display/resources.rs43
-rw-r--r--src/display/systems.rs (renamed from src/systems.rs)87
-rw-r--r--src/input/events.rs (renamed from src/events.rs)1
-rw-r--r--src/input/mod.rs8
-rw-r--r--src/input/resources.rs48
-rw-r--r--src/input/systems.rs46
-rw-r--r--src/lib.rs85
-rw-r--r--src/resources.rs109
-rw-r--r--src/widgets/components.rs14
-rw-r--r--src/widgets/mod.rs21
-rw-r--r--src/widgets/systems.rs35
13 files changed, 299 insertions, 241 deletions
diff --git a/src/components.rs b/src/display/components.rs
index 3814cc7..4459325 100644
--- a/src/components.rs
+++ b/src/display/components.rs
@@ -1,21 +1,13 @@
+use bevy::{prelude::*, render::render_resource::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages}};
+use bevy_dither_post_process::components::DitherPostProcessSettings;
+use bevy_framebuffer_extract::{components::{ExtractFramebufferBundle, FramebufferExtractDestination}, render_assets::FramebufferExtractSource};
-use bevy::{
- prelude::*,
- render::render_resource::{
- Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
- },
-};
-use grex_dither_post_process::components::DitherPostProcessSettings;
-use grex_framebuffer_extract::{
- components::{ExtractFramebufferBundle, FramebufferExtractDestination},
- render_assets::FramebufferExtractSource,
-};
-
-use crate::resources::TerminalWidget;
-
+/// Marker component for terminal display
#[derive(Component)]
pub struct TerminalDisplay;
+/// Bundle for terminal display, contains a handle to an image to be used as a render target to
+/// render to the terminal
#[derive(Bundle)]
pub struct TerminalDisplayBundle {
_terminal_display: TerminalDisplay,
@@ -25,6 +17,9 @@ pub struct TerminalDisplayBundle {
}
impl TerminalDisplayBundle {
+ /// Create a new terminal display with the given dither level. A higher level exponentially
+ /// increases the size of the bayer matrix used in the ordered dithering calculations. If in
+ /// doubt, 3 is a good starting value to test with.
pub fn new(dither_level: u32, asset_server: &AssetServer) -> Self {
let terminal_size = crossterm::terminal::size().unwrap();
let size = Extent3d {
@@ -69,17 +64,9 @@ impl TerminalDisplayBundle {
}
}
+ /// Retrieves the handle to this display's target image. Anything written here will be
+ /// displayed.
pub fn image_handle(&self) -> Handle<Image> {
self.image_handle.clone()
}
}
-
-#[derive(Component)]
-pub struct Widget {
- pub widget: Box<dyn TerminalWidget + Send + Sync>,
- pub depth: u32,
- pub enabled: bool,
-}
-
-#[derive(Component)]
-pub struct Tooltip(pub String);
diff --git a/src/display/mod.rs b/src/display/mod.rs
new file mode 100644
index 0000000..ae45445
--- /dev/null
+++ b/src/display/mod.rs
@@ -0,0 +1,8 @@
+/// Components for this module
+pub mod components;
+
+/// Resources for this module
+pub mod resources;
+
+/// Systems for this module
+pub(crate) mod systems;
diff --git a/src/display/resources.rs b/src/display/resources.rs
new file mode 100644
index 0000000..7321aea
--- /dev/null
+++ b/src/display/resources.rs
@@ -0,0 +1,43 @@
+use std::io::{stdout, Stdout};
+
+use bevy::prelude::*;
+use crossterm::{
+ event::{
+ DisableMouseCapture, EnableMouseCapture, KeyboardEnhancementFlags,
+ PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
+ },
+ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
+ ExecutableCommand,
+};
+use ratatui::backend::CrosstermBackend;
+
+/// Ratatui terminal instance. Enters alternate screen when constructed, and exits once dropped.
+#[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();
+ stdout()
+ .execute(PushKeyboardEnhancementFlags(
+ KeyboardEnhancementFlags::REPORT_EVENT_TYPES,
+ ))
+ .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)
+ }
+}
+
+impl Drop for Terminal {
+ fn drop(&mut self) {
+ let mut stdout = stdout();
+ let _ = stdout.execute(PopKeyboardEnhancementFlags);
+ let _ = stdout.execute(DisableMouseCapture);
+ let _ = stdout.execute(LeaveAlternateScreen);
+ let _ = disable_raw_mode();
+ }
+}
diff --git a/src/systems.rs b/src/display/systems.rs
index dedef65..a768af5 100644
--- a/src/systems.rs
+++ b/src/display/systems.rs
@@ -2,44 +2,31 @@ use bevy::{
prelude::*,
render::render_resource::{Extent3d, TextureFormat},
};
-use crossterm::event::{read, Event, KeyEventKind};
-use grex_framebuffer_extract::{
+use crossterm::event::Event;
+use bevy_framebuffer_extract::{
components::FramebufferExtractDestination, render_assets::FramebufferExtractSource,
};
-
-use crate::{
- components::Widget, events::TerminalInputEvent, resources::{EventQueue, Terminal, TerminalInput}
-};
-
use ratatui::{
- prelude::*,
+ style::Stylize,
widgets::{Paragraph, Wrap},
};
+use crate::input::events::TerminalInputEvent;
+
+use super::resources::Terminal;
+
const BRAILLE_CODE_MIN: u16 = 0x2800;
const BRAILLE_CODE_MAX: u16 = 0x28FF;
-const BRAILLE_DOT_BIT_POSITIONS: [u8; 8] = [0, 1, 2, 6, 3, 4, 5, 7];
-pub fn setup(event_queue: Res<EventQueue>) {
- let event_queue = event_queue.0.clone();
- std::thread::spawn(move || {
- loop {
- // `read()` blocks until an `Event` is available
- match read() {
- Ok(event) => {
- event_queue.lock().unwrap().push(event);
- }
- Err(err) => {
- panic!("Error reading input events: {:?}", err);
- }
- }
- }
- });
-}
+/// 0 3
+/// 1 4
+/// 2 5
+/// 6 7
+const BRAILLE_DOT_BIT_POSITIONS: [u8; 8] = [0, 1, 2, 6, 3, 4, 5, 7];
+/// Prints out the contents of a render image to the terminal as braille characters
pub fn print_to_terminal(
mut terminal: ResMut<Terminal>,
- mut widgets: Query<&mut Widget>,
image_exports: Query<&FramebufferExtractDestination>,
) {
for image_export in image_exports.iter() {
@@ -47,10 +34,6 @@ pub fn print_to_terminal(
.0
.lock()
.expect("Failed to get lock on output texture");
- //TODO: Find a better way of preventing first frame
- if image.size() == UVec2::ONE {
- continue;
- }
if image.texture_descriptor.format != TextureFormat::R8Unorm {
warn_once!("Extracted framebuffer texture is not R8Unorm format. Will attempt conversion, but consider changing your render texture's format.");
info_once!("{:?}", image);
@@ -87,24 +70,19 @@ pub fn print_to_terminal(
terminal
.0
.draw(|frame| {
- let area = frame.size();
frame.render_widget(
Paragraph::new(string)
.white()
.bold()
.wrap(Wrap { trim: true }),
- area,
+ frame.size(),
);
- let mut active_widgets = widgets.iter_mut().filter(|widget| widget.enabled).collect::<Vec<_>>();
- active_widgets.sort_by(|a, b| a.depth.cmp(&b.depth));
- for mut widget in active_widgets {
- widget.widget.render(frame, area);
- }
})
.expect("Failed to draw terminal frame");
}
}
+/// Utility function to convert a u8 into the corresponding braille character
fn braille_char(mask: u8) -> char {
match char::from_u32((BRAILLE_CODE_MIN + mask as u16) as u32) {
Some(character) => {
@@ -117,40 +95,7 @@ fn braille_char(mask: u8) -> char {
}
}
-pub fn widget_input_handling(
- mut widgets: Query<&mut Widget>,
- mut event_reader: EventReader<TerminalInputEvent>,
- mut commands: Commands,
-) {
- for event in event_reader.read() {
- for mut widget in widgets.iter_mut().filter(|widget| widget.enabled) {
- widget.widget.handle_events(event, &mut commands);
- }
- }
-}
-
-pub fn input_handling(
- event_queue: Res<EventQueue>,
- mut input: ResMut<TerminalInput>,
- mut event_writer: EventWriter<TerminalInputEvent>,
-) {
- let mut event_queue = event_queue.0.lock().unwrap();
- while let Some(event) = event_queue.pop() {
- if let Event::Key(event) = event {
- match event.kind {
- KeyEventKind::Press => {
- input.press(event.code);
- }
- KeyEventKind::Release => {
- input.release(event.code);
- }
- _ => (),
- }
- }
- event_writer.send(TerminalInputEvent(event));
- }
-}
-
+/// Watches for terminal resize events and resizes the render image accordingly
pub fn resize_handling(
mut images: ResMut<Assets<Image>>,
mut sources: ResMut<Assets<FramebufferExtractSource>>,
diff --git a/src/events.rs b/src/input/events.rs
index 3f7057b..cf46445 100644
--- a/src/events.rs
+++ b/src/input/events.rs
@@ -1,5 +1,6 @@
use bevy::prelude::*;
use crossterm::event::Event;
+/// An event triggered when a crossterm input event is received
#[derive(Event)]
pub struct TerminalInputEvent(pub Event);
diff --git a/src/input/mod.rs b/src/input/mod.rs
new file mode 100644
index 0000000..80c36bc
--- /dev/null
+++ b/src/input/mod.rs
@@ -0,0 +1,8 @@
+/// Events for this module
+pub mod events;
+
+/// Resources for this module
+pub mod resources;
+
+/// Systems for this module
+pub(crate) mod systems;
diff --git a/src/input/resources.rs b/src/input/resources.rs
new file mode 100644
index 0000000..afe6353
--- /dev/null
+++ b/src/input/resources.rs
@@ -0,0 +1,48 @@
+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<KeyCode>,
+ released_keys: HashSet<KeyCode>,
+}
+
+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 is released
+ pub fn is_released(&self, code: KeyCode) -> bool {
+ self.released_keys.contains(&code)
+ }
+
+ /// Sets given key to pressed
+ pub(super) fn press(&mut self, code: KeyCode) {
+ if !self.is_pressed(code) {
+ self.pressed_keys.insert(code);
+ }
+ }
+
+ /// Sets given key to released and removes pressed state
+ pub(super) fn release(&mut self, code: KeyCode) {
+ if self.is_pressed(code) {
+ self.pressed_keys.remove(&code);
+ }
+ if !self.is_released(code) {
+ self.released_keys.insert(code);
+ }
+ }
+
+ /// Clears all released keys
+ pub(super) fn clear_released(&mut self) {
+ self.released_keys.clear();
+ }
+}
+
+/// Event queue for crossterm input event thread
+#[derive(Resource, Default)]
+pub(crate) struct EventQueue(pub(super) Arc<Mutex<Vec<Event>>>);
diff --git a/src/input/systems.rs b/src/input/systems.rs
new file mode 100644
index 0000000..3b7f11d
--- /dev/null
+++ b/src/input/systems.rs
@@ -0,0 +1,46 @@
+use bevy::prelude::*;
+use crossterm::event::{read, Event, KeyEventKind};
+
+use super::{events::TerminalInputEvent, resources::{EventQueue, TerminalInput}};
+
+/// Initializes event queue and thread
+pub fn setup_input(event_queue: Res<EventQueue>) {
+ let event_queue = event_queue.0.clone();
+ std::thread::spawn(move || {
+ loop {
+ // `read()` blocks until an `Event` is available
+ match read() {
+ Ok(event) => {
+ event_queue.lock().unwrap().push(event);
+ }
+ Err(err) => {
+ panic!("Error reading input events: {:?}", err);
+ }
+ }
+ }
+ });
+}
+
+/// Reads events from queue and broadcasts corresponding `TerminalInputEvent`s
+pub fn input_handling(
+ event_queue: Res<EventQueue>,
+ mut input: ResMut<TerminalInput>,
+ mut event_writer: EventWriter<TerminalInputEvent>,
+) {
+ input.clear_released();
+ let mut event_queue = event_queue.0.lock().unwrap();
+ while let Some(event) = event_queue.pop() {
+ if let Event::Key(event) = event {
+ match event.kind {
+ KeyEventKind::Press => {
+ input.press(event.code);
+ }
+ KeyEventKind::Release => {
+ input.release(event.code);
+ }
+ _ => (),
+ }
+ }
+ event_writer.send(TerminalInputEvent(event));
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 0a1a925..280e413 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,46 +1,58 @@
-use std::{fs::OpenOptions, io::stdout, path::PathBuf, sync::{Arc, Mutex}};
+#![warn(missing_docs)]
+
+//! Bevy plugin which allows a camera to render to a terminal window.
+
+use std::{
+ fs::OpenOptions,
+ path::PathBuf,
+ sync::{Arc, Mutex},
+};
use bevy::{
log::{
- tracing_subscriber::{self, Registry, prelude::*},
- LogPlugin, Level,
+ tracing_subscriber::{self, prelude::*, Registry},
+ Level, LogPlugin,
},
- prelude::*, utils::tracing::level_filters::LevelFilter,
+ prelude::*,
+ utils::tracing::level_filters::LevelFilter,
};
-use crossterm::{
- event::{DisableMouseCapture, PopKeyboardEnhancementFlags},
- terminal::{disable_raw_mode, LeaveAlternateScreen},
- ExecutableCommand,
-};
-use grex_dither_post_process::DitherPostProcessPlugin;
-use grex_framebuffer_extract::FramebufferExtractPlugin;
+use bevy_dither_post_process::DitherPostProcessPlugin;
+use bevy_framebuffer_extract::FramebufferExtractPlugin;
pub use crossterm;
use once_cell::sync::Lazy;
pub use ratatui;
-pub mod components;
-pub mod events;
-pub mod resources;
-mod systems;
+/// Functions and types related to capture and display of world to terminal
+pub mod display;
+
+/// Functions and types related to capturing and processing user keyboard input
+pub mod input;
+
+/// Functions and types related to constructing and rendering TUI widgets
+pub mod widgets;
static LOG_PATH: Lazy<Arc<Mutex<PathBuf>>> = Lazy::new(|| Arc::new(Mutex::new(PathBuf::default())));
+/// Plugin providing terminal display functionality
pub struct TerminalDisplayPlugin {
+ /// Path to redirect tracing logs to. Defaults to "debug.log"
pub log_path: PathBuf,
}
impl Default for TerminalDisplayPlugin {
fn default() -> Self {
Self {
- log_path: "debug.log".into()
+ log_path: "debug.log".into(),
}
}
}
impl Plugin for TerminalDisplayPlugin {
fn build(&self, app: &mut App) {
- *LOG_PATH.lock().expect("Failed to get lock on log path mutex") = self.log_path.clone();
+ *LOG_PATH
+ .lock()
+ .expect("Failed to get lock on log path mutex") = self.log_path.clone();
app.add_plugins((
DitherPostProcessPlugin,
FramebufferExtractPlugin,
@@ -50,7 +62,12 @@ impl Plugin for TerminalDisplayPlugin {
.write(true)
.create(true)
.truncate(true)
- .open(LOG_PATH.lock().expect("Failed to get lock on log path mutex").clone())
+ .open(
+ LOG_PATH
+ .lock()
+ .expect("Failed to get lock on log path mutex")
+ .clone(),
+ )
.unwrap();
let file_layer = tracing_subscriber::fmt::Layer::new()
.with_writer(log_file)
@@ -60,29 +77,23 @@ impl Plugin for TerminalDisplayPlugin {
..Default::default()
},
))
- .add_systems(Startup, systems::setup)
+ .add_systems(Startup, input::systems::setup_input)
.add_systems(
Update,
(
- systems::input_handling,
- systems::resize_handling,
- systems::print_to_terminal,
- systems::widget_input_handling,
+ input::systems::input_handling,
+ display::systems::resize_handling,
+ (
+ display::systems::print_to_terminal,
+ widgets::systems::draw_widgets,
+ )
+ .chain(),
+ widgets::systems::widget_input_handling,
),
)
- .insert_resource(resources::Terminal::default())
- .insert_resource(resources::EventQueue::default())
- .insert_resource(resources::TerminalInput::default())
- .add_event::<events::TerminalInputEvent>();
- }
-}
-
-impl Drop for TerminalDisplayPlugin {
- fn drop(&mut self) {
- let mut stdout = stdout();
- let _ = stdout.execute(PopKeyboardEnhancementFlags);
- let _ = stdout.execute(DisableMouseCapture);
- let _ = stdout.execute(LeaveAlternateScreen);
- let _ = disable_raw_mode();
+ .insert_resource(display::resources::Terminal::default())
+ .insert_resource(input::resources::EventQueue::default())
+ .insert_resource(input::resources::TerminalInput::default())
+ .add_event::<input::events::TerminalInputEvent>();
}
}
diff --git a/src/resources.rs b/src/resources.rs
deleted file mode 100644
index a373a80..0000000
--- a/src/resources.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-use std::{
- any::Any, io::{stdout, Stdout}, sync::{Arc, Mutex}
-};
-
-use bevy::{
- prelude::*,
- utils::HashSet,
-};
-use crossterm::{
- event::{
- EnableMouseCapture, Event, KeyCode, KeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
- },
- terminal::{enable_raw_mode, EnterAlternateScreen},
- ExecutableCommand,
-};
-use ratatui::{backend::CrosstermBackend, layout::Rect, Frame};
-
-use crate::events::TerminalInputEvent;
-
-#[derive(Resource, Default)]
-pub struct TerminalInput {
- pressed_keys: HashSet<KeyCode>,
- released_keys: HashSet<KeyCode>,
-}
-
-impl TerminalInput {
- pub fn is_pressed(&self, code: KeyCode) -> bool {
- self.pressed_keys.contains(&code)
- }
-
- pub fn is_released(&self, code: KeyCode) -> bool {
- self.released_keys.contains(&code)
- }
-
- pub(super) fn press(&mut self, code: KeyCode) {
- if !self.is_pressed(code) {
- self.pressed_keys.insert(code);
- }
- }
-
- pub(super) fn release(&mut self, code: KeyCode) {
- if self.is_pressed(code) {
- self.pressed_keys.remove(&code);
- }
- if !self.is_released(code) {
- self.released_keys.insert(code);
- }
- }
-}
-
-#[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();
- stdout()
- .execute(PushKeyboardEnhancementFlags(
- KeyboardEnhancementFlags::REPORT_EVENT_TYPES,
- ))
- .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: Any {
- fn init(&mut self) {}
- fn update(&mut self) {}
- fn render(&mut self, frame: &mut Frame, rect: Rect);
- fn handle_events(&mut self, _event: &TerminalInputEvent, _commands: &mut Commands) {}
- fn depth(&self) -> u32 {
- 0
- }
-}
diff --git a/src/widgets/components.rs b/src/widgets/components.rs
new file mode 100644
index 0000000..b6ff925
--- /dev/null
+++ b/src/widgets/components.rs
@@ -0,0 +1,14 @@
+use bevy::prelude::*;
+
+use super::TerminalWidget;
+
+/// Component representing a terminal widget.
+#[derive(Component)]
+pub struct Widget {
+ /// The widget instance itself, containing rendering and input logic
+ pub widget: Box<dyn TerminalWidget + Send + Sync>,
+ /// Depth to render widget at
+ pub depth: u32,
+ /// Whether this widget is currently enabled or should be hidden
+ pub enabled: bool,
+}
diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs
new file mode 100644
index 0000000..46bfa20
--- /dev/null
+++ b/src/widgets/mod.rs
@@ -0,0 +1,21 @@
+use bevy::prelude::*;
+use downcast_rs::{impl_downcast, DowncastSync};
+use ratatui::{layout::Rect, Frame};
+
+use crate::input::events::TerminalInputEvent;
+
+/// Components for this module
+pub mod components;
+
+/// Systems for this module
+pub(crate) mod systems;
+
+/// Trait which defines an interface for terminal widgets
+pub trait TerminalWidget: DowncastSync {
+ /// Called every frame to render the widget
+ fn render(&mut self, frame: &mut Frame, rect: Rect);
+
+ /// Called when a terminal input event is invoked to update any state accordingly
+ fn handle_events(&mut self, _event: &TerminalInputEvent, _commands: &mut Commands) {}
+}
+impl_downcast!(sync TerminalWidget);
diff --git a/src/widgets/systems.rs b/src/widgets/systems.rs
new file mode 100644
index 0000000..69a84e3
--- /dev/null
+++ b/src/widgets/systems.rs
@@ -0,0 +1,35 @@
+use bevy::prelude::*;
+
+use crate::{display::resources::Terminal, input::events::TerminalInputEvent};
+
+use super::components::Widget;
+
+/// Invokes every enabled widget's `render` method
+pub fn draw_widgets(mut terminal: ResMut<Terminal>, mut widgets: Query<&mut Widget>) {
+ terminal
+ .0
+ .draw(|frame| {
+ let mut active_widgets = widgets
+ .iter_mut()
+ .filter(|widget| widget.enabled)
+ .collect::<Vec<_>>();
+ active_widgets.sort_by(|a, b| a.depth.cmp(&b.depth));
+ for mut widget in active_widgets {
+ widget.widget.render(frame, frame.size());
+ }
+ })
+ .unwrap();
+}
+
+/// Invokes every enabled widget's `handle_events` methods for each incoming input event
+pub fn widget_input_handling(
+ mut widgets: Query<&mut Widget>,
+ mut event_reader: EventReader<TerminalInputEvent>,
+ mut commands: Commands,
+) {
+ for event in event_reader.read() {
+ for mut widget in widgets.iter_mut().filter(|widget| widget.enabled) {
+ widget.widget.handle_events(event, &mut commands);
+ }
+ }
+}