Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_tui/src/lib_tui/draw/window.rs

12.5 KiB, 13 runs

created by r1870400018:1189, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::lib_tui::{
2 draw::{
3 canvas::CanvasConfig,
4 outline::{
5 Outline,
6 OutlineConfigs,
7 OutlineState,
8 },
9 status::{
10 StatusStrip,
11 StatusStripConfig,
12 StatusStripContent,
13 StatusStripRight,
14 StatusStripType,
15 },
16 tab::TabbedTextManager,
17 tbox::TextBox,
18 },
19 render::{
20 Drawable,
21 Drawer,
22 Renderer,
23 Sink,
24 When,
25 },
26 text::{
27 typ::{
28 HighlightType,
29 TextType,
30 },
31 },
32 window::WindowMode,
33};
34
35use oxedyne_fe2o3_core::prelude::*;
36use oxedyne_fe2o3_geom::{
37 dim::{
38 Coord,
39 Dim,
40 },
41 rect::{
42 AbsRect,
43 AbsSize,
44 RectView,
45 },
46};
47use oxedyne_fe2o3_text::{
48 lines::TextLines,
49};
50
51
52#[derive(Clone, Debug, Default)]
53pub struct WindowConfig {
54 pub typ: WindowType,
55 pub canvas: CanvasConfig, // Default canvas config, overridden by tabs.
56 pub outlines: OutlineConfigs,
57 pub header: Option<StatusStripConfig>,
58 pub footer: Option<StatusStripConfig>,
59 pub min_size: AbsSize,
60 pub menu_text: Option<TextLines<TextType, HighlightType>>,
61}
62
63#[derive(Clone, Debug)]
64pub enum TextBoxesState {
65 Single(TextBox),
66 Tabbed(TabbedTextManager),
67}
68
69impl TextBoxesState {
70 pub fn get_text_box(&self) -> Option<&TextBox> {
71 match self {
72 Self::Single(tbox) => Some(&tbox),
73 Self::Tabbed(tmgr) => {
74 match tmgr.get_focal_tabbed_text_box() {
75 Some(ttbox) => Some(&ttbox.tbox),
76 None => None,
77 }
78 }
79 }
80 }
81 pub fn get_text_box_mut(&mut self) -> Option<&mut TextBox> {
82 match self {
83 Self::Single(tbox) => Some(tbox),
84 Self::Tabbed(tmgr) => {
85 match tmgr.get_focal_tabbed_text_box_mut() {
86 Some(ttbox) => Some(&mut ttbox.tbox),
87 None => None,
88 }
89 }
90 }
91 }
92}
93
94impl Default for TextBoxesState {
95 fn default() -> Self {
96 Self::Single(TextBox::default())
97 }
98}
99
100impl TextBoxesState {
101 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut TextBox> {
102 match self {
103 Self::Single(tbox) => {
104 vec![tbox].into_iter()
105 }
106 Self::Tabbed(tmgr) => {
107 tmgr.iter_mut().collect::<Vec<_>>().into_iter()
108 }
109 }
110 }
111}
112
113#[derive(Clone, Debug, Default)]
114pub struct WindowState {
115 pub header: StatusStripContent,
116 pub footer: StatusStripContent,
117 pub text_boxes: TextBoxesState,
118 pub lines: OutlineState,
119}
120
121#[derive(Clone, Debug, Default, PartialEq)]
122pub enum WindowType {
123 #[default]
124 Fixed,
125 Dynamic,
126}
127
128impl From<WindowStateInit> for WindowState {
129 fn from(wsi: WindowStateInit) -> Self {
130 Self {
131 header: wsi.header.unwrap_or(StatusStripContent::default()),
132 footer: wsi.footer.unwrap_or(StatusStripContent::default()),
133 text_boxes: wsi.text_boxes.unwrap_or(TextBoxesState::default()),
134 lines: OutlineState::default(),
135 }
136 }
137}
138
139#[derive(Clone, Debug, Default)]
140pub struct WindowStateInit {
141 pub header: Option<StatusStripContent>,
142 pub footer: Option<StatusStripContent>,
143 pub text_boxes: Option<TextBoxesState>,
144}
145
146#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
147pub enum WindowId {
148 Menu,
149 Help,
150 Log,
151 User(u8),
152}
153
154impl Default for WindowId {
155 fn default() -> Self {
156 Self::User(3)
157 }
158}
159
160impl WindowId {
161 pub fn title(&self) -> &str {
162 match self {
163 Self::Menu => "Menu",
164 Self::Help => "Help",
165 Self::Log => "Log",
166 Self::User(_) => "User",
167 }
168 }
169 pub fn label(&self) -> String {
170 match self {
171 Self::Menu => fmt!("0"),
172 Self::Help => fmt!("1"),
173 Self::Log => fmt!("2"),
174 Self::User(label) => fmt!("{}", label),
175 }
176 }
177}
178
179#[derive(Clone, Debug, Default)]
180pub struct Window {
181 pub cfg: WindowConfig,
182 pub focus: bool,
183 pub mode: WindowMode,
184 pub view: RectView, // The outer extent of the window.
185 pub id: WindowId,
186 pub state: WindowState,
187 pub layer: u8,
188}
189
190impl Drawable for Window {
191 fn render<S: Sink, R: Renderer<S>>(
192 &mut self,
193 drawer: &mut Drawer<S, R>,
194 when: When,
195 )
196 -> Outcome<()>
197 {
198 let term = AbsRect::from(res!(drawer.rend.size()));
199 let win_view = match self.view.relative_to(term) {
200 Some(view) => view,
201 None => return Ok(()), // Window is outside terminal view.
202 };
203
204 // Initialise window extent. We'll progressively draw some components and modify these
205 // metrics to reveal the final viewing area.
206 let (mut x, mut y, mut w, mut h) = win_view.tup();
207
208 // Account for outline.
209 if w > Dim(2) && h > Dim(2) {
210 x += 1;
211 y += 1;
212 w -= 2;
213 h -= 2;
214 }
215
216 // Account for header and draw if necessary.
217 if let Some(cfg) = &self.cfg.header {
218 let content = self.clone_and_update_status_strip(StatusStripType::Header);
219 let mut header = StatusStrip::new(
220 cfg.clone(),
221 content,
222 AbsRect::from((x, y, w, h)),
223 );
224 res!(header.render(drawer, When::Later));
225 y += 1;
226 h -= 1;
227 }
228
229 // Account for footer and draw if necessary.
230 if let Some(cfg) = &self.cfg.footer {
231 let content = self.clone_and_update_status_strip(StatusStripType::Footer);
232 let mut footer = StatusStrip::new(
233 cfg.clone(),
234 content,
235 AbsRect::from((x, y, w, h)),
236 );
237 res!(footer.render(drawer, When::Later));
238 h -= 1;
239 }
240
241 // Account for a tab strip and draw if necessary.
242 if let TextBoxesState::Tabbed(ttmgr) = &mut self.state.text_boxes {
243 if !ttmgr.is_empty() {
244 res!(ttmgr.update_tabs(AbsRect::from((x, y, w, h))));
245 res!(ttmgr.render(drawer, When::Later));
246 let dh = ttmgr.strip_height();
247 y += dh;
248 h -= dh;
249 }
250 }
251
252 // This is the viewing area.
253 let content_view = AbsRect::from((x, y, w, h));
254
255 // Draw the outline if necessary.
256 let mut outline = Outline {
257 cfgs: &self.cfg.outlines,
258 view: win_view.clone(),
259 state: &self.state.lines,
260 focus: self.focus,
261 wmode: self.mode,
262 };
263 res!(outline.render(drawer, When::Later));
264
265 // Display the text view. We are either drawing a single tex box, with or without an
266 // associated one-row tab strip, or multiple tiled text boxes under two rows of tabs.
267 match &mut self.state.text_boxes {
268 TextBoxesState::Single(tbox) => {
269 tbox.view = content_view;
270 res!(tbox.render(drawer, When::Later));
271 }
272 TextBoxesState::Tabbed(ttmgr) => {
273 let focus_is_on_tiled = ttmgr.focus_is_on_tiled();
274 match &mut ttmgr.tiled {
275 Some(tiled) => {
276 if focus_is_on_tiled {
277 let mut start_x = x;
278 for tabbed_tile in tiled {
279 let ttbox = &mut ttmgr.tboxes[tabbed_tile.index];
280 let wt = tabbed_tile.width;
281 ttbox.tbox.view = AbsRect::from((start_x, y, wt, h));
282 res!(ttbox.tbox.render(drawer, When::Later));
283 start_x += wt;
284 }
285 } else {
286 if let Some(ttbox) = ttmgr.get_focal_tabbed_text_box_mut() {
287 ttbox.tbox.view = content_view;
288 res!(ttbox.tbox.render(drawer, When::Later));
289 }
290 }
291 }
292 None => {
293 if let Some(ttbox) = ttmgr.get_focal_tabbed_text_box_mut() {
294 ttbox.tbox.view = content_view;
295 res!(ttbox.tbox.render(drawer, When::Later));
296 }
297 }
298 }
299 }
300 }
301
302 res!(drawer.rend.reset_style(When::Later));
303
304 if when == When::Now {
305 res!(drawer.rend.flush());
306 }
307 Ok(())
308 }
309}
310
311impl Window {
312
313 pub fn new(
314 id: WindowId,
315 cfg: WindowConfig,
316 view: RectView,
317 state: Option<WindowStateInit>,
318 )
319 -> Outcome<Self>
320 {
321 if cfg.typ == WindowType::Dynamic {
322 if let RectView::AlwaysRelative(_) = view {
323 return Err(err!(
324 "A dynamic window view cannot be set as {:?}.", view;
325 Input, Invalid));
326 }
327 }
328 Ok(Self {
329 id,
330 cfg,
331 view,
332 state: match state {
333 Some(wsi) => WindowState::from(wsi),
334 None => WindowState::default(),
335 },
336 ..Default::default()
337 })
338 }
339
340 pub fn set_state(&mut self, state:WindowStateInit) {
341 self.state = WindowState::from(state);
342 }
343
344 pub fn set_window_focus(&mut self, focus: bool) {
345 self.focus = focus;
346 }
347
348 pub fn set_window_mode(&mut self, mode: WindowMode) {
349 self.mode = mode;
350 }
351
352 pub fn get_focal_text_box(&self) -> Option<&TextBox> {
353 match &self.state.text_boxes {
354 TextBoxesState::Single(tbox) => Some(&tbox),
355 TextBoxesState::Tabbed(ttmgr) => match ttmgr.get_focal_tabbed_text_box() {
356 Some(ttbox) => Some(&ttbox.tbox),
357 None => None,
358 }
359 }
360 }
361
362 pub fn get_focal_text_box_mut(&mut self) -> Option<&mut TextBox> {
363 match &mut self.state.text_boxes {
364 TextBoxesState::Single(tbox) => Some(tbox),
365 TextBoxesState::Tabbed(ttmgr) => match ttmgr.get_focal_tabbed_text_box_mut() {
366 Some(ttbox) => Some(&mut ttbox.tbox),
367 None => None,
368 }
369 }
370 }
371
372 pub fn clone_and_update_status_strip(
373 &self,
374 typ: StatusStripType,
375 )
376 -> StatusStripContent
377 {
378 let mut content = match typ {
379 StatusStripType::Header => self.state.header.clone(),
380 StatusStripType::Footer => self.state.footer.clone(),
381 };
382 // The left side currently contains an optional origin, which does not need to be
383 // updated frequently.
384 if let Some(tbox) = self.get_focal_text_box() {
385 match &mut content.right {
386 Some(right) => match right {
387 StatusStripRight::Cursor(coord) => {
388 if let Some(cursor) = tbox.tview.vtyp.get_cursor() {
389 *coord = *cursor + Coord::from((1, 1));
390 }
391 }
392 StatusStripRight::Label(label) => {
393 *label = self.id.label();
394 }
395 StatusStripRight::Mode(mode_opt) => {
396 *mode_opt = if self.mode == WindowMode::Interaction {
397 if let Some(editor) = tbox.tview.vtyp.get_editor() {
398 Some(editor.mode)
399 } else {
400 None
401 }
402 } else {
403 None
404 };
405 }
406 StatusStripRight::ModeLabel(mode_opt, label) => {
407 *mode_opt = if self.mode == WindowMode::Interaction {
408 if let Some(editor) = tbox.tview.vtyp.get_editor() {
409 Some(editor.mode)
410 } else {
411 None
412 }
413 } else {
414 None
415 };
416 *label = self.id.label();
417 }
418 }
419 _ => {}
420 }
421 }
422 content
423 }
424
425 pub fn create_tab(
426 &mut self,
427 )
428 -> Outcome<()>
429 {
430 Ok(())
431 }
432}