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;