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}