1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! Safe interoperability with ratatui widgets.
//!
//! [`RatatuiView`] participates in Tuika layout while rendering through
//! [`Surface::render_ratatui`](crate::Surface::render_ratatui), so arbitrary
//! widget code never receives the frame's underlying buffer.
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::Rect;
use crate::{RenderCtx, Size, Surface, View};
/// A Tuika view backed by a ratatui rendering closure.
///
/// The closure form is intentional: many ratatui widgets borrow their data.
/// Captured owned data can be borrowed while constructing the widget inside
/// the closure, without requiring a self-referential wrapper.
pub struct RatatuiView<F> {
measure: Measure,
render: F,
}
#[derive(Clone, Copy, Debug)]
enum Measure {
Fill,
Fixed(Size),
}
impl<F> RatatuiView<F>
where
F: Fn(Rect, &mut Buffer),
{
/// Create a view that requests all space offered by its parent.
///
/// ```
/// use ratatui::widgets::{Block, Widget};
/// use tuika::RatatuiView;
///
/// let view = RatatuiView::fill(|area, buffer| {
/// Block::bordered().title(" ratatui ").render(area, buffer);
/// });
/// ```
pub fn fill(render: F) -> Self {
Self {
measure: Measure::Fill,
render,
}
}
/// Create a view with a fixed intrinsic size for `Dimension::Auto` layout.
pub fn sized(size: Size, render: F) -> Self {
Self {
measure: Measure::Fixed(size),
render,
}
}
}
impl<F> View for RatatuiView<F>
where
F: Fn(Rect, &mut Buffer),
{
fn measure(&self, available: Size) -> Size {
match self.measure {
Measure::Fill => available,
Measure::Fixed(size) => Size::new(
size.width.min(available.width),
size.height.min(available.height),
),
}
}
fn render(&self, area: Rect, surface: &mut Surface, _ctx: &RenderCtx) {
surface.render_ratatui(area, &self.render);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Theme;
use crate::test_support::row;
#[test]
fn ratatui_view_renders_borrowing_widget_data() {
use ratatui::widgets::{Sparkline, Widget};
let data = vec![1, 2, 4, 8];
let view = RatatuiView::fill(move |area, buffer| {
Sparkline::default().data(&data).render(area, buffer);
});
let theme = Theme::default();
let rendered = crate::testing::render(&view, 4, 1, &theme);
assert_ne!(row(&rendered, 0), "");
}
}