revue/lib.rs
1//! # Revue
2//!
3//! A Vue-style TUI framework for Rust with CSS styling, reactive state management,
4//! and a rich set of widgets for building beautiful terminal user interfaces.
5//!
6//! ## Features
7//!
8//! | Feature | Description |
9//! |---------|-------------|
10//! | **CSS Styling** | External CSS files with variables, selectors, transitions, and hot reload |
11//! | **Flexbox Layout** | Custom TUI-optimized layout engine for flexible layouts |
12//! | **Reactive State** | Vue-inspired Signal/Computed/Effect pattern |
13//! | **80+ Widgets** | Text, Button, Input, Table, Tree, Modal, Toast, Charts, and more |
14//! | **Markdown & Images** | Built-in markdown rendering with Kitty image protocol support |
15//! | **Developer Tools** | Hot reload, widget inspector, snapshot testing (Pilot) |
16//! | **Theming** | Built-in themes: Dracula, Nord, Monokai, Gruvbox, Catppuccin |
17//!
18//! ## Quick Start
19//!
20//! ```rust,ignore
21//! use revue::prelude::*;
22//!
23//! fn main() -> Result<()> {
24//! let mut app = App::builder().build();
25//! let counter = Counter::new();
26//!
27//! app.run_with_handler(counter, |key, state| {
28//! state.handle_key(&key.key)
29//! })
30//! }
31//!
32//! struct Counter { value: i32 }
33//!
34//! impl Counter {
35//! fn new() -> Self { Self { value: 0 } }
36//!
37//! fn handle_key(&mut self, key: &Key) -> bool {
38//! match key {
39//! Key::Up => { self.value += 1; true }
40//! Key::Down => { self.value -= 1; true }
41//! _ => false,
42//! }
43//! }
44//! }
45//!
46//! impl View for Counter {
47//! fn render(&self, ctx: &mut RenderContext) {
48//! vstack()
49//! .child(Text::new(format!("Count: {}", self.value)))
50//! .child(Text::muted("[↑/↓] to change, [q] to quit"))
51//! .render(ctx);
52//! }
53//! }
54//! ```
55//!
56//! ## CSS Styling
57//!
58//! Revue supports CSS for styling widgets:
59//!
60//! ```css
61//! /* styles.css */
62//! :root {
63//! --primary: #bd93f9;
64//! --bg: #282a36;
65//! }
66//!
67//! .button {
68//! background: var(--primary);
69//! color: var(--bg);
70//! transition: background 0.3s ease;
71//! }
72//!
73//! .button:hover {
74//! background: #ff79c6;
75//! }
76//! ```
77//!
78//! ## Reactive State
79//!
80//! ```rust,ignore
81//! use revue::prelude::*;
82//!
83//! let count = signal(0);
84//! let doubled = computed(move || count.get() * 2);
85//!
86//! effect(move || {
87//! println!("Count changed to: {}", count.get());
88//! });
89//!
90//! count.set(5); // Triggers effect, doubled is now 10
91//! ```
92//!
93//! ## Widget Gallery
94//!
95//! ### Layout
96//! - [`widget::vstack()`] / [`widget::hstack()`] - Vertical/horizontal stack layout
97//! - [`widget::Border`] - Bordered container with title support
98//! - [`widget::Tabs`] - Tab navigation
99//! - [`widget::ScrollView`] - Scrollable content area
100//! - [`widget::Layers`] - Overlapping widgets (for modals, toasts)
101//!
102//! ### Input
103//! - [`widget::Input`] - Single-line text input
104//! - [`widget::TextArea`] - Multi-line text editor
105//! - [`widget::Button`] - Clickable button with variants
106//! - [`widget::Checkbox`] - Toggle checkbox
107//! - [`widget::RadioGroup`] - Radio button group
108//! - [`widget::Select`] - Dropdown selection
109//!
110//! ### Display
111//! - [`widget::Text`] - Styled text display
112//! - [`widget::Progress`] - Progress bar
113//! - [`widget::Spinner`] - Loading spinner
114//! - [`widget::Badge`] / [`widget::Tag`] - Labels and tags
115//! - [`widget::Avatar`] - User avatar display
116//! - [`widget::Skeleton`] - Loading placeholder
117//!
118//! ### Data
119//! - [`widget::Table`] - Data table with columns
120//! - [`widget::List`] - Selectable list
121//! - [`widget::Tree`] - Hierarchical tree view
122//! - [`widget::Sparkline`] - Inline mini chart
123//! - [`widget::BarChart`] - Bar chart visualization
124//! - [`widget::Canvas`] / [`widget::BrailleCanvas`] - Custom drawing
125//!
126//! ### Feedback
127//! - [`widget::Modal`] - Dialog overlay
128//! - [`widget::Toast`] - Notification popup
129//! - [`widget::CommandPalette`] - Fuzzy command search (Ctrl+P)
130//!
131//! ## Module Overview
132//!
133//! | Module | Description |
134//! |--------|-------------|
135//! | [`app`] | Application lifecycle and event loop |
136//! | [`dom`] | Virtual DOM and rendering tree |
137//! | [`event`] | Keyboard/mouse events and keymaps |
138//! | [`layout`] | Flexbox layout engine |
139//! | [`reactive`] | Signal/Computed/Effect primitives |
140//! | [`render`] | Terminal rendering and buffer |
141//! | [`style`] | CSS parsing and theming |
142//! | [`testing`] | Pilot testing framework |
143//! | [`widget`] | All widget implementations |
144//! | [`worker`] | Background task execution |
145//!
146//! ## Testing with Pilot
147//!
148//! ```rust,ignore
149//! use revue::testing::{Pilot, TestApp};
150//!
151//! #[test]
152//! fn test_counter() {
153//! let mut app = TestApp::new(Counter::new());
154//! let mut pilot = Pilot::new(&mut app);
155//!
156//! pilot
157//! .press(Key::Up)
158//! .press(Key::Up)
159//! .assert_contains("2");
160//! }
161//! ```
162//!
163//! ## Themes
164//!
165//! Built-in themes available via [`style::themes`]:
166//!
167//! - **Dracula** - Dark purple theme
168//! - **Nord** - Arctic blue theme
169//! - **Monokai** - Classic dark theme
170//! - **Gruvbox** - Retro groove theme
171//! - **Catppuccin** - Pastel dark theme
172//!
173//! ## Comparison with Other Frameworks
174//!
175//! | Feature | Revue | Ratatui | Textual | Cursive |
176//! |---------|-------|---------|---------|---------|
177//! | Language | Rust | Rust | Python | Rust |
178//! | CSS Styling | ✅ | ❌ | ✅ | ❌ |
179//! | Reactive State | ✅ | ❌ | ✅ | ❌ |
180//! | Hot Reload | ✅ | ❌ | ✅ | ❌ |
181//! | Widget Count | 80+ | 13 | 35+ | 40+ |
182//! | Snapshot Testing | ✅ | ❌ | ❌ | ❌ |
183
184#![warn(missing_docs)]
185
186/// The version of the library, including the git commit hash in development builds.
187///
188/// In production releases (from crates.io), this will be a semver like "2.33.4".
189///
190/// In development builds, this will be in the format "2.33.4-SHA" where SHA is the
191/// short git commit hash, allowing precise identification of the exact code version.
192pub const VERSION: &str = env!("REVUE_VERSION");
193
194/// The full git commit hash of this build.
195///
196/// Empty in release builds, contains the 40-character commit SHA in development builds.
197pub const GIT_SHA: &str = env!("GIT_SHA");
198
199/// Whether this is a development build (with commit hash in version) or a release build.
200///
201/// Returns `true` for development builds (version includes SHA), `false` for releases.
202pub fn is_dev_build() -> bool {
203 env!("REVUE_IS_DEV") == "true"
204}
205
206// Internal logging macros - no-op when tracing feature is disabled
207#[cfg(feature = "tracing")]
208macro_rules! log_debug {
209 ($($arg:tt)*) => { tracing::debug!($($arg)*) }
210}
211#[cfg(not(feature = "tracing"))]
212macro_rules! log_debug {
213 ($($arg:tt)*) => { { let _ = ($($arg)*,); } }
214}
215pub(crate) use log_debug;
216
217#[cfg(feature = "tracing")]
218macro_rules! log_warn {
219 ($($arg:tt)*) => { tracing::warn!($($arg)*) }
220}
221#[cfg(not(feature = "tracing"))]
222macro_rules! log_warn {
223 ($($arg:tt)*) => { { let _ = ($($arg)*,); } }
224}
225pub(crate) use log_warn;
226
227#[cfg(feature = "tracing")]
228macro_rules! log_error {
229 ($($arg:tt)*) => { tracing::error!($($arg)*) }
230}
231#[cfg(not(feature = "tracing"))]
232macro_rules! log_error {
233 ($($arg:tt)*) => { { let _ = ($($arg)*,); } }
234}
235pub(crate) use log_error;
236
237pub mod a11y;
238pub mod app;
239pub mod constants;
240pub mod devtools;
241pub mod dom;
242pub mod event;
243pub mod layout;
244pub mod patterns;
245pub mod plugin;
246pub mod query;
247pub mod reactive;
248pub mod render;
249pub mod style;
250pub mod tasks;
251pub mod testing;
252pub mod text;
253pub mod utils;
254pub mod widget;
255pub mod worker;
256
257// Re-export derive macros
258pub use revue_macros::Store;
259
260/// Error type for Revue operations.
261///
262/// This enum covers all error cases that can occur when using Revue,
263/// including CSS parsing errors, I/O errors, and general runtime errors.
264///
265/// # Example
266///
267/// ```rust,ignore
268/// use revue::{Error, Result};
269///
270/// fn load_styles() -> Result<()> {
271/// // Operations that might fail...
272/// Ok(())
273/// }
274/// ```
275#[derive(Debug, thiserror::Error)]
276pub enum Error {
277 /// CSS parsing error.
278 ///
279 /// Occurs when parsing invalid CSS syntax or unsupported properties.
280 #[error("CSS error: {0}")]
281 Css(#[from] style::ParseError),
282
283 /// I/O error.
284 ///
285 /// Occurs during file operations (loading CSS, images, etc.).
286 #[error("I/O error: {0}")]
287 Io(#[from] std::io::Error),
288
289 /// Generic error with custom message.
290 ///
291 /// Used for errors that don't fit other categories.
292 #[error("{0}")]
293 Other(String),
294}
295
296/// Result type alias for Revue operations.
297///
298/// Shorthand for `std::result::Result<T, revue::Error>`.
299///
300/// # Example
301///
302/// ```rust,ignore
303/// use revue::Result;
304///
305/// fn load_config() -> Result<String> {
306/// let content = std::fs::read_to_string("config.css")?;
307/// Ok(content)
308/// }
309/// ```
310pub type Result<T> = std::result::Result<T, Error>;
311
312// ─────────────────────────────────────────────────────────────────────────
313// Error Handling Guidelines
314// ─────────────────────────────────────────────────────────────────────────
315
316/// # Error Handling Guidelines
317///
318/// Revue follows these error handling patterns to provide clear, actionable error messages.
319///
320/// ## Public APIs
321///
322/// **Always return `Result<T>`** for public APIs that can fail:
323///
324/// ```rust,ignore
325/// use revue::Result;
326///
327/// pub fn parse_css(input: &str) -> Result<Stylesheet> {
328/// // Parse and return Ok(stylesheet) or Err(Error)
329/// }
330/// ```
331///
332/// ## Internal Code
333///
334/// Choose the appropriate error handling strategy:
335///
336/// | Situation | Use | Example |
337/// |-----------|-----|---------|
338/// | Recoverable error | `Result<T, E>` | File not found, invalid input |
339/// | Optional value | `Option<T>` | Missing config, lookup by ID |
340/// | Truly invariant | `expect(msg)` | Logic errors, "never happens" |
341/// | Never fails | `unwrap()` // With comment explaining why | Static constants, known-good values |
342///
343/// ## Error Type Hierarchy
344///
345/// Revue uses `thiserror` for error definitions:
346///
347/// ```rust,ignore
348/// #[derive(Debug, thiserror::Error)]
349/// pub enum Error {
350/// #[error("CSS error: {0}")]
351/// Css(#[from] style::ParseError),
352///
353/// #[error("I/O error: {0}")]
354/// Io(#[from] std::io::Error),
355///
356/// #[error("{0}")]
357/// Other(String),
358/// }
359/// ```
360///
361/// ## Error Context
362///
363/// **Good: Add context for clarity**
364///
365/// ```rust,ignore
366/// let file = std::fs::read_to_string(path)
367/// .map_err(|e| Error::Other(format!("Failed to read config: {}", e)))?;
368/// ```
369///
370/// **Bad: Lose context**
371///
372/// ```rust,ignore
373/// let file = std::fs::read_to_string(path)?; // Error doesn't mention config
374/// ```
375///
376/// ## Converting Errors
377///
378/// Use `?` operator for automatic conversion:
379///
380/// ```rust,ignore
381/// use revue::Result;
382///
383/// fn load_stylesheet(path: &str) -> Result<Stylesheet> {
384/// let content = std::fs::read_to_string(path)?; // io::Error → Error::Io
385/// Ok(parse_css(&content)?)
386/// }
387/// ```
388///
389/// Prelude module for convenient imports.
390///
391/// Import everything you need with a single line:
392///
393/// ```rust,ignore
394/// use revue::prelude::*;
395/// ```
396///
397/// # Included Items
398///
399/// ## Core Types
400/// - [`app::App`] - Application builder and runner
401/// - [`widget::View`], [`widget::RenderContext`] - Widget rendering trait
402/// - [`Result`] - Error handling
403///
404/// ## Events
405/// - [`event::Key`], [`event::KeyEvent`], [`event::Event`] - Input handling
406///
407/// ## Reactive
408/// - [`reactive::signal`], [`reactive::computed`], [`reactive::effect`] - State primitives
409/// - [`reactive::Signal`], [`reactive::Computed`] - Reactive types
410///
411/// ## Layout
412/// - [`widget::vstack`], [`widget::hstack`] - Stack layouts
413/// - [`layout::Rect`] - Rectangle geometry
414///
415/// ## Widgets
416/// All 85+ widgets and their constructors are included.
417/// See [`widget`] module documentation for the full list.
418///
419/// ## Testing
420/// - [`testing::Pilot`], [`testing::TestApp`], [`testing::TestConfig`] - Testing utilities
421///
422/// ## Workers
423/// - [`worker::WorkerPool`], [`worker::WorkerHandle`] - Background tasks
424pub mod prelude {
425 // Macros
426 pub use crate::Store;
427
428 // App
429 pub use crate::app::App;
430
431 // Events
432 pub use crate::event::{Event, Key, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
433
434 // Layout
435 pub use crate::layout::Rect;
436
437 // Reactive primitives
438 pub use crate::reactive::{computed, effect, signal, Computed, Signal};
439
440 // Store support
441 pub use crate::reactive::{create_store, use_store, Store, StoreExt, StoreRegistry};
442
443 // Async support
444 pub use crate::reactive::{
445 use_async, use_async_immediate, use_async_poll, AsyncResult, AsyncState,
446 };
447
448 // Style
449 pub use crate::style::Color;
450
451 // Theme system
452 pub use crate::style::{
453 cycle_theme, register_theme, set_theme, set_theme_by_id, theme_ids, toggle_theme,
454 use_theme, Theme, ThemeVariant, Themes,
455 };
456
457 // Animation system
458 pub use crate::style::{
459 easing,
460 effective_duration,
461 // Reduced motion support
462 should_skip_animation,
463 // Widget animation presets
464 widget_animations,
465 Animation,
466 AnimationDirection,
467 AnimationFillMode,
468 AnimationGroup,
469 // Core animation types
470 AnimationState,
471 Animations,
472 Choreographer,
473 CssKeyframe,
474 GroupMode,
475 // CSS @keyframes style
476 KeyframeAnimation,
477 // Choreography
478 Stagger,
479 Tween,
480 };
481
482 // Widgets - Types
483 pub use crate::widget::{
484 Alignment,
485 Anchor,
486 Avatar,
487 AvatarShape,
488 AvatarSize,
489 Badge,
490 BadgeShape,
491 BadgeVariant,
492 BarChart,
493 BarOrientation,
494 Border,
495 BorderType,
496 // Braille canvas
497 BrailleCanvas,
498 BrailleContext,
499 BrailleGrid,
500 // New widgets
501 Button,
502 ButtonVariant,
503 Canvas,
504 Checkbox,
505 CheckboxStyle,
506 Circle,
507 Column,
508 Command,
509 // Command palette
510 CommandPalette,
511 Direction,
512 // Convenience widgets
513 Divider,
514 DividerStyle,
515 DrawContext,
516 EventResult,
517 FilledCircle,
518 FilledRectangle,
519 FocusStyle,
520 Input,
521 Interactive,
522 // Layer system
523 Layers,
524 Line,
525 List,
526 Modal,
527 ModalButton,
528 ModalButtonStyle,
529 Orientation,
530 Pagination,
531 PaginationStyle,
532 Points,
533 Positioned,
534 Progress,
535 ProgressStyle,
536 RadioGroup,
537 RadioLayout,
538 RadioStyle,
539 Rectangle,
540 RenderContext,
541 ScrollView,
542 Select,
543 Shape,
544 Skeleton,
545 SkeletonShape,
546 Sparkline,
547 SparklineStyle,
548 Spinner,
549 SpinnerStyle,
550 Stack,
551 Tab,
552 Table,
553 Tabs,
554 Tag,
555 TagStyle,
556 Text,
557 TextArea,
558 ThemePicker,
559 Timeout,
560 // UX widgets
561 Toast,
562 ToastLevel,
563 ToastPosition,
564 Tree,
565 TreeNode,
566 View,
567 WidgetState,
568 };
569
570 // Feature-gated widget types
571 #[cfg(feature = "image")]
572 pub use crate::widget::{Image, ScaleMode};
573 #[cfg(feature = "markdown")]
574 pub use crate::widget::{Markdown, MarkdownPresentation, ViewMode};
575
576 // Widgets - Constructors
577 pub use crate::widget::{
578 avatar,
579 avatar_icon,
580 badge,
581 barchart,
582 border,
583 braille_canvas,
584 // New constructors
585 button,
586 canvas,
587 checkbox,
588 chip,
589 column,
590 // Command palette
591 command_palette,
592 // Convenience widget constructors
593 divider,
594 dot_badge,
595 hstack,
596 input,
597 // Layer system constructors
598 layers,
599 list,
600 modal,
601 pagination,
602 positioned,
603 progress,
604 radio_group,
605 scroll_view,
606 select,
607 skeleton,
608 skeleton_avatar,
609 skeleton_paragraph,
610 skeleton_text,
611 sparkline,
612 spinner,
613 table,
614 tabs,
615 tag,
616 text,
617 textarea,
618 theme_picker,
619 // UX constructors
620 toast,
621 tree,
622 tree_node,
623 vdivider,
624 vstack,
625 };
626
627 // Feature-gated widget constructors
628 #[cfg(feature = "image")]
629 pub use crate::widget::image_from_file;
630 #[cfg(feature = "markdown")]
631 pub use crate::widget::{markdown, markdown_presentation};
632
633 // DOM system
634 pub use crate::dom::{DomId, DomNode, DomRenderer, DomTree, NodeState, Query, WidgetMeta};
635
636 // Worker system
637 pub use crate::worker::{
638 run_blocking, spawn as spawn_worker, WorkerChannel, WorkerHandle, WorkerMessage,
639 WorkerPool, WorkerState,
640 };
641
642 // Tasks - Timer, TaskRunner, EventBus
643 pub use crate::tasks::{
644 EventBus, EventId, Subscription, TaskId, TaskResult, TaskRunner, Timer, TimerEntry, TimerId,
645 };
646
647 // Patterns - Common TUI patterns
648 pub use crate::patterns::{
649 build_color,
650 priority_color,
651 spinner_char,
652 status_color,
653 // Async operations
654 AsyncTask,
655 BreadcrumbItem,
656 ConfirmAction,
657 ConfirmState,
658 FieldType,
659 FormField,
660 // Form validation
661 FormState,
662 // State management
663 MessageState,
664 NavigationEvent,
665 // Navigation
666 NavigationState,
667 Route,
668 SearchMode,
669 // Search/filter
670 SearchState,
671 ValidationError,
672 Validators,
673 BG,
674 BG_INSET,
675 BG_SUBTLE,
676 BLUE,
677 BORDER,
678 BORDER_MUTED,
679 // Colors
680 CYAN,
681 ERROR,
682 FG,
683 FG_DIM,
684 FG_SUBTLE,
685 GREEN,
686 INFO,
687 ORANGE,
688 PURPLE,
689 RED,
690 SPINNER_FRAMES,
691 SUCCESS,
692 WARNING,
693 YELLOW,
694 };
695
696 // Config loading (requires config feature)
697 #[cfg(feature = "config")]
698 pub use crate::patterns::{AppConfig, ConfigError};
699
700 // Accessibility
701 pub use crate::utils::{
702 // Announcement functions
703 announce,
704 // Widget-specific announcement helpers
705 announce_button_clicked,
706 announce_checkbox_changed,
707 announce_dialog_closed,
708 announce_dialog_opened,
709 announce_error,
710 announce_list_selection,
711 announce_now,
712 announce_success,
713 announce_tab_changed,
714 has_announcements,
715 is_high_contrast,
716 // Preference getters/setters
717 prefers_reduced_motion,
718 set_high_contrast,
719 set_reduced_motion,
720 take_announcements,
721 };
722
723 // Testing (Pilot)
724 pub use crate::testing::{Pilot, TestApp, TestConfig};
725 // Visual regression testing
726 pub use crate::testing::{
727 CapturedCell, CiEnvironment, CiProvider, TestReport, VisualCapture, VisualDiff, VisualTest,
728 VisualTestConfig, VisualTestResult,
729 };
730
731 // DevTools
732 #[allow(deprecated)] // Re-exporting deprecated functions for backwards compatibility
733 pub use crate::devtools::{
734 disable_devtools, enable_devtools, is_devtools_enabled, toggle_devtools, ComputedProperty,
735 DevTools, DevToolsConfig, DevToolsPosition, DevToolsTab, EventFilter, EventLogger,
736 EventType, Inspector, InspectorConfig, LoggedEvent, PropertySource, StateDebugger,
737 StateEntry, StateValue, StyleCategory, StyleInspector, WidgetNode,
738 };
739
740 // Profiler
741 pub use crate::utils::profiler::{
742 profile, profiler_report, start_profile, FlameNode, ProfileGuard, Profiler, Stats, Timing,
743 };
744
745 // Result type
746 pub use crate::Result;
747
748 // Constants
749 pub use crate::constants::{
750 // Animation durations
751 ANIMATION_DEFAULT_DURATION,
752 ANIMATION_FAST_DURATION,
753 ANIMATION_SLOW_DURATION,
754 ANIMATION_VERY_SLOW_DURATION,
755 // Debounce
756 DEBOUNCE_DEFAULT,
757 DEBOUNCE_FILE_SYSTEM,
758 DEBOUNCE_SEARCH,
759 FRAME_DURATION_30FPS,
760 // Frame rates
761 FRAME_DURATION_60FPS,
762 // Messages
763 MESSAGE_DEFAULT_DURATION,
764 MESSAGE_LONG_DURATION,
765 MESSAGE_QUICK_DURATION,
766 POLL_IMMEDIATE,
767 // Screen transitions
768 SCREEN_TRANSITION_DURATION,
769 // Stagger
770 STAGGER_DELAY_DEFAULT,
771 // Tick rates
772 TICK_RATE_DEFAULT,
773 };
774}