oxedyne/fe2o3/fe2o3_tui/src/main.rs
7.5 KiB, 25 runs
created by r1870400018:4716, 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
| 1 | #![forbid(unsafe_code)] |
| 2 | pub mod app; |
| 3 | pub mod lib_tui; |
| 4 | |
| 5 | use crate::{ |
| 6 | app::{ |
| 7 | cfg::AppConfig, |
| 8 | constant, |
| 9 | log::AppLoggerConsole, |
| 10 | }, |
| 11 | lib_tui::{ |
| 12 | action::ActionData, |
| 13 | draw::window::WindowId, |
| 14 | event::AppFlow, |
| 15 | render::{ |
| 16 | CrosstermRenderer, |
| 17 | DebugCrosstermRenderer, |
| 18 | Drawable, |
| 19 | Drawer, |
| 20 | Renderer, |
| 21 | Sink, |
| 22 | When, |
| 23 | }, |
| 24 | text::typ::{ |
| 25 | HighlightType, |
| 26 | TextType, |
| 27 | }, |
| 28 | window::{ |
| 29 | WindowManager, |
| 30 | WindowManagerConfig, |
| 31 | }, |
| 32 | }, |
| 33 | }; |
| 34 | |
| 35 | use oxedyne_fe2o3_core::{ |
| 36 | prelude::*, |
| 37 | log::{ |
| 38 | bot::FileConfig, |
| 39 | }, |
| 40 | }; |
| 41 | use oxedyne_fe2o3_geom::{ |
| 42 | rect::AbsRect, |
| 43 | }; |
| 44 | use oxedyne_fe2o3_text::{ |
| 45 | Text, |
| 46 | lines::TextLines, |
| 47 | }; |
| 48 | |
| 49 | use std::{ |
| 50 | sync::{ |
| 51 | Arc, |
| 52 | RwLock, |
| 53 | }, |
| 54 | }; |
| 55 | |
| 56 | use crossterm::event::{ |
| 57 | Event, |
| 58 | KeyEvent, |
| 59 | }; |
| 60 | |
| 61 | |
| 62 | #[derive(Clone, Debug, Default)] |
| 63 | pub struct App<'a> { |
| 64 | pub cfg: AppConfig, |
| 65 | pub log: Arc<RwLock<TextLines<TextType, HighlightType>>>, |
| 66 | pub mouse: (u16, u16), |
| 67 | pub win_mgr: WindowManager<'a>, |
| 68 | } |
| 69 | |
| 70 | impl App<'_> { |
| 71 | |
| 72 | pub fn new(cfg: Option<AppConfig>) -> Outcome<Self> { |
| 73 | let cfg = match cfg { |
| 74 | Some(cfg) => cfg, |
| 75 | None => AppConfig::default(), |
| 76 | }; |
| 77 | Ok(Self { |
| 78 | cfg: cfg.clone(), |
| 79 | log: Arc::new(RwLock::new(TextLines::new(vec![Text::new("Log line 1.", None)], None))), |
| 80 | win_mgr: res!(WindowManager::new(WindowManagerConfig::from(cfg))), |
| 81 | ..Default::default() |
| 82 | }) |
| 83 | } |
| 84 | |
| 85 | pub fn handle_key_press( |
| 86 | &mut self, |
| 87 | key: KeyEvent, |
| 88 | data: Option<ActionData>, |
| 89 | ) |
| 90 | -> Outcome<AppFlow> |
| 91 | { |
| 92 | self.win_mgr.native_key_handler(key, data) |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | fn setup_log(log_level: LogLevel) -> Outcome<()> { |
| 97 | let mut log_cfg = log_get_config!(); |
| 98 | log_cfg.level = log_level; |
| 99 | log_cfg.console = None; |
| 100 | let file_cfg = FileConfig::new( |
| 101 | res!(std::env::current_dir()), |
| 102 | "ironic".to_string(), |
| 103 | "log".to_string(), |
| 104 | 0, |
| 105 | Some(1_048_576), |
| 106 | ); |
| 107 | let log_path = file_cfg.path(); |
| 108 | log_cfg.file = Some(file_cfg); |
| 109 | log_set_config!(log_cfg); |
| 110 | info!("Logging at {:?}", log_path); |
| 111 | Ok(()) |
| 112 | } |
| 113 | |
| 114 | fn main() -> Outcome<()> { |
| 115 | |
| 116 | const NORMAL_LOG_THRESHOLD: LogLevel = LogLevel::Debug; |
| 117 | |
| 118 | let log_level = res!(LogLevel::from_str("debug")); |
| 119 | res!(setup_log(log_level)); |
| 120 | |
| 121 | let mut app = res!(App::new(None)); |
| 122 | |
| 123 | loop { |
| 124 | match if log_level <= NORMAL_LOG_THRESHOLD { |
| 125 | let mut app_logger = AppLoggerConsole::new(app.log.clone()); |
| 126 | let simplex_thread = app_logger.go(); |
| 127 | set_log_out!(simplex_thread); |
| 128 | run(&mut app, CrosstermRenderer::new(std::io::stdout())) |
| 129 | } else { |
| 130 | run(&mut app, DebugCrosstermRenderer::new(std::io::stdout())) |
| 131 | } { |
| 132 | Ok(()) => { |
| 133 | break; |
| 134 | } |
| 135 | Err(e) => { |
| 136 | error!(e); |
| 137 | //if log_level > NORMAL_LOG_THRESHOLD { |
| 138 | break; |
| 139 | //} |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | log_finish_wait!(); |
| 145 | Ok(()) |
| 146 | } |
| 147 | |
| 148 | fn run<S: Sink, R: Renderer<S>>( |
| 149 | app: &mut App, |
| 150 | renderer: R, |
| 151 | ) |
| 152 | -> Outcome<()> |
| 153 | { |
| 154 | trace!("Enter run"); |
| 155 | res!(app.make_windows(res!(renderer.size()))); |
| 156 | res!(app.win_mgr.set_focus_by_id(&WindowId::Help)); |
| 157 | |
| 158 | let mut drawer = Drawer::new(renderer, app.win_mgr.style_lib.clone(), true); |
| 159 | res!(drawer.on()); |
| 160 | |
| 161 | let mut refresh_render = true; |
| 162 | |
| 163 | //info!("Window list:"); |
| 164 | //for (index, name) in app.win_mgr.list_windows() { |
| 165 | // info!("{}{}: {}", if index == app.win_mgr.focus { ">" } else { " " }, index, name); |
| 166 | //} |
| 167 | |
| 168 | loop { |
| 169 | |
| 170 | let term = AbsRect::from(res!(drawer.rend.size())); |
| 171 | let data = ActionData { |
| 172 | term: Some(term), |
| 173 | ..Default::default() |
| 174 | }; |
| 175 | |
| 176 | // Render, if we need to. |
| 177 | if refresh_render { |
| 178 | res!(drawer.rend.clear()); |
| 179 | for window in app.win_mgr.iter_mut() { |
| 180 | if !window.focus { |
| 181 | res!(window.render(&mut drawer, When::Later)); |
| 182 | } |
| 183 | } |
| 184 | // Display the focal window last. |
| 185 | let result = app.win_mgr.get_focal_window_mut(); |
| 186 | let focal_window = res!(result); |
| 187 | res!(focal_window.render(&mut drawer, When::Later)); |
| 188 | |
| 189 | res!(app.win_mgr.draw_cursor(&mut drawer, When::Later)); |
| 190 | res!(drawer.rend.flush()); |
| 191 | refresh_render = false; |
| 192 | } |
| 193 | |
| 194 | if res!(crossterm::event::poll(std::time::Duration::from_millis( |
| 195 | constant::MAIN_LOOP_WAIT_MILLIS |
| 196 | ))) { |
| 197 | match res!(crossterm::event::read()) { |
| 198 | Event::Mouse(mouse_event) => { |
| 199 | app.mouse = (mouse_event.column, mouse_event.row); |
| 200 | //res!(app.append_log(&fmt!("Mouse: ({}, {})", app.mouse.0, app.mouse.1))); |
| 201 | //app.windows[0].rect.top_left = Coord::new(app.mouse); |
| 202 | } |
| 203 | Event::Key(key_event) => { |
| 204 | match app.handle_key_press(key_event, Some(data)) { |
| 205 | Ok(AppFlow::Quit) => { |
| 206 | res!(drawer.off()); |
| 207 | return Ok(()); |
| 208 | } |
| 209 | Err(e) => { |
| 210 | res!(drawer.off()); |
| 211 | return Err(e); |
| 212 | } |
| 213 | Ok(AppFlow::CursorOnly) => { |
| 214 | // Just update cursor position without full redraw. |
| 215 | let result = app.win_mgr.get_focal_window_text_box_mut(); |
| 216 | if let Some(tbox) = res!(result) { |
| 217 | tbox.tview.update_cursor(); |
| 218 | res!(drawer.rend.set_cursor(tbox.tview.term_cursor, When::Later)); |
| 219 | if let Some(cursor_style) = tbox.cfg.cursor_style { |
| 220 | res!(drawer.rend.set_cursor_style(cursor_style, When::Later)); |
| 221 | } |
| 222 | res!(app.win_mgr.draw_cursor(&mut drawer, When::Later)); |
| 223 | res!(drawer.rend.flush()); |
| 224 | } |
| 225 | } |
| 226 | Ok(AppFlow::FocalWindowRender) => { |
| 227 | let result = app.win_mgr.get_focal_window_mut(); |
| 228 | let focal_window = res!(result); |
| 229 | |
| 230 | //let term = AbsRect::from(res!(drawer.rend.size())); |
| 231 | //if let Some(win_rect) = focal_window.view.relative_to(term) { |
| 232 | // // win_view is within the terminal view. |
| 233 | // res!(drawer.rend.clear_rect(win_rect)); |
| 234 | //}; |
| 235 | |
| 236 | res!(focal_window.render(&mut drawer, When::Later)); |
| 237 | res!(app.win_mgr.draw_cursor(&mut drawer, When::Later)); |
| 238 | res!(drawer.rend.flush()); |
| 239 | } |
| 240 | Ok(AppFlow::FullScreenRender) => { |
| 241 | // This code is pulled out into the top of the loop so as to provide |
| 242 | // initialisation. |
| 243 | refresh_render = true; |
| 244 | } |
| 245 | _ => (), |
| 246 | } |
| 247 | } |
| 248 | _ => (), |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | } |