Skip to main content

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}