Skip to main content

eye_declare/
lib.rs

1//! Declarative inline TUI rendering for Rust, built on [Ratatui](https://ratatui.rs).
2//!
3//! eye_declare provides a React-like component model for terminal UIs that render
4//! **inline** — content grows downward into the terminal's native scrollback rather
5//! than taking over the full screen. This makes it ideal for CLI tools, AI assistants,
6//! build systems, and interactive prompts where earlier output should remain visible.
7//!
8//! # Quick start
9//!
10//! ```ignore
11//! use eye_declare::{element, Application, Elements, Spinner};
12//!
13//! struct State { messages: Vec<String>, loading: bool }
14//!
15//! fn view(state: &State) -> Elements {
16//!     element! {
17//!         #(for (i, msg) in state.messages.iter().enumerate() {
18//!             Text(key: format!("msg-{i}")) { #(msg.clone()) }
19//!         })
20//!         #(if state.loading {
21//!             Spinner(key: "loading", label: "Thinking...")
22//!         })
23//!     }
24//! }
25//!
26//! #[tokio::main]
27//! async fn main() -> std::io::Result<()> {
28//!     let (mut app, handle) = Application::builder()
29//!         .state(State { messages: vec![], loading: true })
30//!         .view(view)
31//!         .build()?;
32//!
33//!     tokio::spawn(async move {
34//!         handle.update(|s| s.messages.push("Hello!".into()));
35//!         handle.update(|s| s.loading = false);
36//!     });
37//!
38//!     app.run().await
39//! }
40//! ```
41//!
42//! # Core concepts
43//!
44//! - **[`#[component]`](macro@component) + [`#[props]`](macro@props)** — Define
45//!   components as functions. Props are a struct with `#[props]`; the function
46//!   receives props, optional state, hooks, and children, returning an [`Elements`]
47//!   tree. Hooks declare behavioral capabilities (events, focus, intervals).
48//!
49//! - **[`Elements`]** — A list of component descriptions returned by view functions.
50//!   The framework reconciles the new list against the existing tree, preserving
51//!   state for reused nodes.
52//!
53//! - **[`element!`]** — A JSX-like proc macro for building `Elements` declaratively.
54//!   Supports props, children, keys, conditionals (`#(if ...)`), loops (`#(for ...)`),
55//!   and splicing pre-built `Elements` (`#(expr)`).
56//!
57//! - **[`Application`]** — Owns your state and manages the render loop.
58//!   [`Handle`] lets you send state updates from any thread or async task.
59//!
60//! - **[`InlineRenderer`]** — The lower-level rendering engine for when you need
61//!   direct control over the render loop (sync code, embedding, custom event loops).
62//!
63//! # Built-in components
64//!
65//! | Component | Description |
66//! |-----------|-------------|
67//! | [`Text`] | Styled text with word wrapping (data children: [`Span`]) |
68//! | [`Spinner`] | Animated spinner with auto-tick via lifecycle hooks |
69//! | [`Markdown`] | Headings, bold, italic, inline code, code blocks, lists |
70//! | [`View`] | Unified layout container with optional borders, padding, and background |
71//! | [`Canvas`] | Raw buffer rendering via a user-provided closure |
72//! | [`VStack`] | Vertical container — children stack top-to-bottom |
73//! | [`HStack`] | Horizontal container with [`WidthConstraint`]-based layout |
74//!
75//! # Layout
76//!
77//! Vertical stacking is the default. [`HStack`] provides horizontal layout where
78//! children declare their width via [`WidthConstraint::Fixed`] or [`WidthConstraint::Fill`].
79//! Components can declare [`Insets`] for border/padding chrome — children render inside
80//! the inset area while the component draws its chrome in the full area.
81//!
82//! # Lifecycle hooks
83//!
84//! Components declare effects using the [`Hooks`] API:
85//!
86//! - [`Hooks::use_interval`] — periodic callback (e.g., animation)
87//! - [`Hooks::use_mount`] — fires once after the component is built
88//! - [`Hooks::use_unmount`] — fires when the component is removed
89//! - [`Hooks::use_autofocus`] — request focus on mount
90//! - [`Hooks::use_focus_scope`] — confine Tab cycling to this subtree
91//! - [`Hooks::provide_context`] — make a value available to descendants
92//! - [`Hooks::use_context`] — read a value provided by an ancestor
93//!
94//! # Context
95//!
96//! The context system lets ancestor components provide values to their
97//! descendants without prop-drilling. Register root-level contexts via
98//! [`ApplicationBuilder::with_context`], or have components provide
99//! them via [`Hooks::provide_context`] in their component function.
100//! Descendants read context values with [`Hooks::use_context`].
101//!
102//! This is commonly used with [`Application::run_loop`] to give
103//! components access to an app-domain event channel:
104//!
105//! ```ignore
106//! let (tx, mut rx) = tokio::sync::mpsc::channel(32);
107//! let (mut app, handle) = Application::builder()
108//!     .state(MyState::default())
109//!     .view(my_view)
110//!     .with_context(tx)
111//!     .build()?;
112//!
113//! let h = handle.clone();
114//! tokio::spawn(async move {
115//!     while let Some(event) = rx.recv().await {
116//!         match event {
117//!             AppEvent::Submit(val) => h.update(|s| s.result = val),
118//!             AppEvent::Quit => { h.exit(); break; }
119//!         }
120//!     }
121//! });
122//!
123//! app.run_loop().await?;
124//! ```
125//!
126//! # Feature flags
127//!
128//! | Flag | Default | Description |
129//! |------|---------|-------------|
130//! | `macros` | yes | Enables the [`element!`] proc macro via `eye_declare_macros` |
131
132/// Application wrapper, builder, handle, and control flow types.
133///
134/// See [`Application`] for the high-level entry point.
135pub mod app;
136
137/// Traits and types for the `element!` macro's child collection system.
138///
139/// Most users won't interact with this module directly — it powers the
140/// `element!` macro's ability to type-check parent-child relationships
141/// at compile time. See [`ChildCollector`] if you're building a component
142/// that accepts data children (like [`Text`](crate::Text) accepts [`Span`](crate::Span)s).
143pub mod children;
144
145/// The [`Component`] trait (framework-internal) and built-in containers
146/// ([`VStack`], [`HStack`], [`Column`]).
147pub mod component;
148
149/// Built-in components: [`Text`](components::text::Text),
150/// [`Spinner`](components::spinner::Spinner),
151/// [`Markdown`](components::markdown::Markdown), and
152/// [`View`](components::view::View).
153pub mod components;
154
155/// The [`Elements`] list and [`ElementHandle`] for building component trees.
156pub mod element;
157
158/// Lifecycle hooks for declaring component effects.
159///
160/// See [`Hooks`] for the API used inside `#[component]` functions.
161pub mod hooks;
162
163/// The [`InlineRenderer`] — low-level inline rendering engine.
164///
165/// Use this when you need direct control over the render loop
166/// rather than the higher-level [`Application`] wrapper.
167pub mod inline;
168
169/// The [`Insets`] type for declaring content padding and border chrome.
170pub mod insets;
171
172/// Cell measurement type for component props. See [`Cells`].
173pub mod cells;
174
175pub(crate) mod context;
176pub(crate) mod escape;
177pub(crate) mod frame;
178pub(crate) mod node;
179pub(crate) mod renderer;
180pub(crate) mod wrap;
181
182pub use app::{
183    Application, ApplicationBuilder, CommittedElement, ControlFlow, CtrlCBehavior, Handle,
184    KeyboardProtocol,
185};
186pub use cells::Cells;
187pub use children::{
188    AddTo, ChildCollector, ComponentWithSlot, DataChildren, DataHandle, SpliceInto,
189};
190pub use component::{Column, Component, EventResult, HStack, Tracked, TrackedRef, VStack};
191pub use components::canvas::Canvas;
192pub use components::markdown::{Markdown, MarkdownState};
193pub use components::spinner::{Spinner, SpinnerState};
194pub use components::text::{Span, Text, TextChild};
195pub use components::view::{Direction, View};
196pub use components::viewport::Viewport;
197pub use element::{ElementHandle, Elements};
198
199pub use hooks::Hooks;
200pub use inline::InlineRenderer;
201pub use insets::Insets;
202pub use node::{Layout, NodeId, WidthConstraint};
203pub use typed_builder::TypedBuilder;
204
205// Re-exports for component props
206/// Re-exported from `ratatui_widgets` for use with [`View::border`].
207pub use ratatui_widgets::borders::BorderType;
208
209/// Declarative element tree macro.
210///
211/// Builds an [`Elements`] list from JSX-like syntax. This is the primary
212/// way to describe UI trees in eye_declare.
213///
214/// # Syntax
215///
216/// ```ignore
217/// element! {
218///     // Component with props
219///     Spinner(label: "Loading...", done: false)
220///
221///     // Component with children (slot)
222///     VStack {
223///         "hello"
224///     }
225///
226///     // Key for stable identity across rebuilds
227///     Markdown(key: "intro", source: "# Hello".into())
228///
229///     // String literal shorthand (becomes a Text)
230///     "Some plain text"
231///
232///     // Conditional children
233///     #(if state.loading {
234///         Spinner(label: "Please wait...")
235///     })
236///
237///     // Loop children
238///     #(for item in &state.items {
239///         Markdown(key: item.id.clone(), source: item.text.clone())
240///     })
241///
242///     // Splice pre-built Elements
243///     #(footer_elements(state))
244/// }
245/// ```
246///
247/// The macro returns an [`Elements`] value. View functions typically
248/// return this directly:
249///
250/// ```ignore
251/// fn my_view(state: &MyState) -> Elements {
252///     element! {
253///         Spinner(label: "working...")
254///     }
255/// }
256/// ```
257#[cfg(feature = "macros")]
258pub use eye_declare_macros::component;
259#[cfg(feature = "macros")]
260pub use eye_declare_macros::element;
261#[cfg(feature = "macros")]
262pub use eye_declare_macros::props;