use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use super::types::Annotation;
pub struct Annotate<W> {
widget: W,
annotation: Annotation,
}
impl<W> Annotate<W> {
pub fn new(widget: W, annotation: Annotation) -> Self {
Self { widget, annotation }
}
pub fn annotation(&self) -> &Annotation {
&self.annotation
}
pub fn annotation_mut(&mut self) -> &mut Annotation {
&mut self.annotation
}
pub fn inner(&self) -> &W {
&self.widget
}
pub fn inner_mut(&mut self) -> &mut W {
&mut self.widget
}
pub fn into_inner(self) -> W {
self.widget
}
pub fn focused(mut self, focused: bool) -> Self {
self.annotation.focused = focused;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.annotation.disabled = disabled;
self
}
pub fn selected(mut self, selected: bool) -> Self {
self.annotation.selected = selected;
self
}
pub fn value(mut self, value: impl Into<String>) -> Self {
self.annotation.value = Some(value.into());
self
}
}
impl<W: Widget> Widget for Annotate<W> {
fn render(self, area: Rect, buf: &mut Buffer) {
ANNOTATION_CONTEXT.with(|ctx| {
if let Some(registry) = ctx.borrow_mut().as_mut() {
registry.register(area, self.annotation.clone());
}
});
self.widget.render(area, buf);
}
}
pub struct AnnotateContainer<W> {
widget: W,
annotation: Annotation,
}
impl<W> AnnotateContainer<W> {
pub fn new(widget: W, annotation: Annotation) -> Self {
Self { widget, annotation }
}
}
impl<W: Widget> Widget for AnnotateContainer<W> {
fn render(self, area: Rect, buf: &mut Buffer) {
ANNOTATION_CONTEXT.with(|ctx| {
if let Some(registry) = ctx.borrow_mut().as_mut() {
registry.open(area, self.annotation.clone());
}
});
self.widget.render(area, buf);
ANNOTATION_CONTEXT.with(|ctx| {
if let Some(registry) = ctx.borrow_mut().as_mut() {
registry.close();
}
});
}
}
use super::registry::AnnotationRegistry;
use std::cell::RefCell;
thread_local! {
static ANNOTATION_CONTEXT: RefCell<Option<AnnotationRegistry>> = const { RefCell::new(None) };
}
pub fn with_annotations<F, R>(f: F) -> AnnotationRegistry
where
F: FnOnce() -> R,
{
ANNOTATION_CONTEXT.with(|ctx| {
*ctx.borrow_mut() = Some(AnnotationRegistry::new());
});
f();
ANNOTATION_CONTEXT.with(|ctx| ctx.borrow_mut().take().unwrap_or_default())
}
pub fn with_registry<F, R>(f: F) -> Option<R>
where
F: FnOnce(&mut AnnotationRegistry) -> R,
{
ANNOTATION_CONTEXT.with(|ctx| ctx.borrow_mut().as_mut().map(f))
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::widgets::Paragraph;
#[test]
fn test_annotate_widget() {
let widget = Annotate::new(Paragraph::new("Test"), Annotation::button("test-btn"));
assert!(widget.annotation().has_id("test-btn"));
}
#[test]
fn test_annotate_builder_methods() {
let widget = Annotate::new(Paragraph::new("Input"), Annotation::input("name"))
.focused(true)
.value("John");
assert!(widget.annotation().focused);
assert_eq!(widget.annotation().value, Some("John".to_string()));
}
#[test]
fn test_with_annotations() {
let registry = with_annotations(|| {
with_registry(|reg| {
reg.register(Rect::new(0, 0, 10, 1), Annotation::button("a"));
reg.register(Rect::new(0, 2, 10, 1), Annotation::button("b"));
});
});
assert_eq!(registry.len(), 2);
}
#[test]
fn test_nested_annotations() {
let registry = with_annotations(|| {
with_registry(|reg| {
reg.open(Rect::new(0, 0, 80, 24), Annotation::container("main"));
reg.register(Rect::new(5, 5, 10, 1), Annotation::button("inner"));
reg.close();
});
});
assert_eq!(registry.len(), 2);
let inner = registry.get_by_id("inner").unwrap();
assert_eq!(inner.parent, Some(0));
}
#[test]
fn test_annotate_annotation_mut() {
let mut widget = Annotate::new(Paragraph::new("Test"), Annotation::button("btn"));
widget.annotation_mut().focused = true;
assert!(widget.annotation().focused);
}
#[test]
fn test_annotate_inner() {
let widget = Annotate::new(Paragraph::new("Inner Widget"), Annotation::button("btn"));
let _inner = widget.inner();
}
#[test]
fn test_annotate_inner_mut() {
let mut widget = Annotate::new(Paragraph::new("Mutable"), Annotation::button("btn"));
let _inner = widget.inner_mut();
}
#[test]
fn test_annotate_into_inner() {
let widget = Annotate::new(Paragraph::new("Unwrap Me"), Annotation::button("btn"));
let _inner = widget.into_inner();
}
#[test]
fn test_annotate_disabled() {
let widget =
Annotate::new(Paragraph::new("Disabled"), Annotation::button("btn")).disabled(true);
assert!(widget.annotation().disabled);
}
#[test]
fn test_annotate_selected() {
let widget =
Annotate::new(Paragraph::new("Selected"), Annotation::button("btn")).selected(true);
assert!(widget.annotation().selected);
}
#[test]
fn test_annotate_container_new() {
let container =
AnnotateContainer::new(Paragraph::new("Container"), Annotation::container("main"));
let _ = container;
}
#[test]
fn test_with_registry_no_context() {
let result = with_registry(|_| 42);
assert!(result.is_none());
}
#[test]
fn test_with_registry_returns_value() {
let value = with_annotations(|| with_registry(|_| 42));
assert!(value.is_empty());
}
#[test]
fn test_annotate_render_with_terminal() {
use crate::backend::CaptureBackend;
use ratatui::Terminal;
let backend = CaptureBackend::new(40, 10);
let mut terminal = Terminal::new(backend).unwrap();
let registry = with_annotations(|| {
terminal
.draw(|frame| {
let widget =
Annotate::new(Paragraph::new("Rendered"), Annotation::button("btn"));
frame.render_widget(widget, frame.area());
})
.unwrap();
});
assert_eq!(registry.len(), 1);
assert!(registry.get_by_id("btn").is_some());
assert!(terminal.backend().contains_text("Rendered"));
}
#[test]
fn test_annotate_container_render() {
use crate::backend::CaptureBackend;
use ratatui::Terminal;
let backend = CaptureBackend::new(40, 10);
let mut terminal = Terminal::new(backend).unwrap();
let registry = with_annotations(|| {
terminal
.draw(|frame| {
let container = AnnotateContainer::new(
Paragraph::new("Container Content"),
Annotation::container("main"),
);
frame.render_widget(container, frame.area());
})
.unwrap();
});
assert_eq!(registry.len(), 1);
assert!(registry.get_by_id("main").is_some());
}
#[test]
fn test_annotate_combined_states() {
let widget = Annotate::new(Paragraph::new("All States"), Annotation::input("input"))
.focused(true)
.disabled(true)
.selected(true)
.value("test value");
let annotation = widget.annotation();
assert!(annotation.focused);
assert!(annotation.disabled);
assert!(annotation.selected);
assert_eq!(annotation.value, Some("test value".to_string()));
}
#[test]
fn test_nested_rendering() {
use crate::backend::CaptureBackend;
use ratatui::Terminal;
let backend = CaptureBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
let registry = with_annotations(|| {
terminal
.draw(|frame| {
let container = AnnotateContainer::new(
Paragraph::new("Parent"),
Annotation::container("parent"),
);
let inner = Annotate::new(Paragraph::new("Child"), Annotation::button("child"));
let area1 = Rect::new(0, 0, 40, 5);
let area2 = Rect::new(0, 5, 40, 5);
frame.render_widget(container, area1);
frame.render_widget(inner, area2);
})
.unwrap();
});
assert_eq!(registry.len(), 2);
}
}