// 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 core::{action::Action, components::Component, config::Config, state::Mode, tui::Event}; use crossterm::event::KeyCode; use ratatui::{ prelude::*, widgets::{Block, Borders, Clear, Paragraph}, }; use tokio::sync::mpsc::UnboundedSender; use tui_input::{Input, InputRequest}; #[derive(Default)] pub struct InputUsername { command_tx: Option>, config: Config, input: Input, mode: Mode, } impl InputUsername { #[must_use] pub fn new() -> Self { Self { input: Input::new(String::from("")), ..Default::default() } } } impl Component for InputUsername { 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 handle_events(&mut self, event: Option) -> Result> { if self.mode != Mode::SetUserName { return Ok(None); } let action = match event { Some(Event::Key(key_event)) => match key_event.code { KeyCode::Backspace => { self.input.handle(InputRequest::DeletePrevChar); None } KeyCode::Char(c) => { let ok_chars: Vec = vec![' ', '-', '_']; if c.is_ascii_alphanumeric() || ok_chars.contains(&c) { self.input.handle(InputRequest::InsertChar(c)); } None } KeyCode::Enter => { let username = self.input.value(); Some(Action::SetUserName(String::from(username))) } KeyCode::Esc => Some(Action::SetMode(Mode::Home)), _ => None, }, Some(Event::Mouse(_)) => None, _ => None, }; Ok(action) } fn update(&mut self, action: Action) -> Result> { match action { Action::SetMode(mode) => self.mode = mode.clone(), _ => {} } Ok(None) } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { if self.mode != Mode::SetUserName { // Bail early return Ok(()); } // Set areas let [_, center_area, _] = Layout::horizontal([ Constraint::Length(1), Constraint::Min(1), Constraint::Length(1), ]) .areas(area); let [_, input_area, _] = Layout::vertical([ Constraint::Length(1), Constraint::Length(3), Constraint::Length(1), ]) .areas(center_area); frame.render_widget(Clear, area); let outer_block = Block::bordered().cyan().bold(); frame.render_widget(outer_block, area); // Input Box let width = input_area.width.max(3) - 3; // keep 2 for borders and 1 for cursor let scroll = self.input.visual_scroll(width as usize); let input = Paragraph::new(self.input.value()) .scroll((0, scroll as u16)) .white() .block(Block::new().borders(Borders::ALL).title("Enter Username")); frame.render_widget(input, input_area); // Ratatui hides the cursor unless it's explicitly set. Position the cursor past the // end of the input text and one line down from the border to the input line let x = self.input.visual_cursor().max(scroll) - scroll + 1; frame.set_cursor_position((input_area.x + x as u16, input_area.y + 1)); // Done Ok(()) } }