xref: /Held/src/utils/ui/uicore.rs (revision c775ed96190e893de2e6dd160a742b2329b8c15c)
1 use std::{
2     io,
3     sync::{atomic::AtomicU16, Arc, Mutex, MutexGuard, Once, RwLock, Weak},
4 };
5 
6 use crossterm::{
7     style::Color,
8     terminal::{self},
9 };
10 use lazy_static::lazy_static;
11 
12 use crate::{
13     config::appconfig::AppSetting,
14     utils::{
15         buffer::EditBuffer, cursor::CursorCrtl, style::StyleManager, terminal::TermManager,
16         ui::InfoLevel,
17     },
18 };
19 
20 #[cfg(feature = "dragonos")]
21 use crate::utils::input::Input;
22 
23 use super::{
24     mode::mode::{Command, InputMode, Insert, LastLine, ModeType},
25     AppInfo,
26 };
27 
28 lazy_static! {
29     static ref COMMAND: Arc<Command> = Arc::new(Command);
30     static ref INSERT: Arc<Insert> = Arc::new(Insert);
31     static ref LASTLINE: Arc<LastLine> = Arc::new(LastLine::new());
32     pub static ref APP_INFO: Mutex<AppInfo> = Mutex::new(AppInfo {
33         level: InfoLevel::Info,
34         info: String::new()
35     });
36 }
37 
38 pub static TAB_SIZE: AtomicU16 = AtomicU16::new(4);
39 
40 #[derive(Debug, Clone, Copy)]
41 pub struct WinSize {
42     pub cols: u16,
43     pub rows: u16,
44 }
45 
46 #[derive(Debug)]
47 pub struct UiCore {
48     pub buffer: Arc<EditBuffer>,
49     pub cursor: CursorCrtl,
50 
51     #[allow(dead_code)]
52     setting: AppSetting,
53     container: Weak<Ui>,
54 
55     edited: bool,
56     edited_once: Once,
57 }
58 
59 impl UiCore {
60     pub fn new(buf: Arc<EditBuffer>, cursor: CursorCrtl, setting: AppSetting) -> Self {
61         Self {
62             buffer: buf,
63             cursor,
64             container: Weak::new(),
65             setting,
66             edited: false,
67             edited_once: Once::new(),
68         }
69     }
70 
71     pub fn edited(&self) -> bool {
72         self.edited
73     }
74 
75     pub fn set_edited(&mut self) {
76         self.edited_once.call_once(|| self.edited = true)
77     }
78 
79     pub fn update_bottom_state_bar(&mut self) -> io::Result<()> {
80         let container = self.container.upgrade().unwrap();
81         let mode = container.mode.read().unwrap().mode_type();
82         if mode == ModeType::LastLine {
83             return Ok(());
84         }
85 
86         let size = *WINSIZE.read().unwrap();
87 
88         let store_x = self.cursor.x();
89         let store_y = self.cursor.y();
90 
91         self.cursor.set_prefix_mode(false);
92 
93         DEF_STYLE.read().unwrap().set_cmd_style()?;
94         let cmd_y = size.rows - 1;
95         self.cursor.move_to_row(cmd_y)?;
96         self.cursor.clear_current_line()?;
97         self.cursor
98             .write_with_pos(format!("{mode:?}"), 0, cmd_y, false)?;
99 
100         let (buf_x, buf_y) = (store_x, store_y + 1 + self.buffer.offset() as u16);
101         let index_info = format!("row:{buf_y} col:{buf_x}");
102         let len = index_info.len() as u16;
103         self.cursor
104             .write_with_pos(index_info, size.cols - len, cmd_y, false)?;
105 
106         self.cursor.set_prefix_mode(true);
107         self.cursor.move_to(store_x, store_y)?;
108 
109         let mut info = APP_INFO.lock().unwrap();
110         info.level.set_style()?;
111         self.cursor
112             .write_with_pos(&info.info, size.cols / 3, cmd_y, false)?;
113 
114         info.reset();
115         self.cursor.move_to(store_x, store_y)?;
116 
117         StyleManager::reset_color()?;
118 
119         Ok(())
120     }
121 
122     /// 渲染部分文件内容,从y行开始渲染count行
123     /// 返回实际渲染行数
124     pub fn render_content(&mut self, mut y: u16, mut count: usize) -> io::Result<usize> {
125         y += UI_HEAD_OFFSET;
126         let content_winsize = *CONTENT_WINSIZE.read().unwrap();
127 
128         // 超出正文范围
129         if y + count as u16 > content_winsize.rows {
130             count = (content_winsize.rows - y) as usize;
131         }
132 
133         let def_style = *DEF_STYLE.read().unwrap();
134 
135         let content = self.buffer.get_content(y as usize, count);
136 
137         if content.is_none() {
138             return Ok(0);
139         }
140         let content = content.unwrap();
141 
142         // 保存光标
143         let pos = self.cursor.store_tmp_pos();
144 
145         let tmp = y;
146 
147         let num_len = (tmp + content_winsize.rows).to_string().len();
148 
149         self.cursor.set_prefix_mode(false);
150         for line in content.iter() {
151             let str = String::from_utf8_lossy(&line.data).to_string();
152             def_style.set_content_style()?;
153 
154             // 移动
155             self.cursor
156                 .move_to(num_len as u16 + 2 + CursorCrtl::PREFIX_COL, y)?;
157             self.cursor.clear_current_line()?;
158             self.cursor.write(str)?;
159             y += 1;
160             StyleManager::reset_color()?;
161         }
162 
163         self.cursor.update_line_prefix(&content, tmp, num_len)?;
164         self.cursor.set_prefix_mode(true);
165 
166         self.cursor.restore_tmp_pos(pos)?;
167 
168         self.cursor.highlight(None)?;
169 
170         Ok(content.len())
171     }
172 
173     // 将正文向上滚动count行
174     pub fn scroll_up(&mut self, mut count: u16) -> io::Result<()> {
175         let winsize = *CONTENT_WINSIZE.read().unwrap();
176 
177         let pos = self.cursor.store_tmp_pos();
178 
179         // 计算最多还能滚动多少行
180         let offset = self.buffer.offset();
181 
182         // 最多出两行
183         let linecount = self.buffer.line_count();
184         if offset + winsize.rows as usize + count as usize >= linecount {
185             count = linecount as u16 - offset as u16 - winsize.rows;
186         }
187         self.buffer.set_offset(offset + count as usize);
188         // 将光标移动到滚动后的位置
189         self.cursor.move_to_row(winsize.rows - count)?;
190 
191         // 执行滚动
192         TermManager::scroll_up(count)?;
193 
194         // 清除光标以下的内容
195         TermManager::clear_under_cursor()?;
196 
197         // 渲染count行数据
198         self.render_content(self.cursor.y(), count as usize)?;
199 
200         self.cursor.restore_tmp_pos(pos)?;
201 
202         self.cursor.highlight(Some(self.cursor.y() - count))?;
203         Ok(())
204     }
205 
206     pub fn scroll_down(&mut self, mut count: u16) -> io::Result<()> {
207         let pos = self.cursor.store_tmp_pos();
208 
209         // 计算最多还能滚动多少行
210         let offset = self.buffer.offset();
211         if offset < count as usize {
212             count = offset as u16;
213         }
214 
215         self.buffer.set_offset(offset - count as usize);
216 
217         // 执行滚动
218         TermManager::scroll_down(count)?;
219 
220         // 将光标移动第count行
221         self.cursor.move_to_row(count - 1)?;
222         // 清除光标以上的内容
223         TermManager::clear_up_cursor()?;
224 
225         // 渲染count行数据
226         self.render_content(0, count as usize)?;
227 
228         self.cursor.restore_tmp_pos(pos)?;
229 
230         self.cursor.highlight(Some(self.cursor.y() + count))?;
231 
232         Ok(())
233     }
234 }
235 
236 #[derive(Debug)]
237 pub struct Ui {
238     pub core: Arc<Mutex<UiCore>>,
239     pub mode: RwLock<Arc<dyn InputMode>>,
240 }
241 
242 lazy_static! {
243     pub static ref WINSIZE: RwLock<WinSize> = {
244         let size = terminal::size().unwrap();
245         RwLock::new(WinSize {
246             cols: size.0,
247             rows: size.1,
248         })
249     };
250     pub static ref CONTENT_WINSIZE: RwLock<WinSize> = {
251         let size = *WINSIZE.read().unwrap();
252         RwLock::new(WinSize {
253             cols: size.cols,
254             rows: size.rows - UI_CMD_HEIGHT - UI_HEAD_OFFSET,
255         })
256     };
257     pub static ref DEF_STYLE: RwLock<UiStyle> = {
258         let style = UiStyle {
259             content_fg: Some(Color::White),
260             content_bg: None,
261             cmd_line_fg: Some(Color::White),
262             cmd_line_bg: Some(Color::DarkCyan),
263         };
264 
265         RwLock::new(style)
266     };
267 }
268 
269 pub static UI_HEAD_OFFSET: u16 = 0;
270 pub const UI_CMD_HEIGHT: u16 = 1;
271 
272 impl Ui {
273     pub fn new(buf: Arc<EditBuffer>, setting: AppSetting) -> Arc<Self> {
274         let mut cursor = CursorCrtl::new(buf.clone(), setting.line);
275         cursor.move_to(0, 0).unwrap();
276 
277         let core = Arc::new(Mutex::new(UiCore::new(buf, cursor, setting)));
278         let ret = Arc::new(Self {
279             mode: RwLock::new(Arc::new(Command)),
280             core: core.clone(),
281         });
282 
283         core.lock().unwrap().container = Arc::downgrade(&ret);
284 
285         ret
286     }
287     pub fn init_ui() -> io::Result<()> {
288         TermManager::init_term()?;
289         Ok(())
290     }
291 
292     pub fn start_page_ui(&self) -> io::Result<()> {
293         StyleManager::set_foreground_color(Color::Cyan)?;
294         let mut core = self.core.lock().unwrap();
295         core.cursor
296             .write_with_pos("Held - DragonOS/Linux Term Editor\n", 5, 0, false)?;
297         StyleManager::set_foreground_color(Color::Green)?;
298         core.cursor
299             .write_with_pos("Author: heyicong@dragonos.org\n", 7, 1, false)?;
300         StyleManager::set_foreground_color(Color::DarkMagenta)?;
301         core.cursor
302             .write_with_pos("Type any key to continue ><\n", 8, 2, false)?;
303         StyleManager::reset_color()?;
304 
305         core.cursor.move_to(0, 0)?;
306 
307         #[cfg(feature = "dragonos")]
308         let _ = Input::wait_keydown();
309 
310         #[cfg(not(feature = "dragonos"))]
311         loop {
312             let ev = crossterm::event::read()?;
313             if let crossterm::event::Event::Key(_) = ev {
314                 break;
315             }
316         }
317 
318         TermManager::clear_all()?;
319 
320         Ok(())
321     }
322 
323     pub fn ui_loop(&self) -> io::Result<bool> {
324         let mut core = self.core.lock().unwrap();
325         core.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?;
326         core.update_bottom_state_bar()?;
327         core.cursor.move_to(0, 0)?;
328         core.cursor.highlight(None)?;
329         loop {
330             #[cfg(feature = "dragonos")]
331             let callback = {
332                 let key = Input::wait_keydown()?;
333                 self.mode.read().unwrap().key_event_route(&mut core, key)?
334             };
335 
336             #[cfg(not(feature = "dragonos"))]
337             let callback = {
338                 let ev = crossterm::event::read()?;
339                 self.mode.read().unwrap().event_route(&mut core, ev)?
340             };
341 
342             match callback {
343                 super::event::WarpUiCallBackType::ChangMode(mode) => {
344                     self.set_mode(mode, &mut core)?
345                 }
346                 super::event::WarpUiCallBackType::None => {}
347                 super::event::WarpUiCallBackType::Exit(store) => {
348                     self.ui_exit();
349                     return Ok(store);
350                 }
351             }
352 
353             if self.mode.read().unwrap().mode_type() != ModeType::LastLine {
354                 core.update_bottom_state_bar()?;
355             }
356         }
357     }
358 
359     fn set_mode(&self, mode: ModeType, ui: &mut MutexGuard<UiCore>) -> io::Result<()> {
360         if mode != ModeType::LastLine {
361             ui.cursor.set_prefix_mode(true);
362 
363             ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?;
364         }
365         match mode {
366             ModeType::Command => *self.mode.write().unwrap() = COMMAND.clone(),
367             ModeType::LastLine => {
368                 ui.cursor.set_prefix_mode(false);
369                 let lastline = LASTLINE.clone();
370                 lastline.reset();
371                 *self.mode.write().unwrap() = lastline;
372 
373                 ui.cursor.move_to(0, u16::MAX - 1)?;
374                 DEF_STYLE.read().unwrap().set_cmd_style()?;
375                 // 写一个空行
376                 ui.cursor.clear_current_line()?;
377                 ui.cursor.move_to_columu(0)?;
378                 ui.cursor.write(':')?;
379             }
380             ModeType::Insert => *self.mode.write().unwrap() = INSERT.clone(),
381         }
382 
383         Ok(())
384     }
385 
386     fn ui_exit(&self) {
387         // 处理未保存退出时的提醒
388 
389         // 解决退出程序后颜色没改变的问题
390         StyleManager::reset_color().unwrap();
391     }
392 }
393 
394 #[derive(Debug, Clone, Copy)]
395 pub struct UiStyle {
396     pub content_fg: Option<Color>,
397     pub content_bg: Option<Color>,
398     pub cmd_line_fg: Option<Color>,
399     pub cmd_line_bg: Option<Color>,
400 }
401 
402 impl UiStyle {
403     pub fn set_cmd_style(&self) -> io::Result<()> {
404         StyleManager::reset_color()?;
405         if self.cmd_line_bg.is_some() {
406             StyleManager::set_background_color(self.cmd_line_bg.unwrap())?;
407         }
408         if self.cmd_line_fg.is_some() {
409             StyleManager::set_foreground_color(self.cmd_line_fg.unwrap())?;
410         }
411 
412         Ok(())
413     }
414 
415     pub fn set_content_style(&self) -> io::Result<()> {
416         StyleManager::reset_color()?;
417         if self.content_bg.is_some() {
418             StyleManager::set_background_color(self.content_bg.unwrap())?;
419         }
420         if self.content_fg.is_some() {
421             StyleManager::set_foreground_color(self.content_fg.unwrap())?;
422         }
423 
424         Ok(())
425     }
426 }
427