1use std::io::Write;
6use std::sync::Arc;
7
8use smelt_style::theme::Theme;
9
10use crate::compositor::Compositor;
11use crate::grid::{Grid, GridSlice};
12use crate::layout::{resolve_layout, LayoutTree, PaintId, Rect};
13use crate::paint_layout_tree;
14
15pub struct Surface {
16 compositor: Compositor,
17 layout: LayoutTree,
18 theme: Arc<Theme>,
19 size: (u16, u16),
20}
21
22impl Surface {
23 pub fn new(width: u16, height: u16) -> Self {
24 Self::with_theme(width, height, Theme::new())
25 }
26
27 pub fn with_theme(width: u16, height: u16, theme: Theme) -> Self {
28 Self {
29 compositor: Compositor::new(width, height),
30 layout: LayoutTree::vbox(Vec::new()),
31 theme: Arc::new(theme),
32 size: (width, height),
33 }
34 }
35
36 pub fn terminal_size(&self) -> (u16, u16) {
37 self.size
38 }
39
40 pub fn set_terminal_size(&mut self, w: u16, h: u16) {
41 self.size = (w, h);
42 self.compositor.resize(w, h);
43 }
44
45 pub fn area(&self) -> Rect {
47 Rect::new(0, 0, self.size.0, self.size.1)
48 }
49
50 pub fn layout(&self) -> &LayoutTree {
51 &self.layout
52 }
53
54 pub fn set_layout(&mut self, layout: LayoutTree) {
55 self.layout = layout;
56 }
57
58 pub fn theme(&self) -> &Arc<Theme> {
59 &self.theme
60 }
61
62 pub fn theme_mut(&mut self) -> &mut Theme {
64 Arc::make_mut(&mut self.theme)
65 }
66
67 pub fn force_redraw(&mut self) {
68 self.compositor.force_redraw();
69 }
70
71 pub fn paint_rect(&self, id: PaintId) -> Option<Rect> {
73 resolve_layout(&self.layout, self.area()).get(&id).copied()
74 }
75
76 pub fn compositor(&self) -> &Compositor {
77 &self.compositor
78 }
79
80 pub fn compositor_mut(&mut self) -> &mut Compositor {
81 &mut self.compositor
82 }
83
84 pub fn render<W, F>(&mut self, w: &mut W, mut paint: F) -> std::io::Result<()>
86 where
87 W: Write,
88 F: FnMut(PaintId, &mut GridSlice<'_>, &Arc<Theme>),
89 {
90 let layout = self.layout.clone();
91 let area = self.area();
92 let size = self.size;
93 let theme_arc = Arc::clone(&self.theme);
94 self.compositor
95 .render_with(&self.theme, w, move |grid, _theme| {
96 let theme = &theme_arc;
97 let mut dispatch = |id: PaintId,
98 leaf: Rect,
99 grid: &mut Grid,
100 theme: &Arc<Theme>,
101 _ts: (u16, u16)| {
102 let mut slice = grid.slice_mut(leaf);
103 paint(id, &mut slice, theme);
104 };
105 paint_layout_tree(grid, theme, &layout, area, size, &mut dispatch);
106 })
107 }
108
109 pub fn render_raw<W, F>(&mut self, w: &mut W, paint: F) -> std::io::Result<()>
111 where
112 W: Write,
113 F: FnOnce(&mut Grid, &Theme),
114 {
115 self.compositor.render_with(&self.theme, w, paint)
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::layout::{Constraint, PaintId};
123
124 #[test]
125 fn area_reports_full_terminal_rect() {
126 let s = Surface::new(80, 24);
127 assert_eq!(s.area(), Rect::new(0, 0, 80, 24));
128 }
129
130 #[test]
131 fn set_terminal_size_updates_reported_size_and_area() {
132 let mut s = Surface::new(10, 5);
133 s.set_terminal_size(40, 20);
134 assert_eq!(s.terminal_size(), (40, 20));
135 assert_eq!(s.area(), Rect::new(0, 0, 40, 20));
136 }
137
138 #[test]
139 fn paint_rect_returns_leaf_rect_resolved_against_current_size() {
140 let mut s = Surface::new(80, 24);
143 let top = PaintId(1);
144 let bottom = PaintId(2);
145 s.set_layout(LayoutTree::vbox(vec![
146 (Constraint::Fill, LayoutTree::leaf(top)),
147 (Constraint::Fill, LayoutTree::leaf(bottom)),
148 ]));
149 let top_rect = s.paint_rect(top).expect("top leaf resolved");
150 let bot_rect = s.paint_rect(bottom).expect("bottom leaf resolved");
151 assert_eq!(top_rect.height + bot_rect.height, 24);
152 assert_eq!(top_rect.width, 80);
153 assert_eq!(bot_rect.width, 80);
154 }
155
156 #[test]
157 fn paint_rect_returns_none_for_unknown_leaf() {
158 let s = Surface::new(80, 24);
159 assert_eq!(s.paint_rect(PaintId(999)), None);
160 }
161
162 #[test]
163 fn theme_mut_allows_in_place_mutation() {
164 use smelt_style::style::{Color, Style};
165 let mut s = Surface::new(10, 5);
166 s.theme_mut().set("Error", Style::new().fg(Color::Red));
167 assert_eq!(s.theme().get("Error").fg, Some(Color::Red));
169 }
170}