use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use ratatui_core::layout::Rect;
use crate::{Element, RenderCtx, Size, Surface, View};
#[derive(Clone, Default)]
pub struct RedrawHandle {
requested: Arc<AtomicBool>,
}
impl RedrawHandle {
pub fn request(&self) {
self.requested.store(true, Ordering::Release);
}
pub(crate) fn take(&self) -> bool {
self.requested.swap(false, Ordering::AcqRel)
}
}
pub struct Live<T> {
value: Arc<RwLock<T>>,
redraw: Option<RedrawHandle>,
}
impl<T> Clone for Live<T> {
fn clone(&self) -> Self {
Self {
value: Arc::clone(&self.value),
redraw: self.redraw.clone(),
}
}
}
impl<T> Live<T> {
pub fn new(value: T) -> Self {
Self {
value: Arc::new(RwLock::new(value)),
redraw: None,
}
}
pub fn with_redraw(value: T, redraw: RedrawHandle) -> Self {
Self {
value: Arc::new(RwLock::new(value)),
redraw: Some(redraw),
}
}
pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
let value = self.value.read().unwrap_or_else(|error| error.into_inner());
read(&value)
}
pub fn update<R>(&self, update: impl FnOnce(&mut T) -> R) -> R {
let result = {
let mut value = self
.value
.write()
.unwrap_or_else(|error| error.into_inner());
update(&mut value)
};
if let Some(redraw) = &self.redraw {
redraw.request();
}
result
}
pub fn set(&self, value: T) {
self.update(|current| *current = value);
}
}
pub struct LiveView<T, F> {
value: Live<T>,
build: F,
}
impl<T, F> LiveView<T, F>
where
F: Fn(&T) -> Element,
{
pub fn new(value: Live<T>, build: F) -> Self {
Self { value, build }
}
fn current(&self) -> Element {
self.value.with(&self.build)
}
}
impl<T, F> View for LiveView<T, F>
where
F: Fn(&T) -> Element,
{
fn measure(&self, available: Size) -> Size {
self.current().measure(available)
}
fn render(&self, area: Rect, surface: &mut Surface, ctx: &RenderCtx) {
self.current().render(area, surface, ctx);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::components::Text;
use crate::style::Theme;
use crate::test_support::row;
use crate::view::element;
#[test]
fn live_view_reads_updated_data_without_reconstruction() {
let redraw = RedrawHandle::default();
let value = Live::with_redraw(1u32, redraw.clone());
let view = LiveView::new(value.clone(), |value| {
element(Text::raw(format!("count: {value}")))
});
let theme = Theme::default();
assert_eq!(
row(&crate::testing::render(&view, 12, 1, &theme), 0),
"count: 1"
);
value.set(2);
assert!(redraw.take());
assert_eq!(
row(&crate::testing::render(&view, 12, 1, &theme), 0),
"count: 2"
);
}
}