use std::sync::Arc; use crossterm::event::KeyCode; use ratatui::{Frame, buffer::Buffer, layout::Rect, widgets::Widget}; use vlogger::*; use tokio::time::{Duration, timeout}; use crate::log; use super::*; #[derive(Debug, Clone)] pub enum InputMode { Input, PopUp(Arc>, String, usize), } #[derive(Debug)] pub struct Renderer { buffer: String, exit: bool, layout: RenderLayout, mode: InputMode, } #[derive(Clone, Debug)] pub enum RenderCommand { RenderStringToPaneId { str: String, pane: RenderTarget, }, RenderStringToPaneFocused { str: String, }, RenderKeyInput(KeyCode), ListMove { pane: RenderTarget, index: usize, }, ChangeLayout(RenderLayoutKind), ClearPane, /// Mouse Events MouseClickLeft(u16, u16), MouseScrollUp, MouseScrollDown, SetMode(InputMode), Exit, } #[allow(dead_code)] impl Renderer { pub fn new(layout: RenderLayoutKind) -> Self { Self { buffer: String::new(), exit: false, layout: RenderLayout::generate(layout), mode: InputMode::Input, } } fn log(&mut self, msg: String) { self.buffer.push_str(&msg) } pub fn draw(&mut self, frame: &mut Frame) { frame.render_widget(self, frame.area()); } fn exit(&mut self) { log!(DEBUG, "Renderer Exit"); self.exit = true; } fn buffer_extend>(&mut self, input: S) { self.buffer.push_str(input.as_ref()); } fn input_pane(&mut self) -> Option<&mut Pane> { self .layout .panes .iter_mut() .find(|p| p.target == RenderTarget::CliInput) } fn get_pane(&mut self, pane: RenderTarget) -> Option<&mut Pane> { self.layout.panes.iter_mut().find(|p| p.target == pane) } fn focused(&mut self) -> Option<&mut Pane> { self.layout.panes.iter_mut().find(|p| p.focused == true) } pub fn rects(&self, area: Rect) -> std::rc::Rc<[Rect]> { self.layout.rects(area) } pub fn handle_mouse_click_left(&mut self, x: u16, y: u16, rects: std::rc::Rc<[Rect]>) { for (i, r) in rects.iter().enumerate() { if r.contains(ratatui::layout::Position { x, y }) { self.layout.panes[i].focused = true; } else { self.layout.panes[i].focused = false; } } } pub fn handle_scroll_up(&mut self) { if let Some(p) = self.focused() { if p.scroll < p.max_scroll { p.scroll += 1; } } } pub fn handle_scroll_down(&mut self) { if let Some(p) = self.focused() { if p.scroll > i16::MIN { p.scroll -= 1; } } } pub fn handle_char_input(&mut self, c: char) { if let Some(p) = self.input_pane() { if let RenderBuffer::List { list, index, .. } = &mut p.buffer { list[*index].push(c); } } } pub fn handle_backspace(&mut self) { if let Some(p) = self.input_pane() { if let RenderBuffer::List { list, index, .. } = &mut p.buffer { list[*index].pop(); } } } pub fn handle_enter(&mut self) { if let Some(p) = self.input_pane() { if let RenderBuffer::List { list, index, .. } = &mut p.buffer { list.push(String::new()); *index += 1; } } } pub fn handle_arrow_key(&mut self, key: KeyCode) { match &mut self.mode { InputMode::Input => {} InputMode::PopUp(content, .., idx) => { log(msg!(DEBUG, "Received keycode: {key}")); log(msg!(DEBUG, "idx before: {idx}")); match key { KeyCode::Up => { *idx = idx.saturating_sub(1) } KeyCode::Down => { if *idx < content.len().saturating_sub(1) { *idx += 1; } } _ => {} } log(msg!(DEBUG, "idx after: {idx}")) } } if let Some(pane) = self.focused() { match &pane.target { RenderTarget::CliInput => {} RenderTarget::CliOutput => {} _ => {} } } } pub fn list_move(&mut self, pane: RenderTarget, index: usize) { if let Some(p) = self.get_pane(pane) { if let RenderBuffer::List { list, index: idx, .. } = &mut p.buffer { if index > 0 && index < list.len() { list[*idx] = list[index].clone(); } } } } pub fn render_string_to_focused(&mut self, str: String) { if let Some(p) = self.focused() { match &mut p.buffer { RenderBuffer::List { list, index, .. } => { list.push(str); *index += 1; } RenderBuffer::String(s) => s.push_str(&str), _ => {} } } } pub fn render_string_to_id(&mut self, str: String, pane: RenderTarget) { if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { match &mut p.buffer { RenderBuffer::List { list, index, .. } => { list.push(str); *index += 1; } RenderBuffer::String(s) => s.push_str(&str), _ => {} } } } pub fn clear_pane(&mut self, pane: RenderTarget) { if matches!(pane, RenderTarget::All) { for p in self.layout.panes.iter_mut() { match &mut p.buffer { RenderBuffer::List { list, index, .. } => { list.clear(); *index = 0; list.push(String::new()); } RenderBuffer::String(s) => s.clear(), _ => {} } } } else if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { match &mut p.buffer { RenderBuffer::List { list, index, .. } => { list.clear(); *index = 0; list.push(String::new()); } RenderBuffer::String(s) => s.clear(), _ => {} } } } pub fn apply(&mut self, mes: RenderCommand, area: Rect) { let rects = self.layout.rects(area); match mes { RenderCommand::MouseClickLeft(x, y) => self.handle_mouse_click_left(x, y, rects), RenderCommand::MouseScrollUp => self.handle_scroll_up(), RenderCommand::MouseScrollDown => self.handle_scroll_down(), RenderCommand::RenderKeyInput(k) => match k { KeyCode::Char(c) => self.handle_char_input(c), KeyCode::Backspace => self.handle_backspace(), KeyCode::Enter => self.handle_enter(), KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right => self.handle_arrow_key(k), _ => {} }, RenderCommand::ListMove { pane, index } => self.list_move(pane, index), RenderCommand::RenderStringToPaneFocused { str } => self.render_string_to_focused(str), RenderCommand::RenderStringToPaneId { str, pane } => self.render_string_to_id(str, pane), RenderCommand::Exit => self.exit(), RenderCommand::ChangeLayout(l) => self.layout = RenderLayout::generate(l), RenderCommand::ClearPane => self.clear_pane(RenderTarget::All), RenderCommand::SetMode(mode) => { match &mode { InputMode::Input => { if let InputMode::PopUp(..) = self.mode { self.layout.panes.pop(); } } InputMode::PopUp(content, title, ..) => { let pane = Pane::new( Some(title.to_string()), RenderTarget::PopUp, RenderBuffer::Select(content.clone(), 0), true, ); self.layout.panes.push(pane); } } self.mode = mode }, } } async fn listen( &mut self, rx: &mut tokio::sync::broadcast::Receiver, ) -> Result { if let Ok(Ok(mes)) = timeout(Duration::from_millis(400), rx.recv()).await { return Ok(mes); } Err(()) } } impl Widget for &mut Renderer { fn render(self, area: Rect, buf: &mut Buffer) { let rects = self.layout.rects(area); for (i, p) in self.layout.panes.iter_mut().enumerate() { if p.target == RenderTarget::PopUp { p.render(area, buf); } else { p.render(rects[i], buf) } } } }