watcher/node/src/renderer/renderer.rs
2025-08-31 20:58:20 +02:00

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)
}
}
}
}