// This file is part of Deja-vu. // // Deja-vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Deja-vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // use color_eyre::Result; use crossterm::event::KeyEvent; use ratatui::{ prelude::*, widgets::{Block, Borders, Padding, Paragraph, Wrap}, }; use tokio::sync::mpsc::UnboundedSender; use super::{state::StatefulList, Component}; use crate::{action::Action, config::Config, line::DVLine}; #[derive(Default)] pub struct Right { command_tx: Option>, config: Config, list_header: Vec, list_labels: Vec>, list: StatefulList>, selections: Vec>, selections_saved: Vec>, title: String, } impl Right { pub fn new() -> Self { Self { selections: vec![None, None], selections_saved: vec![None, None], title: String::from("Info"), ..Default::default() } } fn get_first(&self) -> Option { if self.selections_saved[0].is_some() { self.selections_saved[0] } else if self.selections[0].is_some() { self.selections[0] } else { self.list.selected() } } fn get_second(&self) -> Option { if self.selections_saved[1].is_some() { self.selections_saved[1] } else if self.selections[1].is_some() { self.selections[1] } else if self.selections[0].is_some() && self.selections[0] != self.list.selected() { self.list.selected() } else { None } } fn set_highlight(&mut self, index: usize) { self.list.select(index); } } impl Component for Right { fn handle_key_event(&mut self, key: KeyEvent) -> Result> { let _ = key; // to appease clippy Ok(None) } fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { self.command_tx = Some(tx); Ok(()) } fn register_config_handler(&mut self, config: Config) -> Result<()> { self.config = config; Ok(()) } fn update(&mut self, action: Action) -> Result> { match action { Action::Highlight(index) => self.set_highlight(index), Action::KeyUp => self.list.previous(), Action::KeyDown => self.list.next(), Action::Select(one, two) => { self.selections[0] = one; self.selections[1] = two; if self.selections_saved[1].is_none() { // Update selections_saved // Conversely, if both selections set then preserve previous choices self.selections_saved[0] = one; self.selections_saved[1] = two; } } Action::SelectRight(one, two) => { self.selections_saved[0] = one; self.selections_saved[1] = two; } Action::SetMode(_) => { self.selections[0] = None; self.selections[1] = None; self.selections_saved[0] = None; self.selections_saved[1] = None; } Action::UpdateRight(labels, start_index, list) => { self.list_header = list[..start_index].iter().flatten().cloned().collect(); self.list_labels = labels; self.list.set_items(list[start_index..].to_vec()); } _ => {} } Ok(None) } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { let [title_area, body_area] = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(1), Constraint::Min(1)]) .areas(area); // Title let title = Paragraph::new(Line::from(self.title.as_str()).centered()) .block(Block::default().borders(Borders::NONE)); frame.render_widget(title, title_area); // Body let mut body_text: Vec = Vec::new(); if !self.list_header.is_empty() { // Static Header self.list_header .iter() .for_each(|dv| body_text.push(dv.as_line())); body_text.push(Line::from("")); body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); body_text.push(Line::from("")); } // First selection if let Some(first_index) = self.get_first() { if let Some(first_desc) = self.list.get(first_index) { if let Some(label) = self.list_labels.get(0) { label .iter() .for_each(|dv| body_text.push(dv.as_line().bold())); } body_text.push(Line::from("")); first_desc .iter() .for_each(|dv| body_text.push(dv.as_line())); } } // Second selection if let Some(second_index) = self.get_second() { if let Some(second_desc) = self.list.get(second_index) { // Divider body_text.push(Line::from("")); body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); body_text.push(Line::from("")); if let Some(label) = self.list_labels.get(1) { label .iter() .for_each(|dv| body_text.push(dv.as_line().bold())); } body_text.push(Line::from("")); second_desc .iter() .for_each(|dv| body_text.push(dv.as_line())); } } // Build Paragraph let body = Paragraph::new(body_text) .style(Style::default().fg(Color::Gray)) .wrap(Wrap { trim: false }) .block( Block::default() .borders(Borders::ALL) .padding(Padding::new(1, 1, 1, 1)), ); frame.render_widget(body, body_area); // Done Ok(()) } }