1use crate::grid::{GridLine, GridLineFlags};
2
3use super::Screen;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ScreenCellView {
8 pub(super) text: String,
9 pub(super) width: u8,
10 pub(super) padding: bool,
11 pub(super) attr: u16,
12 pub(super) fg: crate::input::Colour,
13 pub(super) bg: crate::input::Colour,
14 pub(super) us: crate::input::Colour,
15 pub(super) link: u32,
16}
17
18impl ScreenCellView {
19 #[must_use]
21 pub fn text(&self) -> &str {
22 &self.text
23 }
24
25 #[must_use]
27 pub const fn width(&self) -> u8 {
28 self.width
29 }
30
31 #[must_use]
33 pub const fn is_padding(&self) -> bool {
34 self.padding
35 }
36
37 #[must_use]
39 pub const fn attr(&self) -> u16 {
40 self.attr
41 }
42
43 #[must_use]
45 pub const fn fg(&self) -> crate::input::Colour {
46 self.fg
47 }
48
49 #[must_use]
51 pub const fn bg(&self) -> crate::input::Colour {
52 self.bg
53 }
54
55 #[must_use]
57 pub const fn us(&self) -> crate::input::Colour {
58 self.us
59 }
60
61 #[must_use]
63 pub const fn link(&self) -> u32 {
64 self.link
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct ScreenLineView {
71 pub(super) cells: Vec<ScreenCellView>,
72 width: u32,
73 pub(super) wrapped: bool,
74 pub(super) start_prompt: bool,
75 pub(super) start_output: bool,
76 pub(super) time: i64,
77}
78
79impl ScreenLineView {
80 #[must_use]
82 pub fn cells(&self) -> &[ScreenCellView] {
83 &self.cells
84 }
85
86 #[must_use]
88 pub const fn width(&self) -> u32 {
89 self.width
90 }
91
92 #[must_use]
94 pub fn cell(&self, x: u32) -> Option<&ScreenCellView> {
95 self.cells.get(x as usize)
96 }
97
98 #[must_use]
100 pub const fn wrapped(&self) -> bool {
101 self.wrapped
102 }
103
104 #[must_use]
106 pub const fn start_prompt(&self) -> bool {
107 self.start_prompt
108 }
109
110 #[must_use]
112 pub const fn start_output(&self) -> bool {
113 self.start_output
114 }
115
116 #[must_use]
118 pub const fn time(&self) -> i64 {
119 self.time
120 }
121
122 #[must_use]
124 pub fn owning_cell_x(&self, x: u32) -> Option<u32> {
125 if x >= self.width {
126 return None;
127 }
128 let Some(cell) = self.cell(x) else {
129 return Some(x);
130 };
131 if !cell.is_padding() {
132 return Some(x);
133 }
134
135 let mut owner = x;
136 while owner > 0 {
137 owner -= 1;
138 let candidate = self.cell(owner)?;
139 if !candidate.is_padding() {
140 let width = u32::from(candidate.width().max(1));
141 if owner.saturating_add(width) > x {
142 return Some(owner);
143 }
144 return None;
145 }
146 }
147 None
148 }
149}
150
151impl Screen {
152 #[must_use]
154 pub fn absolute_line_view(&self, absolute_y: usize) -> Option<ScreenLineView> {
155 let line = self.grid.absolute_line(absolute_y)?;
156 let width = u32::from(self.grid.size().cols.max(1));
157 let cells = if let Some(text) = line.plain_text() {
158 let mut cells = text
159 .bytes()
160 .map(|byte| ScreenCellView {
161 text: char::from(byte).to_string(),
162 width: 1,
163 padding: false,
164 attr: 0,
165 fg: crate::input::COLOUR_DEFAULT,
166 bg: crate::input::COLOUR_DEFAULT,
167 us: crate::input::COLOUR_DEFAULT,
168 link: 0,
169 })
170 .collect::<Vec<_>>();
171 cells.resize_with(width as usize, || ScreenCellView {
172 text: " ".to_owned(),
173 width: 1,
174 padding: false,
175 attr: 0,
176 fg: crate::input::COLOUR_DEFAULT,
177 bg: crate::input::COLOUR_DEFAULT,
178 us: crate::input::COLOUR_DEFAULT,
179 link: 0,
180 });
181 cells
182 } else {
183 line.cells()
184 .iter()
185 .map(|cell| ScreenCellView {
186 text: cell.text().to_owned(),
187 width: cell.width(),
188 padding: cell.is_padding(),
189 attr: cell.attr(),
190 fg: cell.fg(),
191 bg: cell.bg(),
192 us: cell.us(),
193 link: cell.link(),
194 })
195 .collect()
196 };
197 Some(ScreenLineView {
198 cells,
199 width,
200 wrapped: line.flags().contains(GridLineFlags::WRAPPED),
201 start_prompt: line.flags().contains(GridLineFlags::START_PROMPT),
202 start_output: line.flags().contains(GridLineFlags::START_OUTPUT),
203 time: line.time(),
204 })
205 }
206
207 #[must_use]
209 pub fn clone_viewport(&self, top_line: usize, cursor_x: u32, cursor_absolute_y: usize) -> Self {
210 let size = self.grid.size();
211 let rows = usize::from(size.rows.max(1));
212 let cols = u32::from(size.cols.max(1));
213 let total_lines = self.absolute_line_count();
214 let top_line = top_line.min(total_lines.saturating_sub(rows));
215 let mut viewport = Self::new(size, 0);
216
217 viewport.mode = self.mode;
218 viewport.cursor_style = self.cursor_style;
219 viewport.title = self.title.clone();
220 viewport.window_name = self.window_name.clone();
221 viewport.path = self.path.clone();
222 viewport.title_stack = self.title_stack.clone();
223 viewport.hyperlinks = self.hyperlinks.clone();
224 viewport.active_hyperlink = self.active_hyperlink;
225 viewport.bell_count = 0;
226 viewport.utf8_config = self.utf8_config.clone();
227
228 let lines = (0..rows)
229 .map(|offset| {
230 self.grid
231 .absolute_line(top_line + offset)
232 .cloned()
233 .unwrap_or_else(|| GridLine::new(cols))
234 })
235 .collect();
236 viewport.grid.replace_visible(lines);
237
238 viewport.cursor_x = cursor_x.min(viewport.max_cursor_x());
239 viewport.cursor_y = if (top_line..top_line + rows).contains(&cursor_absolute_y) {
240 (cursor_absolute_y - top_line) as u32
241 } else {
242 0
243 };
244 viewport.pending_wrap = false;
245 viewport.saved_cursor_x = None;
246 viewport.saved_cursor_y = None;
247 viewport.saved_cursor_pending_wrap = false;
248 viewport.saved_grid = None;
249 viewport.rupper = 0;
250 viewport.rlower = u32::from(size.rows.max(1)).saturating_sub(1);
251 viewport.reset_tabs();
252 viewport
253 }
254}