Compare commits
No commits in common. "master" and "v0.6.0" have entirely different histories.
@ -1,25 +0,0 @@
|
|||||||
name: Build
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Build:
|
|
||||||
env:
|
|
||||||
RUNNER_TOOL_CACHE: /toolcache
|
|
||||||
container: rust:alpine
|
|
||||||
steps:
|
|
||||||
- name: Install node
|
|
||||||
run: apk add nodejs gcc libc-dev pkgconf libx11-dev alsa-lib-dev eudev-dev tar
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Restore cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/bin/
|
|
||||||
~/.cargo/registry/index/
|
|
||||||
~/.cargo/registry/cache/
|
|
||||||
~/.cargo/git/db/
|
|
||||||
target/
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --release
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1 @@
|
|||||||
/target
|
/target
|
||||||
Cargo.lock
|
|
||||||
.cargo
|
|
||||||
|
4656
Cargo.lock
generated
Normal file
4656
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@ -1,21 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_terminal_display"
|
name = "bevy_terminal_display"
|
||||||
version = "0.7.0"
|
version = "0.6.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "0BSD OR MIT OR Apache-2.0"
|
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."
|
description = "A plugin for the Bevy game engine which enables rendering to a terminal using unicode braille characters."
|
||||||
repository = "https://git.soaos.dev/soaos/bevy_terminal_display"
|
repository = "https://git.exvacuum.dev/bevy_terminal_display"
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
opt-level = 1
|
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
|
||||||
opt-level = 2
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
downcast-rs = "2.0"
|
downcast-rs = "1.2"
|
||||||
once_cell = "1.19"
|
once_cell = "1.19"
|
||||||
|
bevy_headless_render = "0.2"
|
||||||
|
bevy_dither_post_process = "0.3"
|
||||||
ratatui = "0.29"
|
ratatui = "0.29"
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
leafwing-input-manager = "0.16"
|
leafwing-input-manager = "0.16"
|
||||||
@ -30,11 +26,3 @@ features = ["bevy_render"]
|
|||||||
[dependencies.crossterm]
|
[dependencies.crossterm]
|
||||||
version = "0.28"
|
version = "0.28"
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
# SOAOS DEPS
|
|
||||||
|
|
||||||
[dependencies.bevy_dither_post_process]
|
|
||||||
version = "0.3"
|
|
||||||
|
|
||||||
[dependencies.bevy_headless_render]
|
|
||||||
version = "0.2"
|
|
10
README.md
10
README.md
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
[](https://crates.io/crates/bevy_terminal_display)
|
[](https://crates.io/crates/bevy_terminal_display)
|
||||||

|

|
||||||
|

|
||||||
|
[](https://exvacuum.github.io/bevy_terminal_display)
|
||||||
|
|
||||||
A (very experimental) plugin for the [Bevy](https://bevyengine.org) engine which allows for rendering to a terminal window.
|
A (very experimental) plugin for the [Bevy](https://bevyengine.org) engine which allows for rendering to a terminal window.
|
||||||
|
|
||||||
@ -17,12 +19,12 @@ Features Include:
|
|||||||
- Log redirection
|
- Log redirection
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||

|

|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
| Crate Version | Bevy Version |
|
| Crate Version | Bevy Version |
|
||||||
| ------------- | ------------ |
|
| ------------- | ------------ |
|
||||||
| 0.6-0.7 | 0.15 |
|
| 0.6 | 0.15 |
|
||||||
| 0.4 | 0.14 |
|
| 0.4 | 0.14 |
|
||||||
| 0.2 | 0.13 |
|
| 0.2 | 0.13 |
|
||||||
|
|
||||||
@ -31,13 +33,13 @@ Features Include:
|
|||||||
### crates.io
|
### crates.io
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_terminal_display = "0.7"
|
bevy_terminal_display = "0.5"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using git URL in Cargo.toml
|
### Using git URL in Cargo.toml
|
||||||
```toml
|
```toml
|
||||||
[dependencies.bevy_terminal_display]
|
[dependencies.bevy_terminal_display]
|
||||||
git = "https://git.soaos.dev/soaos/bevy_terminal_display"
|
git = "https://git.exvacuum.dev/bevy_terminal_display"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{component::ComponentId, world::DeferredWorld},
|
ecs::{
|
||||||
|
component::ComponentId,
|
||||||
|
world::DeferredWorld,
|
||||||
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::render_resource::{
|
render::render_resource::{
|
||||||
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
|
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
|
||||||
@ -7,40 +10,15 @@ use bevy::{
|
|||||||
};
|
};
|
||||||
use bevy_dither_post_process::components::DitherPostProcessSettings;
|
use bevy_dither_post_process::components::DitherPostProcessSettings;
|
||||||
use bevy_headless_render::components::HeadlessRenderSource;
|
use bevy_headless_render::components::HeadlessRenderSource;
|
||||||
use ratatui::style::Style;
|
|
||||||
|
|
||||||
// TODO: MULTIPLE WINDOWS (probably behind feature flag)
|
|
||||||
// INFO: need abstraction for launching terminal emulators
|
|
||||||
//
|
|
||||||
// /// Structure to refer to a terminal window entity
|
|
||||||
// #[derive(Clone, Debug)]
|
|
||||||
// pub enum TerminalWindowRef {
|
|
||||||
// /// Refers to the primary window created by default in the terminal the command is run in
|
|
||||||
// Primary,
|
|
||||||
// /// Direct reference to an terminal window entity
|
|
||||||
// Entity(Entity),
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[derive(Component, Debug)]
|
|
||||||
// pub struct TerminalWindow;
|
|
||||||
|
|
||||||
/// Marker component for terminal display
|
/// Marker component for terminal display
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
#[component(on_add = on_add_terminal_display)]
|
#[component(on_add = on_add_terminal_display)]
|
||||||
pub struct TerminalDisplay {
|
pub struct TerminalDisplay(pub u32);
|
||||||
/// Level of dithering performed on image
|
|
||||||
pub dither_level: u32,
|
|
||||||
/// Style applied to rendered text
|
|
||||||
pub style: Style,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_add_terminal_display(mut world: DeferredWorld, entity: Entity, _id: ComponentId) {
|
fn on_add_terminal_display(mut world: DeferredWorld, entity: Entity, _id: ComponentId) {
|
||||||
let asset_server = world.get_resource::<AssetServer>().unwrap();
|
let asset_server = world.get_resource::<AssetServer>().unwrap();
|
||||||
let dither_level = world
|
let dither_level = world.entity(entity).get::<TerminalDisplay>().unwrap().0;
|
||||||
.entity(entity)
|
|
||||||
.get::<TerminalDisplay>()
|
|
||||||
.unwrap()
|
|
||||||
.dither_level;
|
|
||||||
|
|
||||||
let terminal_size = crossterm::terminal::size().unwrap();
|
let terminal_size = crossterm::terminal::size().unwrap();
|
||||||
let size = Extent3d {
|
let size = Extent3d {
|
||||||
@ -68,8 +46,8 @@ fn on_add_terminal_display(mut world: DeferredWorld, entity: Entity, _id: Compon
|
|||||||
image.resize(size);
|
image.resize(size);
|
||||||
let image_handle = asset_server.add(image);
|
let image_handle = asset_server.add(image);
|
||||||
|
|
||||||
let headless_render_source = HeadlessRenderSource::new(asset_server, image_handle.clone());
|
let headless_render_source = HeadlessRenderSource::new(&asset_server, image_handle.clone());
|
||||||
let post_process_settings = DitherPostProcessSettings::new(dither_level, asset_server);
|
let post_process_settings = DitherPostProcessSettings::new(dither_level, &asset_server);
|
||||||
world
|
world
|
||||||
.commands()
|
.commands()
|
||||||
.entity(entity)
|
.entity(entity)
|
||||||
@ -79,8 +57,6 @@ fn on_add_terminal_display(mut world: DeferredWorld, entity: Entity, _id: Compon
|
|||||||
} else {
|
} else {
|
||||||
world.commands().entity(entity).insert(Camera {
|
world.commands().entity(entity).insert(Camera {
|
||||||
target: image_handle.into(),
|
target: image_handle.into(),
|
||||||
hdr: true,
|
|
||||||
clear_color: ClearColorConfig::Custom(Color::LinearRgba(LinearRgba::BLACK)),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,9 @@ impl Default for Terminal {
|
|||||||
stdout().execute(EnterAlternateScreen).unwrap();
|
stdout().execute(EnterAlternateScreen).unwrap();
|
||||||
stdout().execute(EnableMouseCapture).unwrap();
|
stdout().execute(EnableMouseCapture).unwrap();
|
||||||
stdout()
|
stdout()
|
||||||
.execute(PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::all()))
|
.execute(PushKeyboardEnhancementFlags(
|
||||||
|
KeyboardEnhancementFlags::REPORT_EVENT_TYPES,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
enable_raw_mode().unwrap();
|
enable_raw_mode().unwrap();
|
||||||
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))
|
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))
|
||||||
|
@ -2,15 +2,16 @@ use bevy::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
render::render_resource::{Extent3d, TextureFormat},
|
render::render_resource::{Extent3d, TextureFormat},
|
||||||
};
|
};
|
||||||
use bevy_headless_render::{
|
use bevy_headless_render::{components::HeadlessRenderDestination, render_assets::HeadlessRenderSource};
|
||||||
components::HeadlessRenderDestination, render_assets::HeadlessRenderSource,
|
|
||||||
};
|
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use ratatui::widgets::{Paragraph, Wrap};
|
use ratatui::{
|
||||||
|
style::Stylize,
|
||||||
|
widgets::{Paragraph, Wrap},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{input::events::TerminalInputEvent, widgets::components::Widget};
|
use crate::{input::events::TerminalInputEvent, widgets::components::Widget};
|
||||||
|
|
||||||
use super::{components::TerminalDisplay, resources::Terminal};
|
use super::resources::Terminal;
|
||||||
|
|
||||||
const BRAILLE_CODE_MIN: u16 = 0x2800;
|
const BRAILLE_CODE_MIN: u16 = 0x2800;
|
||||||
const BRAILLE_CODE_MAX: u16 = 0x28FF;
|
const BRAILLE_CODE_MAX: u16 = 0x28FF;
|
||||||
@ -24,13 +25,11 @@ 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
|
/// Prints out the contents of a render image to the terminal as braille characters
|
||||||
pub fn print_to_terminal(
|
pub fn print_to_terminal(
|
||||||
mut terminal: ResMut<Terminal>,
|
mut terminal: ResMut<Terminal>,
|
||||||
image_exports: Query<(&TerminalDisplay, &HeadlessRenderDestination)>,
|
image_exports: Query<&HeadlessRenderDestination>,
|
||||||
mut widgets: Query<&mut Widget>,
|
mut widgets: Query<&mut Widget>,
|
||||||
) {
|
) {
|
||||||
let display = image_exports.get_single();
|
for image_export in image_exports.iter() {
|
||||||
let mut output_buffer = Vec::<char>::new();
|
let mut image = image_export
|
||||||
if let Ok((_, image)) = display {
|
|
||||||
let mut image = image
|
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.expect("Failed to get lock on output texture");
|
.expect("Failed to get lock on output texture");
|
||||||
@ -45,6 +44,7 @@ pub fn print_to_terminal(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut output_buffer = Vec::<char>::new();
|
||||||
let width = image.width();
|
let width = image.width();
|
||||||
let height = image.height();
|
let height = image.height();
|
||||||
let data = &image.data;
|
let data = &image.data;
|
||||||
@ -64,20 +64,17 @@ pub fn print_to_terminal(
|
|||||||
output_buffer.push(braille_char(mask));
|
output_buffer.push(braille_char(mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let string = output_buffer.into_iter().collect::<String>();
|
let string = output_buffer.into_iter().collect::<String>();
|
||||||
terminal
|
terminal
|
||||||
.0
|
.0
|
||||||
.draw(|frame| {
|
.draw(|frame| {
|
||||||
if !string.is_empty() {
|
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(string)
|
Paragraph::new(string)
|
||||||
.style(display.unwrap().0.style)
|
.white()
|
||||||
.wrap(Wrap { trim: true }),
|
.wrap(Wrap { trim: true }),
|
||||||
frame.area(),
|
frame.area(),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let mut active_widgets = widgets
|
let mut active_widgets = widgets
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@ -90,6 +87,7 @@ pub fn print_to_terminal(
|
|||||||
})
|
})
|
||||||
.expect("Failed to draw terminal frame");
|
.expect("Failed to draw terminal frame");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Utility function to convert a u8 into the corresponding braille character
|
/// Utility function to convert a u8 into the corresponding braille character
|
||||||
fn braille_char(mask: u8) -> char {
|
fn braille_char(mask: u8) -> char {
|
||||||
|
@ -7,7 +7,11 @@ use bevy::{
|
|||||||
use crossterm::event::{read, Event, KeyEvent, KeyEventKind, MediaKeyCode, ModifierKeyCode};
|
use crossterm::event::{read, Event, KeyEvent, KeyEventKind, MediaKeyCode, ModifierKeyCode};
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
|
|
||||||
use super::{components::DummyWindow, events::TerminalInputEvent, resources::EventQueue};
|
use super::{
|
||||||
|
components::DummyWindow,
|
||||||
|
events::TerminalInputEvent,
|
||||||
|
resources::EventQueue,
|
||||||
|
};
|
||||||
|
|
||||||
/// Initializes event queue and thread
|
/// Initializes event queue and thread
|
||||||
pub fn setup_input(mut commands: Commands, event_queue: Res<EventQueue>) {
|
pub fn setup_input(mut commands: Commands, event_queue: Res<EventQueue>) {
|
||||||
|
64
src/lib.rs
64
src/lib.rs
@ -3,29 +3,22 @@
|
|||||||
//! Bevy plugin which allows a camera to render to a terminal window.
|
//! Bevy plugin which allows a camera to render to a terminal window.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::OpenOptions,
|
fs::OpenOptions, io::{stdout, Write}, path::PathBuf, sync::{Arc, Mutex}
|
||||||
io::{stdout, Write},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
log::{
|
log::{
|
||||||
tracing_subscriber::{self, layer::SubscriberExt, EnvFilter, Layer, Registry},
|
tracing_subscriber::{self, layer::SubscriberExt, EnvFilter, Layer, Registry},
|
||||||
Level,
|
Level,
|
||||||
},
|
}, prelude::*, utils::tracing::subscriber,
|
||||||
prelude::*,
|
|
||||||
utils::tracing::subscriber,
|
|
||||||
};
|
};
|
||||||
use bevy_dither_post_process::DitherPostProcessPlugin;
|
use bevy_dither_post_process::DitherPostProcessPlugin;
|
||||||
|
|
||||||
use bevy_headless_render::HeadlessRenderPlugin;
|
use bevy_headless_render::HeadlessRenderPlugin;
|
||||||
use color_eyre::config::HookBuilder;
|
use color_eyre::config::HookBuilder;
|
||||||
pub use crossterm;
|
pub use crossterm;
|
||||||
use crossterm::{
|
use crossterm::{event::{DisableMouseCapture, PopKeyboardEnhancementFlags}, terminal::{disable_raw_mode, LeaveAlternateScreen}, ExecutableCommand};
|
||||||
event::{DisableMouseCapture, PopKeyboardEnhancementFlags},
|
use once_cell::sync::Lazy;
|
||||||
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
|
||||||
ExecutableCommand,
|
|
||||||
};
|
|
||||||
pub use ratatui;
|
pub use ratatui;
|
||||||
|
|
||||||
/// Functions and types related to capture and display of world to terminal
|
/// Functions and types related to capture and display of world to terminal
|
||||||
@ -37,6 +30,8 @@ pub mod input;
|
|||||||
/// Functions and types related to constructing and rendering TUI widgets
|
/// Functions and types related to constructing and rendering TUI widgets
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
||||||
|
static LOG_PATH: Lazy<Arc<Mutex<PathBuf>>> = Lazy::new(|| Arc::new(Mutex::new(PathBuf::default())));
|
||||||
|
|
||||||
/// Plugin providing terminal display functionality
|
/// Plugin providing terminal display functionality
|
||||||
pub struct TerminalDisplayPlugin {
|
pub struct TerminalDisplayPlugin {
|
||||||
/// Path to redirect tracing logs to. Defaults to "debug.log"
|
/// Path to redirect tracing logs to. Defaults to "debug.log"
|
||||||
@ -53,20 +48,23 @@ impl Default for TerminalDisplayPlugin {
|
|||||||
|
|
||||||
impl Plugin for TerminalDisplayPlugin {
|
impl Plugin for TerminalDisplayPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
let log_path = self.log_path.clone();
|
*LOG_PATH
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to get lock on log path mutex") = self.log_path.clone();
|
||||||
let log_file = OpenOptions::new()
|
let log_file = OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(log_path)
|
.open(
|
||||||
|
LOG_PATH
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to get lock on log path mutex")
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let file_layer = tracing_subscriber::fmt::Layer::new()
|
let file_layer = tracing_subscriber::fmt::Layer::new()
|
||||||
.with_writer(log_file)
|
.with_writer(log_file)
|
||||||
.with_filter(EnvFilter::builder().parse_lossy(format!(
|
.with_filter(EnvFilter::builder().parse_lossy(format!("{},{}", Level::INFO, "wgpu=error,naga=warn")));
|
||||||
"{},{}",
|
|
||||||
Level::INFO,
|
|
||||||
"wgpu=error,naga=warn"
|
|
||||||
)));
|
|
||||||
let subscriber = Registry::default().with(file_layer);
|
let subscriber = Registry::default().with(file_layer);
|
||||||
subscriber::set_global_default(subscriber).unwrap();
|
subscriber::set_global_default(subscriber).unwrap();
|
||||||
|
|
||||||
@ -75,18 +73,20 @@ impl Plugin for TerminalDisplayPlugin {
|
|||||||
let error = error.into_eyre_hook();
|
let error = error.into_eyre_hook();
|
||||||
|
|
||||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||||
restore_terminal();
|
let _ = restore_terminal();
|
||||||
error(e)
|
error(e)
|
||||||
}))
|
})).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
restore_terminal();
|
let _ = restore_terminal();
|
||||||
error!("{info}");
|
error!("{info}");
|
||||||
panic(info);
|
panic(info);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.add_plugins((DitherPostProcessPlugin, HeadlessRenderPlugin))
|
app.add_plugins((
|
||||||
|
DitherPostProcessPlugin,
|
||||||
|
HeadlessRenderPlugin,
|
||||||
|
))
|
||||||
.add_systems(Startup, input::systems::setup_input)
|
.add_systems(Startup, input::systems::setup_input)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
@ -100,20 +100,16 @@ impl Plugin for TerminalDisplayPlugin {
|
|||||||
)
|
)
|
||||||
.insert_resource(display::resources::Terminal::default())
|
.insert_resource(display::resources::Terminal::default())
|
||||||
.insert_resource(input::resources::EventQueue::default())
|
.insert_resource(input::resources::EventQueue::default())
|
||||||
.init_resource::<widgets::resources::FocusedWidget>()
|
|
||||||
.add_event::<input::events::TerminalInputEvent>();
|
.add_event::<input::events::TerminalInputEvent>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_terminal() {
|
fn restore_terminal() -> Result<(), Box<dyn std::error::Error>>{
|
||||||
let _ = disable_raw_mode();
|
disable_raw_mode()?;
|
||||||
let mut stdout = stdout();
|
let mut stdout = stdout();
|
||||||
let _ = stdout
|
stdout.execute(PopKeyboardEnhancementFlags)?
|
||||||
.execute(PopKeyboardEnhancementFlags)
|
.execute(DisableMouseCapture)?
|
||||||
.unwrap()
|
.execute(LeaveAlternateScreen)?
|
||||||
.execute(DisableMouseCapture)
|
.flush()?;
|
||||||
.unwrap()
|
Ok(())
|
||||||
.execute(LeaveAlternateScreen)
|
|
||||||
.unwrap()
|
|
||||||
.flush();
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
use super::resources::FocusedWidget;
|
|
||||||
|
|
||||||
struct FocusWidgetCommand(Entity);
|
|
||||||
|
|
||||||
impl Command for FocusWidgetCommand {
|
|
||||||
fn apply(self, world: &mut World) {
|
|
||||||
**world.resource_mut::<FocusedWidget>() = Some(self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Command interface for manipulating terminal widget resources
|
|
||||||
pub trait TerminalWidgetCommands {
|
|
||||||
/// Gives focus to the terminal widget on the provided entity.
|
|
||||||
fn focus_widget(&mut self, widget: Entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TerminalWidgetCommands for Commands<'_, '_> {
|
|
||||||
fn focus_widget(&mut self, widget: Entity) {
|
|
||||||
self.queue(FocusWidgetCommand(widget));
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,15 +7,9 @@ use crate::input::events::TerminalInputEvent;
|
|||||||
/// Components for this module
|
/// Components for this module
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
|
||||||
/// Resources for this module
|
|
||||||
pub mod resources;
|
|
||||||
|
|
||||||
/// Systems for this module
|
/// Systems for this module
|
||||||
pub(crate) mod systems;
|
pub(crate) mod systems;
|
||||||
|
|
||||||
/// Commands for this module
|
|
||||||
pub mod commands;
|
|
||||||
|
|
||||||
/// Trait which defines an interface for terminal widgets
|
/// Trait which defines an interface for terminal widgets
|
||||||
pub trait TerminalWidget: DowncastSync {
|
pub trait TerminalWidget: DowncastSync {
|
||||||
/// Called every frame to render the widget
|
/// Called every frame to render the widget
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
/// Terminal widget entity currently focused and handling input
|
|
||||||
/// Can be manipulated directly or you can request an entity be focused through
|
|
||||||
/// the `focus_widget` command.
|
|
||||||
#[derive(Resource, Default, Deref, DerefMut, Debug)]
|
|
||||||
pub struct FocusedWidget(pub Option<Entity>);
|
|
@ -2,25 +2,20 @@ use bevy::prelude::*;
|
|||||||
|
|
||||||
use crate::input::events::TerminalInputEvent;
|
use crate::input::events::TerminalInputEvent;
|
||||||
|
|
||||||
use super::{components::Widget, resources::FocusedWidget};
|
use super::components::Widget;
|
||||||
|
|
||||||
/// Invokes focused widget's `handle_events` methods for each incoming input event
|
/// Invokes every enabled widget's `handle_events` methods for each incoming input event
|
||||||
pub fn widget_input_handling(
|
pub fn widget_input_handling(
|
||||||
mut widgets: Query<&mut Widget>,
|
mut widgets: Query<&mut Widget>,
|
||||||
mut event_reader: EventReader<TerminalInputEvent>,
|
mut event_reader: EventReader<TerminalInputEvent>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
focused_widget: Res<FocusedWidget>,
|
|
||||||
) {
|
) {
|
||||||
if let Some(entity) = **focused_widget {
|
|
||||||
if let Ok(mut widget) = widgets.get_mut(entity) {
|
|
||||||
if widget.enabled {
|
|
||||||
for event in event_reader.read() {
|
for event in event_reader.read() {
|
||||||
|
for mut widget in widgets.iter_mut().filter(|widget| widget.enabled) {
|
||||||
widget.widget.handle_events(event, &mut commands);
|
widget.widget.handle_events(event, &mut commands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_widgets(mut widgets: Query<&mut Widget>, time: Res<Time>, mut commands: Commands) {
|
pub fn update_widgets(mut widgets: Query<&mut Widget>, time: Res<Time>, mut commands: Commands) {
|
||||||
for mut widget in widgets.iter_mut() {
|
for mut widget in widgets.iter_mut() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user