├── .gitignore ├── doc └── screenshot.png ├── src ├── input │ ├── components.rs │ ├── events.rs │ ├── mod.rs │ ├── resources.rs │ └── systems.rs ├── display │ ├── mod.rs │ ├── resources.rs │ ├── components.rs │ └── systems.rs ├── widgets │ ├── resources.rs │ ├── components.rs │ ├── commands.rs │ ├── mod.rs │ └── systems.rs └── lib.rs ├── LICENSE-0BSD ├── .gitea └── workflows │ └── build.yaml ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .cargo 4 | -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soaosdev/bevy_terminal_display/HEAD/doc/screenshot.png -------------------------------------------------------------------------------- /src/input/components.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Debug, Component)] 4 | pub struct DummyWindow; 5 | -------------------------------------------------------------------------------- /src/display/mod.rs: -------------------------------------------------------------------------------- 1 | /// Components for this module 2 | pub mod components; 3 | 4 | /// Resources for this module 5 | pub mod resources; 6 | 7 | /// Systems for this module 8 | pub(crate) mod systems; 9 | -------------------------------------------------------------------------------- /src/input/events.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use crossterm::event::Event; 3 | 4 | /// An event triggered when a crossterm input event is received 5 | #[derive(Event)] 6 | pub struct TerminalInputEvent(pub Event); 7 | -------------------------------------------------------------------------------- /src/input/mod.rs: -------------------------------------------------------------------------------- 1 | /// Events for this module 2 | pub mod events; 3 | 4 | /// Resources for this module 5 | pub mod resources; 6 | 7 | /// Systems for this module 8 | pub(crate) mod systems; 9 | 10 | /// Components for this module 11 | pub(crate) mod components; 12 | -------------------------------------------------------------------------------- /src/input/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use crossterm::event::Event; 3 | use std::sync::{Arc, Mutex}; 4 | 5 | /// Event queue for crossterm input event thread 6 | #[derive(Resource, Default)] 7 | pub(crate) struct EventQueue(pub(super) Arc>>); 8 | -------------------------------------------------------------------------------- /src/widgets/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | /// Terminal widget entity currently focused and handling input 4 | /// Can be manipulated directly or you can request an entity be focused through 5 | /// the `focus_widget` command. 6 | #[derive(Resource, Default, Deref, DerefMut, Debug)] 7 | pub struct FocusedWidget(pub Option); 8 | -------------------------------------------------------------------------------- /src/widgets/components.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use super::TerminalWidget; 4 | 5 | /// Component representing a terminal widget. 6 | #[derive(Component)] 7 | pub struct Widget { 8 | /// The widget instance itself, containing rendering and input logic 9 | pub widget: Box, 10 | /// Depth to render widget at 11 | pub depth: u32, 12 | /// Whether this widget is currently enabled or should be hidden 13 | pub enabled: bool, 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE-0BSD: -------------------------------------------------------------------------------- 1 | Copyright (C) 2024 by Silas Bartha silas@exvacuum.dev 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /src/widgets/commands.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use super::resources::FocusedWidget; 4 | 5 | struct FocusWidgetCommand(Entity); 6 | 7 | impl Command for FocusWidgetCommand { 8 | fn apply(self, world: &mut World) { 9 | **world.resource_mut::() = Some(self.0); 10 | } 11 | } 12 | 13 | /// Command interface for manipulating terminal widget resources 14 | pub trait TerminalWidgetCommands { 15 | /// Gives focus to the terminal widget on the provided entity. 16 | fn focus_widget(&mut self, widget: Entity); 17 | } 18 | 19 | impl TerminalWidgetCommands for Commands<'_, '_> { 20 | fn focus_widget(&mut self, widget: Entity) { 21 | self.queue(FocusWidgetCommand(widget)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitea/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push] 3 | 4 | jobs: 5 | Build: 6 | env: 7 | RUNNER_TOOL_CACHE: /toolcache 8 | container: rust:alpine 9 | steps: 10 | - name: Install node 11 | run: apk add nodejs gcc libc-dev pkgconf libx11-dev alsa-lib-dev eudev-dev tar 12 | - name: Check out repository code 13 | uses: actions/checkout@v4 14 | - name: Restore cache 15 | uses: actions/cache@v4 16 | with: 17 | path: | 18 | ~/.cargo/bin/ 19 | ~/.cargo/registry/index/ 20 | ~/.cargo/registry/cache/ 21 | ~/.cargo/git/db/ 22 | target/ 23 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 24 | - name: Build 25 | run: cargo build --release 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_terminal_display" 3 | version = "0.7.0" 4 | edition = "2021" 5 | license = "0BSD OR MIT OR Apache-2.0" 6 | description = "A plugin for the Bevy game engine which enables rendering to a terminal using unicode braille characters." 7 | repository = "https://git.soaos.dev/soaos/bevy_terminal_display" 8 | 9 | [profile.dev] 10 | opt-level = 1 11 | 12 | [profile.dev.package."*"] 13 | opt-level = 2 14 | 15 | [dependencies] 16 | crossbeam-channel = "0.5" 17 | downcast-rs = "2.0" 18 | once_cell = "1.19" 19 | ratatui = "0.29" 20 | color-eyre = "0.6" 21 | leafwing-input-manager = "0.16" 22 | serde = "1.0" 23 | smol_str = "0.2" 24 | 25 | [dependencies.bevy] 26 | version = "0.15" 27 | default-features = false 28 | features = ["bevy_render"] 29 | 30 | [dependencies.crossterm] 31 | version = "0.28" 32 | features = ["serde"] 33 | 34 | # SOAOS DEPS 35 | 36 | [dependencies.bevy_dither_post_process] 37 | version = "0.3" 38 | 39 | [dependencies.bevy_headless_render] 40 | version = "0.2" -------------------------------------------------------------------------------- /src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use downcast_rs::{impl_downcast, DowncastSync}; 3 | use ratatui::{layout::Rect, Frame}; 4 | 5 | use crate::input::events::TerminalInputEvent; 6 | 7 | /// Components for this module 8 | pub mod components; 9 | 10 | /// Resources for this module 11 | pub mod resources; 12 | 13 | /// Systems for this module 14 | pub(crate) mod systems; 15 | 16 | /// Commands for this module 17 | pub mod commands; 18 | 19 | /// Trait which defines an interface for terminal widgets 20 | pub trait TerminalWidget: DowncastSync { 21 | /// Called every frame to render the widget 22 | fn render(&mut self, frame: &mut Frame, rect: Rect); 23 | 24 | /// Called when a terminal input event is invoked to update any state accordingly 25 | fn handle_events(&mut self, _event: &TerminalInputEvent, _commands: &mut Commands) {} 26 | 27 | /// Called every frame during the Update schedule 28 | fn update(&mut self, _time: &Time, _commands: &mut Commands) {} 29 | } 30 | impl_downcast!(sync TerminalWidget); 31 | -------------------------------------------------------------------------------- /src/widgets/systems.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use crate::input::events::TerminalInputEvent; 4 | 5 | use super::{components::Widget, resources::FocusedWidget}; 6 | 7 | /// Invokes focused widget's `handle_events` methods for each incoming input event 8 | pub fn widget_input_handling( 9 | mut widgets: Query<&mut Widget>, 10 | mut event_reader: EventReader, 11 | mut commands: Commands, 12 | focused_widget: Res, 13 | ) { 14 | if let Some(entity) = **focused_widget { 15 | if let Ok(mut widget) = widgets.get_mut(entity) { 16 | if widget.enabled { 17 | for event in event_reader.read() { 18 | widget.widget.handle_events(event, &mut commands); 19 | } 20 | } 21 | } 22 | } 23 | } 24 | 25 | pub fn update_widgets(mut widgets: Query<&mut Widget>, time: Res