// 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, HighlightSpacing, List, ListItem, Padding, Paragraph}, }; use tokio::sync::mpsc::UnboundedSender; use super::{state::StatefulList, Component}; use crate::{action::Action, config::Config}; #[derive(Default)] pub struct Left { command_tx: Option>, config: Config, labels: Vec, list: StatefulList, select_one: bool, selections: Vec>, selections_saved: Vec>, title_text: String, } impl Left { pub fn new() -> Self { Self { select_one: false, labels: vec![String::from("one"), String::from("two")], selections: vec![None, None], selections_saved: vec![None, None], title_text: String::from("Home"), ..Default::default() } } fn set_highlight(&mut self, index: usize) { self.list.select(index); } } impl Component for Left { 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::Process => { if let Some(command_tx) = self.command_tx.clone() { match (self.selections[0], self.selections[1]) { (None, None) => { // Making first selection command_tx.send(Action::Select(self.list.selected(), None))?; if self.select_one { // Confirm selection command_tx.send(Action::NextScreen)?; } } (Some(first_index), None) => { if let Some(second_index) = self.list.selected() { // Making second selection if first_index == second_index { // Toggle first selection command_tx.send(Action::Select(None, None))?; } else { // Both selections made, proceed to next screen command_tx.send(Action::Select( Some(first_index), Some(second_index), ))?; command_tx.send(Action::NextScreen)?; } } } _ => panic!("This shouldn't happen?"), } } } Action::Select(one, two) => { self.selections[0] = one; self.selections[1] = two; self.selections_saved[0] = one; self.selections_saved[1] = two; } Action::SetMode(_) => { self.selections[0] = None; self.selections[1] = None; } Action::UpdateLeft(title, labels, items, select_one) => { self.title_text = title; self.labels = labels .iter() .map(|label| format!(" ~{}~", label.to_lowercase())) .collect(); self.list.set_items(items); self.select_one = select_one; } _ => {} } 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_text = if self.selections[1].is_some() || self.select_one { "Confirm Selections" } else { self.title_text.as_str() }; let title = Paragraph::new(Line::from(Span::styled(title_text, Style::default())).centered()) .block(Block::default().borders(Borders::NONE)); frame.render_widget(title, title_area); // Body (Blank) if self.list.is_empty() { // Leave blank let paragraph = Paragraph::new(String::new()).block( Block::default() .borders(Borders::ALL) .padding(Padding::new(1, 1, 1, 1)), ); frame.render_widget(paragraph, body_area); // Bail early return Ok(()); } // Body let list_items: Vec = self .list .items .iter() .enumerate() .map(|(index, item)| { let mut style = Style::default(); let text = if self.selections[0].is_some_and(|first_index| first_index == index) { if let Some(label) = self.labels.get(0) { style = style.yellow(); label.as_str() } else { "" } } else if self.selections[1].is_some_and(|second_index| second_index == index) { if let Some(label) = self.labels.get(1) { style = style.yellow(); label.as_str() } else { "" } } else { "" }; ListItem::new(format!(" {item}\n{text}\n\n")).style(style) }) .collect(); let list = List::new(list_items) .block( Block::default() .borders(Borders::ALL) .padding(Padding::new(1, 1, 1, 1)), ) .highlight_spacing(HighlightSpacing::Always) .highlight_style(Style::new().green().bold()) .highlight_symbol(" --> ") .repeat_highlight_symbol(false); frame.render_stateful_widget(list, body_area, &mut self.list.state); // Done Ok(()) } }