virtual_console_lib/
text_grid.rs1use crate::colors::ColorScheme;
6use crate::terminal::TerminalConfig;
7use carnelian::color::Color;
8use carnelian::render::Context as RenderContext;
9use carnelian::scene::facets::Facet;
10use carnelian::scene::LayerGroup;
11use carnelian::{Size, ViewAssistantContext};
12use fuchsia_trace::duration;
13use std::any::Any;
14use std::cell::RefCell;
15use std::rc::Rc;
16use term_model::ansi::{CursorStyle, TermInfo};
17use term_model::term::cell::Flags;
18use term_model::term::color::Rgb;
19use term_model::Term;
20use terminal::{renderable_layers, FontSet, LayerContent, Offset, RenderableLayer, Renderer};
21
22fn make_rgb(color: &Color) -> Rgb {
23 Rgb { r: color.r, g: color.g, b: color.b }
24}
25
26pub struct TextGridFacet<T> {
29 font_set: FontSet,
30 color_scheme: ColorScheme,
31 size: Size,
32 term: Option<Rc<RefCell<Term<T>>>>,
33 status: Vec<(String, Rgb)>,
34 status_tab_width: usize,
35 renderer: Renderer,
36}
37
38pub enum TextGridMessages<T> {
39 SetTermMessage(Rc<RefCell<Term<T>>>),
40 ChangeStatusMessage(Vec<(String, Rgb)>),
41}
42
43const STATUS_BG: Rgb = Rgb { r: 0, g: 0, b: 0 };
44
45impl<T> TextGridFacet<T> {
46 pub fn new(
47 font_set: FontSet,
48 cell_size: &Size,
49 color_scheme: ColorScheme,
50 term: Option<Rc<RefCell<Term<T>>>>,
51 status: Vec<(String, Rgb)>,
52 status_tab_width: usize,
53 ) -> Self {
54 let renderer = Renderer::new(&font_set, cell_size);
55
56 Self {
57 font_set,
58 color_scheme,
59 size: Size::zero(),
60 term,
61 status,
62 status_tab_width,
63 renderer,
64 }
65 }
66}
67
68impl<T: 'static> Facet for TextGridFacet<T> {
69 fn update_layers(
70 &mut self,
71 _: Size,
72 layer_group: &mut dyn LayerGroup,
73 render_context: &mut RenderContext,
74 view_context: &ViewAssistantContext,
75 ) -> std::result::Result<(), anyhow::Error> {
76 duration!(c"gfx", c"TextGrid::update_layers");
77
78 self.size = view_context.size;
79
80 let config: TerminalConfig = self.color_scheme.into();
81 let term = self.term.as_ref().map(|t| t.borrow());
82 let status_tab_width = self.status_tab_width;
83 let columns = term.as_ref().map(|t| t.cols().0).unwrap_or(1);
84 let bg = make_rgb(&self.color_scheme.back);
85 let term_offset = Offset { column: 0, row: 1 };
87
88 let layers = if STATUS_BG != bg {
95 Some((0..columns).into_iter().map(|x| RenderableLayer {
96 order: x,
97 column: x,
98 row: 0,
99 content: LayerContent::Cursor(CursorStyle::Block),
100 rgb: STATUS_BG,
101 }))
102 } else {
103 None
104 }
105 .into_iter()
106 .flat_map(|iter| iter)
107 .chain(self.status.iter().enumerate().flat_map(|(i, (s, rgb))| {
108 let start = i * status_tab_width;
109 let order = columns + start;
110 s.chars().enumerate().map(move |(x, c)| RenderableLayer {
111 order: order + x,
112 column: start + x,
113 row: 0,
114 content: LayerContent::Char((c, Flags::empty())),
115 rgb: *rgb,
116 })
117 }))
118 .chain(term.iter().flat_map(|term| renderable_layers(term, &config, &term_offset)));
119
120 self.renderer.render(layer_group, render_context, &self.font_set, layers);
121
122 Ok(())
123 }
124
125 fn handle_message(&mut self, message: Box<dyn Any>) {
126 if let Some(message) = message.downcast_ref::<TextGridMessages<T>>() {
127 match message {
128 TextGridMessages::SetTermMessage(term) => {
129 self.term = Some(Rc::clone(term));
130 }
131 TextGridMessages::ChangeStatusMessage(status) => {
132 self.status = status.clone();
133 }
134 }
135 }
136 }
137
138 fn calculate_size(&self, _: Size) -> Size {
139 self.size
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use anyhow::Error;
147 use carnelian::drawing::load_font;
148 use std::path::PathBuf;
149 use term_model::event::{Event, EventListener};
150
151 #[derive(Default)]
152 struct TestListener;
153
154 impl EventListener for TestListener {
155 fn send_event(&self, _event: Event) {}
156 }
157
158 const FONT: &'static str = "/pkg/data/font.ttf";
159
160 #[test]
161 fn can_create_text_grid() -> Result<(), Error> {
162 let font = load_font(PathBuf::from(FONT))?;
163 let font_set = FontSet::new(font, None, None, None, vec![]);
164 let _ = TextGridFacet::<TestListener>::new(
165 font_set,
166 &Size::new(8.0, 16.0),
167 ColorScheme::default(),
168 None,
169 vec![],
170 24,
171 );
172 Ok(())
173 }
174}