use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use crate::cmd::Cmd;
use crate::components::Theme;
use crate::hooks::context::{HookContext, HookStorage};
use crate::hooks::paste::PasteEvent;
use crate::hooks::use_focus::FocusManager;
use crate::hooks::use_input::Key;
use crate::hooks::use_mouse::Mouse;
use crate::renderer::{IntoPrintable, RenderHandle, SharedFrameRateStats};
pub type InputHandlerFn = Rc<dyn Fn(&str, &Key)>;
pub type MouseHandlerFn = Rc<dyn Fn(&Mouse)>;
pub type PasteHandlerFn = Rc<dyn Fn(&PasteEvent)>;
pub struct RuntimeContext {
hook_context: Rc<RefCell<HookContext>>,
input_handlers: Vec<InputHandlerFn>,
mouse_handlers: Vec<MouseHandlerFn>,
mouse_enabled: bool,
focus_manager: FocusManager,
exit_flag: Arc<AtomicBool>,
render_handle: Option<RenderHandle>,
screen_reader_enabled: bool,
screen_reader_initialized: bool,
paste_handlers: Vec<PasteHandlerFn>,
last_activity: Instant,
measurements: std::collections::HashMap<crate::core::ElementId, (u16, u16)>,
measurements_by_key: std::collections::HashMap<String, (u16, u16)>,
frame_rate_stats: Option<Arc<SharedFrameRateStats>>,
theme: Theme,
}
impl RuntimeContext {
pub fn new() -> Self {
Self {
hook_context: Rc::new(RefCell::new(HookContext::new())),
input_handlers: Vec::new(),
mouse_handlers: Vec::new(),
mouse_enabled: false,
focus_manager: FocusManager::new(),
exit_flag: Arc::new(AtomicBool::new(false)),
render_handle: None,
screen_reader_enabled: false,
screen_reader_initialized: false,
paste_handlers: Vec::new(),
last_activity: Instant::now(),
measurements: std::collections::HashMap::new(),
measurements_by_key: std::collections::HashMap::new(),
frame_rate_stats: None,
theme: Theme::dark(),
}
}
pub fn with_app_control(exit_flag: Arc<AtomicBool>, render_handle: RenderHandle) -> Self {
Self {
hook_context: Rc::new(RefCell::new(HookContext::new())),
input_handlers: Vec::new(),
mouse_handlers: Vec::new(),
mouse_enabled: false,
focus_manager: FocusManager::new(),
exit_flag,
render_handle: Some(render_handle),
screen_reader_enabled: false,
screen_reader_initialized: false,
paste_handlers: Vec::new(),
last_activity: Instant::now(),
measurements: std::collections::HashMap::new(),
measurements_by_key: std::collections::HashMap::new(),
frame_rate_stats: None,
theme: Theme::dark(),
}
}
pub fn prepare_render(&mut self) {
self.input_handlers.clear();
self.mouse_handlers.clear();
self.paste_handlers.clear();
self.mouse_enabled = false;
}
pub fn begin_render(&mut self) {
self.prepare_render();
self.hook_context.borrow_mut().begin_render();
}
pub fn end_render(&mut self) {
self.hook_context.borrow_mut().end_render();
}
pub fn run_effects(&mut self) {
self.hook_context.borrow_mut().run_effects();
}
pub fn use_hook<T: Clone + Send + Sync + 'static, F: FnOnce() -> T>(
&mut self,
init: F,
) -> HookStorage {
self.hook_context.borrow_mut().use_hook(init)
}
pub fn queue_cmd(&mut self, cmd: Cmd) {
self.hook_context.borrow_mut().queue_cmd(cmd);
}
pub fn take_cmds(&mut self) -> Vec<Cmd> {
self.hook_context.borrow_mut().take_cmds()
}
pub fn set_render_callback(&mut self, callback: crate::hooks::context::RenderCallback) {
self.hook_context.borrow_mut().set_render_callback(callback);
}
pub fn request_render(&self) {
self.hook_context.borrow().request_render();
if let Some(handle) = &self.render_handle {
handle.request_render();
}
}
pub fn hook_context(&self) -> Rc<RefCell<HookContext>> {
self.hook_context.clone()
}
pub fn register_input_handler<F>(&mut self, handler: F)
where
F: Fn(&str, &Key) + 'static,
{
self.input_handlers.push(Rc::new(handler));
}
pub fn dispatch_input(&self, input: &str, key: &Key) {
for handler in &self.input_handlers {
handler(input, key);
}
}
pub fn input_handler_count(&self) -> usize {
self.input_handlers.len()
}
pub fn register_mouse_handler<F>(&mut self, handler: F)
where
F: Fn(&Mouse) + 'static,
{
self.mouse_handlers.push(Rc::new(handler));
self.mouse_enabled = true;
}
pub fn dispatch_mouse(&self, mouse: &Mouse) {
for handler in &self.mouse_handlers {
handler(mouse);
}
}
pub fn is_mouse_enabled(&self) -> bool {
self.mouse_enabled
}
pub fn set_mouse_enabled(&mut self, enabled: bool) {
self.mouse_enabled = enabled;
}
pub fn register_paste_handler<F>(&mut self, handler: F)
where
F: Fn(&PasteEvent) + 'static,
{
self.paste_handlers.push(Rc::new(handler));
}
pub fn dispatch_paste(&self, event: &PasteEvent) {
for handler in &self.paste_handlers {
handler(event);
}
}
pub fn paste_handler_count(&self) -> usize {
self.paste_handlers.len()
}
pub fn record_activity(&mut self) {
self.last_activity = Instant::now();
}
pub fn idle_duration(&self) -> Duration {
self.last_activity.elapsed()
}
pub fn focus_manager_mut(&mut self) -> &mut FocusManager {
&mut self.focus_manager
}
pub fn focus_manager(&self) -> &FocusManager {
&self.focus_manager
}
pub fn exit_flag(&self) -> Arc<AtomicBool> {
self.exit_flag.clone()
}
pub fn exit(&self) {
self.exit_flag.store(true, Ordering::SeqCst);
}
pub fn should_exit(&self) -> bool {
self.exit_flag.load(Ordering::SeqCst)
}
pub fn render_handle(&self) -> Option<&RenderHandle> {
self.render_handle.as_ref()
}
pub fn println(&self, message: impl IntoPrintable) {
if let Some(handle) = &self.render_handle {
handle.println(message);
}
}
pub fn enter_alt_screen(&self) {
if let Some(handle) = &self.render_handle {
handle.enter_alt_screen();
}
}
pub fn exit_alt_screen(&self) {
if let Some(handle) = &self.render_handle {
handle.exit_alt_screen();
}
}
pub fn is_alt_screen(&self) -> bool {
self.render_handle
.as_ref()
.map(|h| h.is_alt_screen())
.unwrap_or(false)
}
pub fn is_screen_reader_enabled(&self) -> bool {
self.screen_reader_enabled
}
pub fn is_screen_reader_initialized(&self) -> bool {
self.screen_reader_initialized
}
pub fn set_screen_reader_enabled(&mut self, enabled: bool) {
self.screen_reader_enabled = enabled;
self.screen_reader_initialized = true;
}
pub fn set_measurement(&mut self, element_id: crate::core::ElementId, width: u16, height: u16) {
self.measurements.insert(element_id, (width, height));
}
pub fn get_measurement(&self, element_id: crate::core::ElementId) -> Option<(u16, u16)> {
self.measurements.get(&element_id).copied()
}
pub fn set_measure_layouts(
&mut self,
layouts: std::collections::HashMap<crate::core::ElementId, crate::layout::Layout>,
) {
self.measurements.clear();
self.measurements_by_key.clear();
for (id, layout) in layouts {
self.measurements
.insert(id, (layout.width as u16, layout.height as u16));
}
}
pub fn set_measure_layouts_with_keys(
&mut self,
layouts: std::collections::HashMap<crate::core::ElementId, crate::layout::Layout>,
keyed_layouts: std::collections::HashMap<String, crate::layout::Layout>,
) {
self.measurements.clear();
self.measurements_by_key.clear();
for (id, layout) in layouts {
self.measurements
.insert(id, (layout.width as u16, layout.height as u16));
}
for (key, layout) in keyed_layouts {
self.measurements_by_key
.insert(key, (layout.width as u16, layout.height as u16));
}
}
pub fn get_measurement_dims(&self, element_id: crate::core::ElementId) -> Option<(f32, f32)> {
self.measurements
.get(&element_id)
.map(|&(w, h)| (w as f32, h as f32))
}
pub fn get_measurement_by_key_dims(&self, key: &str) -> Option<(f32, f32)> {
self.measurements_by_key
.get(key)
.map(|&(w, h)| (w as f32, h as f32))
}
pub fn set_frame_rate_stats(&mut self, stats: Option<Arc<SharedFrameRateStats>>) {
self.frame_rate_stats = stats;
}
pub fn frame_rate_stats(&self) -> Option<&Arc<SharedFrameRateStats>> {
self.frame_rate_stats.as_ref()
}
pub fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
}
pub fn theme(&self) -> Theme {
self.theme.clone()
}
}
impl Default for RuntimeContext {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static CURRENT_RUNTIME: RefCell<Option<Rc<RefCell<RuntimeContext>>>> = const { RefCell::new(None) };
}
pub fn current_runtime() -> Option<Rc<RefCell<RuntimeContext>>> {
CURRENT_RUNTIME.with(|ctx| ctx.borrow().clone())
}
pub fn set_current_runtime(ctx: Option<Rc<RefCell<RuntimeContext>>>) {
CURRENT_RUNTIME.with(|current| {
*current.borrow_mut() = ctx;
});
}
pub fn with_runtime<F, R>(ctx: Rc<RefCell<RuntimeContext>>, f: F) -> R
where
F: FnOnce() -> R,
{
let prev = current_runtime();
set_current_runtime(Some(ctx.clone()));
struct RuntimeGuard {
prev: Option<Rc<RefCell<RuntimeContext>>>,
}
impl Drop for RuntimeGuard {
fn drop(&mut self) {
set_current_runtime(self.prev.take());
}
}
let guard = RuntimeGuard { prev };
ctx.borrow_mut().prepare_render();
let hook_context = ctx.borrow().hook_context();
let result = crate::hooks::context::with_hooks(hook_context, f);
drop(guard);
result
}
pub fn with_current_runtime<F, R>(f: F) -> Option<R>
where
F: FnOnce(&mut RuntimeContext) -> R,
{
current_runtime().map(|ctx| f(&mut ctx.borrow_mut()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runtime_context_creation() {
let ctx = RuntimeContext::new();
assert!(!ctx.should_exit());
assert!(!ctx.is_mouse_enabled());
assert!(!ctx.is_screen_reader_enabled());
}
#[test]
fn test_runtime_context_exit() {
let ctx = RuntimeContext::new();
assert!(!ctx.should_exit());
ctx.exit();
assert!(ctx.should_exit());
}
#[test]
fn test_runtime_context_input_handlers() {
let mut ctx = RuntimeContext::new();
assert_eq!(ctx.input_handler_count(), 0);
ctx.register_input_handler(|_, _| {});
assert_eq!(ctx.input_handler_count(), 1);
ctx.register_input_handler(|_, _| {});
assert_eq!(ctx.input_handler_count(), 2);
}
#[test]
fn test_runtime_context_mouse_enabled() {
let mut ctx = RuntimeContext::new();
assert!(!ctx.is_mouse_enabled());
ctx.register_mouse_handler(|_| {});
assert!(ctx.is_mouse_enabled());
}
#[test]
fn test_runtime_context_measurements() {
use crate::core::ElementId;
let mut ctx = RuntimeContext::new();
let id = ElementId::new();
assert!(ctx.get_measurement(id).is_none());
ctx.set_measurement(id, 80, 24);
assert_eq!(ctx.get_measurement(id), Some((80, 24)));
}
#[test]
fn test_runtime_context_measurements_by_key() {
use crate::core::ElementId;
use crate::layout::Layout;
use std::collections::HashMap;
let mut ctx = RuntimeContext::new();
let id = ElementId::new();
let mut by_id = HashMap::new();
by_id.insert(
id,
Layout {
x: 0.0,
y: 0.0,
width: 42.0,
height: 9.0,
},
);
let mut by_key = HashMap::new();
by_key.insert(
"main-panel".to_string(),
Layout {
x: 0.0,
y: 0.0,
width: 42.0,
height: 9.0,
},
);
ctx.set_measure_layouts_with_keys(by_id, by_key);
assert_eq!(ctx.get_measurement(id), Some((42, 9)));
assert_eq!(
ctx.get_measurement_by_key_dims("main-panel"),
Some((42.0, 9.0))
);
}
#[test]
fn test_with_runtime() {
let ctx = Rc::new(RefCell::new(RuntimeContext::new()));
let result = with_runtime(ctx.clone(), || {
let runtime = current_runtime().unwrap();
runtime.borrow_mut().register_input_handler(|_, _| {});
runtime.borrow().input_handler_count()
});
assert_eq!(result, 1);
assert!(current_runtime().is_none());
}
#[test]
fn test_hook_state_persistence() {
let ctx = Rc::new(RefCell::new(RuntimeContext::new()));
with_runtime(ctx.clone(), || {
let runtime = current_runtime().unwrap();
let hook = runtime.borrow_mut().use_hook(|| 42i32);
assert_eq!(hook.get::<i32>(), Some(42));
hook.set(100i32);
});
with_runtime(ctx.clone(), || {
let runtime = current_runtime().unwrap();
let hook = runtime.borrow_mut().use_hook(|| 0i32); assert_eq!(hook.get::<i32>(), Some(100));
});
}
#[test]
fn test_handlers_cleared_on_render() {
let ctx = Rc::new(RefCell::new(RuntimeContext::new()));
with_runtime(ctx.clone(), || {
let runtime = current_runtime().unwrap();
runtime.borrow_mut().register_input_handler(|_, _| {});
runtime.borrow_mut().register_input_handler(|_, _| {});
assert_eq!(runtime.borrow().input_handler_count(), 2);
});
with_runtime(ctx.clone(), || {
let runtime = current_runtime().unwrap();
assert_eq!(runtime.borrow().input_handler_count(), 0);
runtime.borrow_mut().register_input_handler(|_, _| {});
assert_eq!(runtime.borrow().input_handler_count(), 1);
});
}
}