reratui_runtime/lib.rs
1//! Runtime and event loop for Reratui TUI framework
2//!
3//! This module provides the core runtime functionality for Reratui applications,
4//! including terminal management, event handling, and the render loop.
5
6mod exit;
7mod managed_terminal;
8
9pub use exit::{request_exit, reset_exit, should_exit};
10pub use managed_terminal::{ManagedTerminal, restore_terminal, setup_terminal};
11
12use anyhow::Result;
13use crossterm::event::Event;
14use reratui_core::Element;
15use reratui_hooks::frame::FrameContext;
16use reratui_hooks::hook_context::HookContext;
17use std::{
18 rc::Rc,
19 time::{Duration, Instant},
20};
21
22/// Renders a component-based TUI application with hooks support
23///
24/// This function sets up a hook context and manages the component lifecycle
25/// including state persistence between renders.
26///
27/// # Arguments
28/// * `app_fn` - A closure that returns an Element (supports both components and RSX)
29///
30/// # Example
31/// ```no_run,ignore
32/// use reratui::prelude::*;
33///
34/// #[component]
35/// fn Counter() -> Element {
36/// let (count, set_count) = use_state(|| 0);
37/// rsx! { <Text text={format!("Count: {}", count)} /> }
38/// }
39///
40/// # async fn example() {
41/// // Direct component
42/// render(|| Counter()).await.unwrap();
43///
44/// // Or with RSX
45/// render(|| {
46/// rsx! { <Counter /> }
47/// }).await.unwrap();
48/// # }
49/// ```
50pub async fn render<F>(initializer: F) -> Result<()>
51where
52 F: Fn() -> Element + 'static,
53{
54 // Initialize panic handler
55 reratui_panic::setup_panic_handler();
56
57 // Initialize terminal backend
58 let mut terminal = setup_terminal()?;
59
60 // Create a new hook context for this component tree
61 let hook_context = Rc::new(HookContext::new());
62
63 // Set the hook context for this thread
64 reratui_hooks::hook_context::set_hook_context(hook_context.clone());
65
66 // Create the element
67 let element = initializer();
68
69 // Frame tracking
70 let mut frame_count: u64 = 0;
71 let mut last_frame_time = Instant::now();
72
73 // Create async event stream
74 use crossterm::event::EventStream;
75 use tokio_stream::StreamExt;
76 let mut events = EventStream::new();
77
78 // Main render loop with continuous rendering
79 loop {
80 // Calculate frame timing
81 let current_time = Instant::now();
82 let delta = current_time.duration_since(last_frame_time);
83 last_frame_time = current_time;
84
85 // Reset hook index before each render
86 hook_context.reset_hook_index();
87
88 // Poll for events with timeout (allows continuous rendering)
89 let timeout = tokio::time::sleep(Duration::from_millis(16));
90 tokio::pin!(timeout);
91
92 tokio::select! {
93 Some(Ok(event)) = events.next() => {
94 // Process key events through global event system
95 let processed = if let Event::Key(key_event) = &event {
96 reratui_hooks::event::global_events::process_global_event(key_event)
97 } else {
98 false
99 };
100
101 // If not processed as a global event, make it available to components
102 if !processed {
103 reratui_hooks::event::set_current_event(Some(std::sync::Arc::new(event)));
104 }
105 }
106 _ = &mut timeout => {
107 // Timeout - clear event and continue rendering
108 reratui_hooks::event::set_current_event(None);
109 }
110 }
111
112 // Check for exit
113 if should_exit() {
114 break;
115 }
116
117 // Render the element
118 terminal.draw(|frame| {
119 // SAFETY: The FrameContext is only used within this render scope
120 // and the frame pointer remains valid for the duration of the draw call
121 let frame_ctx = unsafe { FrameContext::new(frame, frame_count, delta, current_time) };
122
123 // Provide frame context for components
124 let _frame_context = reratui_hooks::context::use_context_provider(|| frame_ctx);
125
126 let area = frame.area();
127 element.render(area, frame.buffer_mut());
128 })?;
129
130 // Clean up unmounted components after render
131 reratui_core::component::cleanup_unmounted();
132
133 // Increment frame counter
134 frame_count += 1;
135 }
136
137 // Clear the current event
138 reratui_hooks::event::set_current_event(None);
139
140 // Clean up the hook context
141 reratui_hooks::hook_context::clear_hook_context();
142
143 // Restore terminal state
144 restore_terminal()?;
145
146 Ok(())
147}