use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use crate::cmd::Cmd;
use crate::hooks::context::{HookContext, HookStorage};
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 struct RuntimeContext {
hook_context: Arc<RwLock<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,
measurements: std::collections::HashMap<u64, (u16, u16)>,
frame_rate_stats: Option<Arc<SharedFrameRateStats>>,
}
impl RuntimeContext {
pub fn new() -> Self {
Self {
hook_context: Arc::new(RwLock::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,
measurements: std::collections::HashMap::new(),
frame_rate_stats: None,
}
}
pub fn with_app_control(exit_flag: Arc<AtomicBool>, render_handle: RenderHandle) -> Self {
Self {
hook_context: Arc::new(RwLock::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,
measurements: std::collections::HashMap::new(),
frame_rate_stats: None,
}
}
pub fn prepare_render(&mut self) {
self.input_handlers.clear();
self.mouse_handlers.clear();
self.mouse_enabled = false;
}
pub fn begin_render(&mut self) {
self.prepare_render();
let mut hook_ctx = self
.hook_context
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.begin_render();
}
pub fn end_render(&mut self) {
let mut hook_ctx = self
.hook_context
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.end_render();
}
pub fn run_effects(&mut self) {
let mut hook_ctx = self
.hook_context
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.run_effects();
}
pub fn use_hook<T: Clone + Send + Sync + 'static, F: FnOnce() -> T>(
&mut self,
init: F,
) -> HookStorage {
let mut hook_ctx = self
.hook_context
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.use_hook(init)
}
pub fn queue_cmd(&mut self, cmd: Cmd) {
let mut hook_ctx = self
.hook_context
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.queue_cmd(cmd);
}
pub fn take_cmds(&mut self) -> Vec<Cmd> {
let mut hook_ctx = self
.hook_context
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.take_cmds()
}
pub fn set_render_callback(&mut self, callback: crate::hooks::context::RenderCallback) {
let mut hook_ctx = self
.hook_context
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.set_render_callback(callback);
}
pub fn request_render(&self) {
let hook_ctx = self
.hook_context
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner());
hook_ctx.request_render();
if let Some(handle) = &self.render_handle {
handle.request_render();
}
}
pub fn hook_context(&self) -> Arc<RwLock<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 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 set_screen_reader_enabled(&mut self, enabled: bool) {
self.screen_reader_enabled = enabled;
}
pub fn set_measurement(&mut self, element_id: u64, width: u16, height: u16) {
self.measurements.insert(element_id, (width, height));
}
pub fn get_measurement(&self, element_id: u64) -> Option<(u16, u16)> {
self.measurements.get(&element_id).copied()
}
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()
}
}
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,
{
set_current_runtime(Some(ctx.clone()));
ctx.borrow_mut().begin_render();
let result = f();
ctx.borrow_mut().end_render();
ctx.borrow_mut().run_effects();
set_current_runtime(None);
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() {
let mut ctx = RuntimeContext::new();
assert!(ctx.get_measurement(1).is_none());
ctx.set_measurement(1, 80, 24);
assert_eq!(ctx.get_measurement(1), Some((80, 24)));
}
#[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);
});
}
}