use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Clone)]
pub struct AppContext {
exit_flag: Arc<AtomicBool>,
render_handle: crate::renderer::RenderHandle,
}
impl AppContext {
pub fn new(exit_flag: Arc<AtomicBool>, render_handle: crate::renderer::RenderHandle) -> Self {
Self {
exit_flag,
render_handle,
}
}
pub fn exit(&self) {
self.exit_flag.store(true, Ordering::SeqCst);
}
pub fn println(&self, message: impl crate::renderer::IntoPrintable) {
self.render_handle.println(message);
}
pub fn enter_alt_screen(&self) {
self.render_handle.enter_alt_screen();
}
pub fn exit_alt_screen(&self) {
self.render_handle.exit_alt_screen();
}
pub fn is_alt_screen(&self) -> bool {
self.render_handle.is_alt_screen()
}
pub fn request_render(&self) {
self.render_handle.request_render();
}
#[cfg(unix)]
pub fn suspend(&self) {
self.render_handle.request_suspend();
}
#[cfg(not(unix))]
pub fn suspend(&self) {
}
}
thread_local! {
static APP_CONTEXT: std::cell::RefCell<Option<AppContext>> = const { std::cell::RefCell::new(None) };
}
pub fn set_app_context(ctx: Option<AppContext>) {
APP_CONTEXT.with(|c| {
*c.borrow_mut() = ctx;
});
}
pub fn get_app_context() -> Option<AppContext> {
if let Some(ctx) = crate::runtime::current_runtime() {
let borrowed = ctx.borrow();
if let Some(handle) = borrowed.render_handle() {
return Some(AppContext::new(borrowed.exit_flag(), handle.clone()));
}
}
APP_CONTEXT.with(|c| c.borrow().clone())
}
pub fn use_app() -> AppContext {
get_app_context().expect("use_app must be called within an App context")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::renderer::RenderHandle;
use crate::renderer::registry::AppSink;
#[test]
fn test_app_context_exit() {
let exit_flag = Arc::new(AtomicBool::new(false));
let ctx = AppContext::new(exit_flag.clone(), test_render_handle());
assert!(!exit_flag.load(Ordering::SeqCst));
ctx.exit();
assert!(exit_flag.load(Ordering::SeqCst));
}
#[test]
fn test_set_get_app_context_legacy() {
let exit_flag = Arc::new(AtomicBool::new(false));
let ctx = AppContext::new(exit_flag.clone(), test_render_handle());
set_app_context(Some(ctx));
crate::runtime::set_current_runtime(None);
let retrieved = APP_CONTEXT.with(|c| c.borrow().clone());
assert!(retrieved.is_some());
retrieved.unwrap().exit();
assert!(exit_flag.load(Ordering::SeqCst));
set_app_context(None);
}
#[test]
fn test_app_context_with_runtime() {
use crate::runtime::{RuntimeContext, with_runtime};
use std::cell::RefCell;
use std::rc::Rc;
let exit_flag = Arc::new(AtomicBool::new(false));
let render_handle = test_render_handle();
let ctx = Rc::new(RefCell::new(RuntimeContext::with_app_control(
exit_flag.clone(),
render_handle,
)));
with_runtime(ctx.clone(), || {
let app = get_app_context().expect("Should get app context");
assert!(!exit_flag.load(Ordering::SeqCst));
app.exit();
assert!(exit_flag.load(Ordering::SeqCst));
});
}
fn test_render_handle() -> RenderHandle {
struct NoopSink;
impl AppSink for NoopSink {
fn request_render(&self) {}
fn println(&self, _message: crate::renderer::Printable) {}
fn enter_alt_screen(&self) {}
fn exit_alt_screen(&self) {}
fn is_alt_screen(&self) -> bool {
false
}
fn queue_exec(&self, _request: crate::cmd::ExecRequest) {}
fn request_suspend(&self) {}
}
RenderHandle::new(Arc::new(NoopSink))
}
}