308 lines
7.9 KiB
Rust
308 lines
7.9 KiB
Rust
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<Vec<String>>, 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<S: AsRef<str>>(&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<RenderCommand>,
|
|
) -> Result<RenderCommand, ()> {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|