Skip to main content

fusabi_tui_scarab/
lib.rs

1//! Scarab shared memory backend for Fusabi TUI.
2//!
3//! This crate provides integration between the Fusabi TUI framework and the
4//! Scarab terminal emulator's shared memory protocol. It enables zero-copy
5//! rendering of TUI applications directly to Scarab's terminal buffer.
6//!
7//! # Architecture
8//!
9//! Scarab uses a split-process architecture with shared memory for IPC:
10//! - **Daemon**: Headless server that owns PTY processes
11//! - **Client**: Bevy-based GUI that renders via shared memory
12//! - **Plugins**: Can run in either process with Fusabi scripting
13//!
14//! This crate provides:
15//! - `ScarabRenderer`: Renderer implementation that writes to shared memory
16//! - `SharedCell` / `SharedState`: Zero-copy types matching Scarab's protocol
17//! - Conversion utilities between Fusabi and Scarab types
18//! - Plugin trait for building TUI applications
19//!
20//! # Examples
21//!
22//! ## Basic Rendering
23//!
24//! ```no_run
25//! use fusabi_tui_scarab::renderer::ScarabRenderer;
26//! use fusabi_tui_render::renderer::Renderer;
27//! use fusabi_tui_core::buffer::Buffer;
28//! use fusabi_tui_core::layout::Rect;
29//! use fusabi_tui_core::style::{Style, Color};
30//!
31//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
32//! // Connect to Scarab's shared memory
33//! let mut renderer = ScarabRenderer::connect(None)?;
34//!
35//! // Create a buffer and draw to it
36//! let size = renderer.size()?;
37//! let mut buffer = Buffer::new(size);
38//!
39//! let style = Style::new().fg(Color::Green);
40//! buffer.set_string(0, 0, "Hello, Scarab!", style);
41//!
42//! // Render to shared memory
43//! renderer.draw(&buffer)?;
44//! renderer.flush()?;
45//! # Ok(())
46//! # }
47//! ```
48//!
49//! ## Plugin Development
50//!
51//! ```no_run
52//! use fusabi_tui_scarab::plugin::{TuiPlugin, PluginContext, RenderContext, InputEvent, Action};
53//! use fusabi_tui_scarab::error::Result;
54//! use fusabi_tui_core::buffer::Buffer;
55//! use fusabi_tui_core::style::{Style, Color};
56//!
57//! struct MyPlugin {
58//!     counter: u32,
59//! }
60//!
61//! impl TuiPlugin for MyPlugin {
62//!     fn on_init(&mut self, ctx: &PluginContext) -> Result<()> {
63//!         println!("Plugin initialized!");
64//!         Ok(())
65//!     }
66//!
67//!     fn on_render(&mut self, ctx: &RenderContext) -> Result<Buffer> {
68//!         let mut buffer = Buffer::new(ctx.size);
69//!         let style = Style::new().fg(Color::Cyan);
70//!
71//!         let text = format!("Frame: {}", ctx.frame);
72//!         buffer.set_string(0, 0, &text, style);
73//!
74//!         Ok(buffer)
75//!     }
76//!
77//!     fn on_input(&mut self, event: InputEvent) -> Result<Action> {
78//!         self.counter += 1;
79//!         Ok(Action::Redraw)
80//!     }
81//! }
82//! ```
83//!
84//! # Shared Memory Layout
85//!
86//! The shared memory region contains a `SharedState` structure with:
87//! - Sequence number for lock-free synchronization
88//! - Cursor position
89//! - 200x100 grid of cells (each 16 bytes)
90//!
91//! All structures use `#[repr(C)]` and `bytemuck::Pod` for zero-copy safety.
92//!
93//! # Features
94//!
95//! - `plugin`: Enables plugin system with JSON serialization (optional)
96
97#![warn(missing_docs)]
98#![warn(clippy::all)]
99
100pub mod convert;
101pub mod error;
102pub mod plugin;
103pub mod renderer;
104pub mod shared;
105
106// Re-export commonly used types
107pub use error::{Result, ScarabError};
108pub use renderer::ScarabRenderer;
109pub use shared::{SharedCell, SharedState, GRID_HEIGHT, GRID_WIDTH, SHMEM_PATH};
110
111#[cfg(feature = "plugin")]
112pub use plugin::{
113    Action, InputEvent, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent,
114    MouseEventKind, PluginContext, RenderContext, TuiPlugin,
115};
116
117/// Prelude module for convenient imports.
118pub mod prelude {
119    //! Convenient re-exports for common types and traits.
120
121    pub use crate::error::{Result, ScarabError};
122    pub use crate::renderer::ScarabRenderer;
123    pub use crate::shared::{SharedCell, SharedState};
124
125    #[cfg(feature = "plugin")]
126    pub use crate::plugin::{
127        Action, InputEvent, KeyCode, KeyEvent, PluginContext, RenderContext, TuiPlugin,
128    };
129
130    // Re-export core TUI types
131    pub use fusabi_tui_core::{
132        buffer::{Buffer, Cell},
133        layout::{Constraint, Direction, Layout, Rect},
134        style::{Color, Modifier, Style},
135    };
136
137    // Re-export renderer trait
138    pub use fusabi_tui_render::renderer::Renderer;
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_shared_cell_size() {
147        // Verify SharedCell is exactly 16 bytes (required by Scarab protocol)
148        assert_eq!(std::mem::size_of::<SharedCell>(), 16);
149    }
150
151    #[test]
152    fn test_shared_state_size() {
153        // Verify SharedState size matches expected layout
154        let expected = std::mem::size_of::<u64>() // sequence_number
155            + std::mem::size_of::<u8>()           // dirty_flag
156            + std::mem::size_of::<u8>()           // error_mode
157            + std::mem::size_of::<u16>()          // cursor_x
158            + std::mem::size_of::<u16>()          // cursor_y
159            + 2                                    // _padding2
160            + (std::mem::size_of::<SharedCell>() * shared::BUFFER_SIZE); // cells
161
162        assert_eq!(std::mem::size_of::<SharedState>(), expected);
163    }
164}