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//! | [`core::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
237// Core modules
238pub mod core;
239pub use core::constants; // Re-export from core
240
241// Runtime systems
242pub mod runtime;
243pub use runtime::{dom, event, layout, render, style};
244
245// State management
246pub mod state;
247pub use state::{patterns, plugin, reactive, tasks, worker};
248
249// Other modules (keep at root for now)
250pub mod a11y;
251pub mod devtools;
252pub mod query;
253pub mod testing;
254pub mod text;
255pub mod utils;
256pub mod widget;
257
258// Re-export derive macros
259pub use revue_macros::Store;
260
261/// Error type for Revue operations.
262///
263/// This enum covers all error cases that can occur when using Revue,
264/// including CSS parsing errors, I/O errors, and general runtime errors.
265///
266/// # Example
267///
268/// ```rust,ignore
269/// use revue::{Error, Result};
270///
271/// fn load_styles() -> Result<()> {
272/// // Operations that might fail...
273/// Ok(())
274/// }
275/// ```
276#[derive(Debug, thiserror::Error)]
277pub enum Error {
278 /// CSS parsing error.
279 ///
280 /// Occurs when parsing invalid CSS syntax or unsupported properties.
281 #[error("CSS error: {0}")]
282 Css(#[from] style::ParseError),
283
284 /// I/O error.
285 ///
286 /// Occurs during file operations (loading CSS, images, etc.).
287 #[error("I/O error: {0}")]
288 Io(#[from] std::io::Error),
289
290 /// Layout error.
291 ///
292 /// Occurs during layout computation.
293 #[error("Layout error: {0}")]
294 Layout(#[from] layout::LayoutError),
295
296 /// Rendering error.
297 ///
298 /// Occurs during buffer operations or rendering.
299 #[error("Render error: {0}")]
300 Render(String),
301
302 /// Generic error with custom message.
303 ///
304 /// Used for errors that don't fit other categories.
305 /// This variant preserves the underlying error source for better debugging.
306 #[error("Unexpected error: {0}")]
307 Other(#[from] anyhow::Error),
308}
309
310/// Result type alias for Revue operations.
311///
312/// Shorthand for `std::result::Result<T, revue::Error>`.
313///
314/// # Example
315///
316/// ```rust,ignore
317/// use revue::Result;
318///
319/// fn load_config() -> Result<String> {
320/// let content = std::fs::read_to_string("config.css")?;
321/// Ok(content)
322/// }
323/// ```
324pub type Result<T> = std::result::Result<T, Error>;
325
326// ─────────────────────────────────────────────────────────────────────────
327// Error Handling Guidelines
328// ─────────────────────────────────────────────────────────────────────────
329
330/// # Error Handling Guidelines
331///
332/// Revue follows these error handling patterns to provide clear, actionable error messages.
333///
334/// ## Public APIs
335///
336/// **Always return `Result<T>`** for public APIs that can fail:
337///
338/// ```rust,ignore
339/// use revue::Result;
340///
341/// pub fn parse_css(input: &str) -> Result<Stylesheet> {
342/// // Parse and return Ok(stylesheet) or Err(Error)
343/// }
344/// ```
345///
346/// ## Internal Code
347///
348/// Choose the appropriate error handling strategy:
349///
350/// | Situation | Use | Example |
351/// |-----------|-----|---------|
352/// | Recoverable error | `Result<T, E>` | File not found, invalid input |
353/// | Optional value | `Option<T>` | Missing config, lookup by ID |
354/// | Truly invariant | `expect(msg)` | Logic errors, "never happens" |
355/// | Never fails | `unwrap()` // With comment explaining why | Static constants, known-good values |
356///
357/// ## Error Type Hierarchy
358///
359/// Revue uses `thiserror` for error definitions:
360///
361/// ```rust,ignore
362/// #[derive(Debug, thiserror::Error)]
363/// pub enum Error {
364/// #[error("CSS error: {0}")]
365/// Css(#[from] style::ParseError),
366///
367/// #[error("I/O error: {0}")]
368/// Io(#[from] std::io::Error),
369///
370/// #[error("Layout error: {0}")]
371/// Layout(#[from] layout::LayoutError),
372///
373/// #[error("Render error: {0}")]
374/// Render(String),
375///
376/// #[error("Unexpected error: {0}")]
377/// Other(#[from] anyhow::Error),
378/// }
379/// ```
380///
381/// ## Error Context
382///
383/// **Good: Use anyhow for context**
384///
385/// ```rust,ignore
386/// use anyhow::Context;
387///
388/// let file = std::fs::read_to_string(path)
389/// .context("Failed to read config")?;
390/// ```
391///
392/// **Bad: Lose context**
393///
394/// ```rust,ignore
395/// let file = std::fs::read_to_string(path)?; // Error doesn't mention config
396/// ```
397///
398/// ## Converting Errors
399///
400/// Use `?` operator for automatic conversion:
401///
402/// ```rust,ignore
403/// use revue::Result;
404///
405/// fn load_stylesheet(path: &str) -> Result<Stylesheet> {
406/// let content = std::fs::read_to_string(path)?; // io::Error → Error::Io
407/// Ok(parse_css(&content)?)
408/// }
409/// ```
410///
411/// Prelude module for convenient imports.
412///
413/// Import everything you need with a single line:
414///
415/// ```rust,ignore
416/// use revue::prelude::*;
417/// ```
418///
419/// # Included Items
420///
421/// ## Core Types
422/// - [`core::app::App`] - Application builder and runner
423/// - [`widget::View`], [`widget::RenderContext`] - Widget rendering trait
424/// - [`Result`] - Error handling
425///
426/// ## Events
427/// - [`event::Key`], [`event::KeyEvent`], [`event::Event`] - Input handling
428///
429/// ## Reactive
430/// - [`reactive::signal`], [`reactive::computed`], [`reactive::effect`] - State primitives
431/// - [`reactive::Signal`], [`reactive::Computed`] - Reactive types
432///
433/// ## Layout
434/// - [`widget::vstack`], [`widget::hstack`] - Stack layouts
435/// - [`layout::Rect`] - Rectangle geometry
436///
437/// ## Widgets
438/// All 85+ widgets and their constructors are included.
439/// See [`widget`] module documentation for the full list.
440///
441/// ## Testing
442/// - [`testing::Pilot`], [`testing::TestApp`], [`testing::TestConfig`] - Testing utilities
443///
444/// ## Workers
445/// - [`worker::WorkerPool`], [`worker::WorkerHandle`] - Background tasks
446pub mod prelude {
447 // Macros
448 pub use crate::Store;
449
450 // App
451 pub use crate::core::app::App;
452
453 // Events
454 pub use crate::event::{Event, Key, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
455
456 // Layout
457 pub use crate::layout::Rect;
458
459 // Reactive primitives
460 pub use crate::reactive::{computed, effect, signal, Computed, Signal};
461
462 // Store support
463 pub use crate::reactive::{create_store, use_store, Store, StoreExt, StoreRegistry};
464
465 // Async support
466 pub use crate::reactive::{
467 use_async, use_async_immediate, use_async_poll, AsyncResult, AsyncState,
468 };
469
470 // Style
471 pub use crate::style::Color;
472
473 // Theme system
474 pub use crate::style::{
475 cycle_theme, register_theme, set_theme, set_theme_by_id, theme_ids, toggle_theme,
476 use_theme, Theme, ThemeVariant, Themes,
477 };
478
479 // Animation system
480 pub use crate::style::{
481 easing,
482 effective_duration,
483 // Reduced motion support
484 should_skip_animation,
485 // Widget animation presets
486 widget_animations,
487 Animation,
488 AnimationDirection,
489 AnimationFillMode,
490 AnimationGroup,
491 // Core animation types
492 AnimationState,
493 Animations,
494 Choreographer,
495 CssKeyframe,
496 GroupMode,
497 // CSS @keyframes style
498 KeyframeAnimation,
499 // CSS @keyframes parser types
500 KeyframeBlock,
501 KeyframesDefinition,
502 // Choreography
503 Stagger,
504 Tween,
505 };
506
507 // Widgets - Types
508 pub use crate::widget::{
509 Alignment,
510 Anchor,
511 Avatar,
512 AvatarShape,
513 AvatarSize,
514 Badge,
515 BadgeShape,
516 BadgeVariant,
517 BarChart,
518 BarOrientation,
519 BigText,
520 Border,
521 BorderType,
522 // Braille canvas
523 BrailleCanvas,
524 BrailleContext,
525 BrailleGrid,
526 // New widgets
527 Button,
528 ButtonVariant,
529 Canvas,
530 Checkbox,
531 CheckboxStyle,
532 Circle,
533 Column,
534 Command,
535 // Command palette
536 CommandPalette,
537 DigitStyle,
538 Digits,
539 Direction,
540 // Convenience widgets
541 Divider,
542 DividerStyle,
543 DrawContext,
544 EmptyState,
545 EmptyStateType,
546 EmptyStateVariant,
547 EventResult,
548 FilledCircle,
549 FilledRectangle,
550 FocusStyle,
551 Gauge,
552 GaugeStyle,
553 Input,
554 Interactive,
555 LabelPosition,
556 // Layer system
557 Layers,
558 Line,
559 List,
560 LogEntry,
561 LogFormat,
562 LogLevel,
563 Modal,
564 ModalButton,
565 ModalButtonStyle,
566 Orientation,
567 Pagination,
568 PaginationStyle,
569 Points,
570 Positioned,
571 Progress,
572 ProgressStyle,
573 RadioGroup,
574 RadioLayout,
575 RadioStyle,
576 Rectangle,
577 RenderContext,
578 RichLog,
579 RichText,
580 ScrollView,
581 Select,
582 Shape,
583 Skeleton,
584 SkeletonShape,
585 Span,
586 Sparkline,
587 SparklineStyle,
588 Spinner,
589 SpinnerStyle,
590 Stack,
591 Status,
592 StatusIndicator,
593 StatusSize,
594 StatusStyle,
595 Style,
596 Tab,
597 Table,
598 Tabs,
599 Tag,
600 TagStyle,
601 Text,
602 TextArea,
603 ThemePicker,
604 Timeout,
605 // UX widgets
606 Toast,
607 ToastLevel,
608 ToastPosition,
609 Tree,
610 TreeNode,
611 View,
612 WidgetState,
613 };
614
615 // Feature-gated widget types
616 #[cfg(feature = "image")]
617 pub use crate::widget::{Image, ScaleMode};
618 #[cfg(feature = "markdown")]
619 pub use crate::widget::{Markdown, MarkdownPresentation, ViewMode};
620
621 // Widgets - Constructors
622 pub use crate::widget::{
623 avatar,
624 avatar_icon,
625 away_indicator,
626 badge,
627 barchart,
628 battery,
629 bigtext,
630 border,
631 braille_canvas,
632 busy_indicator,
633 // New constructors
634 button,
635 canvas,
636 checkbox,
637 chip,
638 clock,
639 column,
640 // Command palette
641 command_palette,
642 digits,
643 // Convenience widget constructors
644 divider,
645 dot_badge,
646 empty_error,
647 empty_state,
648 first_use,
649 gauge,
650 h1,
651 h2,
652 h3,
653 hstack,
654 input,
655 // Layer system constructors
656 layers,
657 list,
658 log_entry,
659 markup,
660 modal,
661 no_results,
662 offline,
663 online,
664 pagination,
665 percentage,
666 positioned,
667 progress,
668 radio_group,
669 rich_text,
670 richlog,
671 scroll_view,
672 select,
673 skeleton,
674 skeleton_avatar,
675 skeleton_paragraph,
676 skeleton_text,
677 span,
678 sparkline,
679 spinner,
680 status_indicator,
681 style,
682 table,
683 tabs,
684 tag,
685 text,
686 textarea,
687 theme_picker,
688 timer_widget as timer,
689 // UX constructors
690 toast,
691 tree,
692 tree_node,
693 vdivider,
694 vstack,
695 };
696
697 // Feature-gated widget constructors
698 #[cfg(feature = "image")]
699 pub use crate::widget::image_from_file;
700 #[cfg(feature = "markdown")]
701 pub use crate::widget::{markdown, markdown_presentation};
702
703 // DOM system
704 pub use crate::dom::{DomId, DomNode, DomRenderer, DomTree, NodeState, Query, WidgetMeta};
705
706 // Render system
707 pub use crate::runtime::render::{Buffer, Modifier};
708
709 // Worker system
710 pub use crate::worker::{
711 run_blocking, spawn as spawn_worker, WorkerChannel, WorkerHandle, WorkerMessage,
712 WorkerPool, WorkerState,
713 };
714
715 // Tasks - Timer, TaskRunner, EventBus
716 pub use crate::tasks::{
717 EventBus, EventId, Subscription, TaskId, TaskResult, TaskRunner, Timer, TimerEntry, TimerId,
718 };
719
720 // Patterns - Common TUI patterns
721 pub use crate::patterns::{
722 build_color,
723 priority_color,
724 spinner_char,
725 status_color,
726 // Async operations
727 AsyncTask,
728 BreadcrumbItem,
729 ConfirmAction,
730 ConfirmState,
731 FieldType,
732 FormField,
733 // Form validation
734 FormState,
735 // State management
736 MessageState,
737 NavigationEvent,
738 // Navigation
739 NavigationState,
740 Route,
741 SearchMode,
742 // Search/filter
743 SearchState,
744 ValidationError,
745 Validators,
746 BG,
747 BG_INSET,
748 BG_SUBTLE,
749 BLUE,
750 BORDER,
751 BORDER_MUTED,
752 // Colors
753 CYAN,
754 ERROR,
755 FG,
756 FG_DIM,
757 FG_SUBTLE,
758 GREEN,
759 INFO,
760 ORANGE,
761 PURPLE,
762 RED,
763 SPINNER_FRAMES,
764 SUCCESS,
765 WARNING,
766 YELLOW,
767 };
768
769 // Config loading (requires config feature)
770 #[cfg(feature = "config")]
771 pub use crate::patterns::{AppConfig, ConfigError};
772
773 // Accessibility
774 pub use crate::utils::{
775 // Announcement functions
776 announce,
777 // Widget-specific announcement helpers
778 announce_button_clicked,
779 announce_checkbox_changed,
780 announce_dialog_closed,
781 announce_dialog_opened,
782 announce_error,
783 announce_list_selection,
784 announce_now,
785 announce_success,
786 announce_tab_changed,
787 has_announcements,
788 is_high_contrast,
789 // Preference getters/setters
790 prefers_reduced_motion,
791 set_high_contrast,
792 set_reduced_motion,
793 take_announcements,
794 };
795
796 // Figlet fonts (for BigText widget)
797 pub use crate::utils::figlet::FigletFont;
798
799 // Testing (Pilot)
800 pub use crate::testing::{Pilot, TestApp, TestConfig};
801 // Visual regression testing
802 pub use crate::testing::{
803 CapturedCell, CiEnvironment, CiProvider, TestReport, VisualCapture, VisualDiff, VisualTest,
804 VisualTestConfig, VisualTestResult,
805 };
806
807 // DevTools
808 #[allow(deprecated)] // Re-exporting deprecated functions for backwards compatibility
809 pub use crate::devtools::{
810 disable_devtools, enable_devtools, is_devtools_enabled, toggle_devtools, ComputedProperty,
811 DevTools, DevToolsConfig, DevToolsPosition, DevToolsTab, EventFilter, EventLogger,
812 EventType, Inspector, InspectorConfig, LoggedEvent, PropertySource, StateDebugger,
813 StateEntry, StateValue, StyleCategory, StyleInspector, WidgetNode,
814 };
815
816 // Profiler
817 pub use crate::utils::profiler::{
818 profile, profiler_report, start_profile, FlameNode, ProfileGuard, Profiler, Stats, Timing,
819 };
820
821 // Result type
822 pub use crate::Result;
823
824 // Constants
825 pub use crate::constants::{
826 // Animation durations
827 ANIMATION_DEFAULT_DURATION,
828 ANIMATION_FAST_DURATION,
829 ANIMATION_SLOW_DURATION,
830 ANIMATION_VERY_SLOW_DURATION,
831 // Debounce
832 DEBOUNCE_DEFAULT,
833 DEBOUNCE_FILE_SYSTEM,
834 DEBOUNCE_SEARCH,
835 FRAME_DURATION_30FPS,
836 // Frame rates
837 FRAME_DURATION_60FPS,
838 // Messages
839 MESSAGE_DEFAULT_DURATION,
840 MESSAGE_LONG_DURATION,
841 MESSAGE_QUICK_DURATION,
842 POLL_IMMEDIATE,
843 // Screen transitions
844 SCREEN_TRANSITION_DURATION,
845 // Stagger
846 STAGGER_DELAY_DEFAULT,
847 // Tick rates
848 TICK_RATE_DEFAULT,
849 };
850}
851
852// Tests extracted to tests/lib_tests.rs
853// All tests use only public constants, functions, and types from revue