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 terminal;
8
9pub use exit::{request_exit, reset_exit, should_exit};
10pub use terminal::{ManagedTerminal, restore_terminal, setup_terminal};
11
12use anyhow::Result;
13use crossterm::event::{self, 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    // Main render loop
74    let mut running = true;
75    while running {
76        // Calculate frame timing
77        let current_time = Instant::now();
78        let delta = current_time.duration_since(last_frame_time);
79        last_frame_time = current_time;
80
81        // Reset hook index before each render
82        hook_context.reset_hook_index();
83
84        // Handle events with a small timeout to prevent blocking
85        if event::poll(Duration::from_millis(16))? {
86            if let Ok(event) = event::read() {
87                // Process key events through global event system
88                let processed = if let Event::Key(key_event) = &event {
89                    // First try to process as a global event
90                    reratui_hooks::event::global_events::process_global_event(key_event)
91                } else {
92                    false
93                };
94
95                // If not processed as a global event, make it available to components
96                // This includes mouse events, resize events, etc.
97                if !processed {
98                    reratui_hooks::event::set_current_event(Some(std::sync::Arc::new(event)));
99
100                    // Check for exit after component event handling
101                    if should_exit() {
102                        running = false;
103                    }
104                }
105            }
106        } else {
107            // No events, clear the current event
108            reratui_hooks::event::set_current_event(None);
109        }
110
111        // Check for exit
112        if should_exit() {
113            running = false;
114        }
115
116        // Render the element
117        terminal.draw(|frame| {
118            // SAFETY: The FrameContext is only used within this render scope
119            // and the frame pointer remains valid for the duration of the draw call
120            let frame_ctx = unsafe { FrameContext::new(frame, frame_count, delta, current_time) };
121
122            // Provide frame context for components
123            let _frame_context = reratui_hooks::context::use_context_provider(|| frame_ctx);
124
125            let area = frame.area();
126            element.render(area, frame.buffer_mut());
127        })?;
128
129        // Clean up unmounted components after render
130        reratui_core::component::cleanup_unmounted();
131
132        // Increment frame counter
133        frame_count += 1;
134
135        // Small delay to prevent high CPU usage (~60 FPS)
136        tokio::time::sleep(Duration::from_millis(16)).await;
137    }
138
139    // Clear the current event
140    reratui_hooks::event::set_current_event(None);
141
142    // Clean up the hook context
143    reratui_hooks::hook_context::clear_hook_context();
144
145    // Restore terminal state
146    restore_terminal()?;
147
148    Ok(())
149}