Add textarea support
This commit is contained in:
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -1442,6 +1442,15 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.37.3"
|
version = "0.37.3"
|
||||||
@@ -1734,6 +1743,7 @@ dependencies = [
|
|||||||
"lru",
|
"lru",
|
||||||
"paste",
|
"paste",
|
||||||
"strum",
|
"strum",
|
||||||
|
"time",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-truncate",
|
"unicode-truncate",
|
||||||
"unicode-width 0.2.0",
|
"unicode-width 0.2.0",
|
||||||
@@ -2232,6 +2242,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tokio-utils",
|
"tokio-utils",
|
||||||
|
"tui-input",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2348,7 +2359,9 @@ checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libc",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
|
"num_threads",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
@@ -2568,6 +2581,16 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui-input"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "911e93158bf80bbc94bad533b2b16e3d711e1132d69a6a6980c3920a63422c19"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm 0.29.0",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.19"
|
version = "1.0.19"
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -8,7 +8,6 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
ratatui = "0.29.0"
|
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
tokio-util = "0.7.9"
|
tokio-util = "0.7.9"
|
||||||
tokio-utils = "0.1.2"
|
tokio-utils = "0.1.2"
|
||||||
@@ -18,6 +17,19 @@ rust-ini = "0.21.3"
|
|||||||
robotstxt = "0.3.0"
|
robotstxt = "0.3.0"
|
||||||
scraper = "0.24.0"
|
scraper = "0.24.0"
|
||||||
|
|
||||||
|
[dependencies.tui-input]
|
||||||
|
version = "0.14.0"
|
||||||
|
features = ["crossterm"]
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.crossterm]
|
||||||
|
version = "0.29.0"
|
||||||
|
features = ["event-stream"]
|
||||||
|
|
||||||
|
[dependencies.ratatui]
|
||||||
|
version = "0.29.0"
|
||||||
|
features = ["all-widgets"]
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4.5.48"
|
version = "4.5.48"
|
||||||
features = ["derive", "cargo"]
|
features = ["derive", "cargo"]
|
||||||
@@ -26,10 +38,6 @@ features = ["derive", "cargo"]
|
|||||||
version = "0.12.23"
|
version = "0.12.23"
|
||||||
features = ["blocking"]
|
features = ["blocking"]
|
||||||
|
|
||||||
[dependencies.crossterm]
|
|
||||||
version = "0.29.0"
|
|
||||||
features = ["event-stream"]
|
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.47.1"
|
version = "1.47.1"
|
||||||
features = ["full"]
|
features = ["full"]
|
||||||
|
|||||||
108
src/app.rs
108
src/app.rs
@@ -1,19 +1,22 @@
|
|||||||
use crate::event::{Event, EventHandler};
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::event::{AppEvent, EventHandler};
|
||||||
use ratatui::widgets::{Block, Borders, Paragraph};
|
use ratatui::widgets::{Block, Borders, Paragraph};
|
||||||
use ratatui::{DefaultTerminal};
|
use ratatui::{DefaultTerminal, Frame};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossterm::event;
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
||||||
use crossterm::event::KeyCode;
|
|
||||||
use crossterm::event::KeyCode::Char;
|
use crossterm::event::KeyCode::Char;
|
||||||
|
use crossterm::event::Event as CrosstermEvent;
|
||||||
use diesel::{Connection, SqliteConnection};
|
use diesel::{Connection, SqliteConnection};
|
||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
use ratatui::prelude::{Widget};
|
use ratatui::prelude::{StatefulWidget, Widget};
|
||||||
use ratatui::style::{Color, Style};
|
use ratatui::style::{Color, Style};
|
||||||
use ratatui::text::{Line, Span, Text};
|
use ratatui::text::{Line, Span, Text};
|
||||||
use crate::config::types::ApplicationConfig;
|
use crate::config::types::ApplicationConfig;
|
||||||
use crate::constants::{APP_CONFIG_DIR, APP_CONIFG_FILE_PATH, APP_DATA_DIR};
|
use crate::constants::{APP_CONFIG_DIR, APP_CONIFG_FILE_PATH, APP_DATA_DIR};
|
||||||
|
use crate::widgets::textarea::TextArea;
|
||||||
|
|
||||||
enum AppState {
|
enum AppState {
|
||||||
Running,
|
Running,
|
||||||
@@ -32,15 +35,16 @@ pub(crate) struct App {
|
|||||||
events: EventHandler,
|
events: EventHandler,
|
||||||
db_connection: SqliteConnection,
|
db_connection: SqliteConnection,
|
||||||
app_config: ApplicationConfig,
|
app_config: ApplicationConfig,
|
||||||
current_event: Option<Event>
|
stateful_widgets: HashMap<String, Box<dyn Any>>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Self {
|
pub async fn create() -> Self {
|
||||||
let app_conf =
|
let app_conf =
|
||||||
if APP_CONIFG_FILE_PATH.exists() { ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH).unwrap() }
|
if APP_CONIFG_FILE_PATH.exists() { ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH).unwrap() }
|
||||||
else { ApplicationConfig::new() };
|
else { ApplicationConfig::new() };
|
||||||
Self::initialize();
|
Self::initialize_folders();
|
||||||
let db_conn = Self::establish_db_connection(app_conf.clone());
|
let db_conn = Self::establish_db_connection(app_conf.clone());
|
||||||
Self {
|
Self {
|
||||||
state: AppState::Running,
|
state: AppState::Running,
|
||||||
@@ -48,11 +52,11 @@ impl App {
|
|||||||
events: EventHandler::new(Duration::from_millis(app_conf.basic_config.tick_rate)),
|
events: EventHandler::new(Duration::from_millis(app_conf.basic_config.tick_rate)),
|
||||||
db_connection: db_conn,
|
db_connection: db_conn,
|
||||||
app_config: app_conf,
|
app_config: app_conf,
|
||||||
current_event: None
|
stateful_widgets: HashMap::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize() {
|
fn initialize_folders() {
|
||||||
if !APP_CONFIG_DIR.exists() {
|
if !APP_CONFIG_DIR.exists() {
|
||||||
std::fs::create_dir_all(APP_CONFIG_DIR.as_path()).unwrap();
|
std::fs::create_dir_all(APP_CONFIG_DIR.as_path()).unwrap();
|
||||||
}
|
}
|
||||||
@@ -67,9 +71,9 @@ impl App {
|
|||||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
pub async fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
|
terminal.draw(|frame| self.draw(frame))?;
|
||||||
let event = self.events.next().await?;
|
let event = self.events.next().await?;
|
||||||
self.update(event)?;
|
self.update(event)?;
|
||||||
if matches!(self.state, AppState::Exiting) {
|
if matches!(self.state, AppState::Exiting) {
|
||||||
@@ -78,20 +82,27 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, event: Event) -> Result<()> {
|
fn update(&mut self, event: AppEvent) -> Result<()> {
|
||||||
self.current_event = Some(event.clone());
|
if matches!(self.state, AppState::Input) && let AppEvent::Raw(raw) = &event {
|
||||||
if let Event::Key(key) = event && matches!(self.state, AppState::Input) {
|
self.stateful_widgets.iter_mut()
|
||||||
match key.code {
|
.map(|(_, widget)| widget.downcast_mut::<TextArea>())
|
||||||
KeyCode::Esc => {
|
.filter(|widget| widget.is_some())
|
||||||
self.state = AppState::Running;
|
.map(|widget| widget.unwrap())
|
||||||
},
|
.filter(|widget| widget.active)
|
||||||
_ => {}
|
.for_each(|textarea| textarea.handle_input(raw));
|
||||||
}
|
}
|
||||||
|
if let AppEvent::Raw(cross_event) = event &&
|
||||||
|
let CrosstermEvent::Key(key) = cross_event{
|
||||||
|
self.handle_key_event(&key)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Key(key) = event &&
|
fn handle_key_event(&mut self, key: &KeyEvent) -> Result<()> {
|
||||||
event::KeyEventKind::is_press(&key.kind) &&
|
if matches!(self.state, AppState::Input) && matches!(key.code, KeyCode::Esc) {
|
||||||
!matches!(self.state, AppState::Input) {
|
self.state = AppState::Running;
|
||||||
|
}
|
||||||
|
if matches!(key.kind, KeyEventKind::Press) && !matches!(self.state, AppState::Input) {
|
||||||
match key.code {
|
match key.code {
|
||||||
Char('q') => self.quit()?,
|
Char('q') => self.quit()?,
|
||||||
Char('a') => self.folder_popup(),
|
Char('a') => self.folder_popup(),
|
||||||
@@ -100,6 +111,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, frame: &mut Frame) {
|
||||||
|
frame.render_widget(self, frame.area())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &mut App {
|
impl Widget for &mut App {
|
||||||
@@ -119,15 +134,8 @@ impl Widget for &mut App {
|
|||||||
self.render_game_list(chunks[1], buf);
|
self.render_game_list(chunks[1], buf);
|
||||||
self.render_footer(chunks[2], buf);
|
self.render_footer(chunks[2], buf);
|
||||||
|
|
||||||
let popup_area = Rect {
|
|
||||||
x: area.width / 4,
|
|
||||||
y: area.height / 3,
|
|
||||||
width: area.width / 2,
|
|
||||||
height: area.height / 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.popup {
|
match self.popup {
|
||||||
AppPopUp::AddFolder => self.render_folder_popup(popup_area, buf),
|
AppPopUp::AddFolder => self.render_folder_popup(area, buf),
|
||||||
AppPopUp::None => {},
|
AppPopUp::None => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -140,8 +148,7 @@ impl App {
|
|||||||
.title(Line::raw("Games"))
|
.title(Line::raw("Games"))
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.style(Style::default());
|
.style(Style::default());
|
||||||
|
self.render_widget(game_list, area, buf);
|
||||||
game_list.render(area, buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(&mut self, area: Rect, buf: &mut Buffer) {
|
fn render_header(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
@@ -151,7 +158,7 @@ impl App {
|
|||||||
Style::default().fg(Color::Green),
|
Style::default().fg(Color::Green),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
title.render(area, buf);
|
self.render_widget(title, area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer(&mut self, area: Rect, buf: &mut Buffer) {
|
fn render_footer(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
@@ -163,11 +170,39 @@ impl App {
|
|||||||
}
|
}
|
||||||
let line = Line::from(navigation_text);
|
let line = Line::from(navigation_text);
|
||||||
let footer = Paragraph::new(line);
|
let footer = Paragraph::new(line);
|
||||||
footer.render(area, buf);
|
self.render_widget(footer, area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_folder_popup(&mut self, area: Rect, buf: &mut Buffer) {
|
fn render_folder_popup(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
todo!()
|
let popup_area = Rect {
|
||||||
|
x: area.width / 4,
|
||||||
|
y: area.height / 3,
|
||||||
|
width: area.width / 2,
|
||||||
|
height: area.height / 3,
|
||||||
|
};
|
||||||
|
let mut textarea = TextArea::new("New Folder", "test");
|
||||||
|
textarea.active = true;
|
||||||
|
self.render_stateful_widget("folder_textarea", textarea, popup_area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_widget<T>(&mut self, widget: T, area: Rect, buf: &mut Buffer)
|
||||||
|
where T: Widget + Clone + 'static
|
||||||
|
{
|
||||||
|
widget.clone().render(area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_stateful_widget<T>(&mut self, id: &str, widget: T, area: Rect, buf: &mut Buffer)
|
||||||
|
where T: StatefulWidget + Clone + 'static
|
||||||
|
{
|
||||||
|
if !self.stateful_widgets.contains_key(id) {
|
||||||
|
self.stateful_widgets.insert(id.to_string(), Box::new(widget.clone()));
|
||||||
|
}
|
||||||
|
let cached_widget = self.stateful_widgets
|
||||||
|
.get_mut(id)
|
||||||
|
.unwrap()
|
||||||
|
.downcast_mut::<T::State>()
|
||||||
|
.unwrap();
|
||||||
|
widget.render(area, buf, cached_widget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +215,7 @@ impl App {
|
|||||||
self.app_config
|
self.app_config
|
||||||
.clone()
|
.clone()
|
||||||
.write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf())?;
|
.write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf())?;
|
||||||
|
self.events.task.abort();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.popup = AppPopUp::None;
|
self.popup = AppPopUp::None;
|
||||||
|
|||||||
39
src/cli.rs
39
src/cli.rs
@@ -1,6 +1,11 @@
|
|||||||
|
use std::io::stdout;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use clap::{command, Args, Command, Parser, Subcommand};
|
use clap::{command, Args, Command, Parser, Subcommand};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use crossterm::cursor::{Hide, Show};
|
||||||
|
use crossterm::execute;
|
||||||
|
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
|
||||||
|
use ratatui::crossterm;
|
||||||
use crate::app;
|
use crate::app;
|
||||||
use crate::config::types::ApplicationConfig;
|
use crate::config::types::ApplicationConfig;
|
||||||
use crate::constants::APP_CONIFG_FILE_PATH;
|
use crate::constants::APP_CONIFG_FILE_PATH;
|
||||||
@@ -35,13 +40,15 @@ pub(crate) struct Cli {
|
|||||||
|
|
||||||
impl Subcommand for Cli {
|
impl Subcommand for Cli {
|
||||||
fn augment_subcommands(cmd: Command) -> Command {
|
fn augment_subcommands(cmd: Command) -> Command {
|
||||||
cmd.subcommand(FolderCommand::augment_args(Command::new("folder")))
|
cmd.subcommand(FolderCommand::augment_args(
|
||||||
.subcommand_required(true)
|
Command::new("folder"))
|
||||||
|
).subcommand_required(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn augment_subcommands_for_update(cmd: Command) -> Command {
|
fn augment_subcommands_for_update(cmd: Command) -> Command {
|
||||||
cmd.subcommand(FolderCommand::augment_args(Command::new("folder")))
|
cmd.subcommand(FolderCommand::augment_args
|
||||||
.subcommand_required(true)
|
(Command::new("folder"))
|
||||||
|
).subcommand_required(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_subcommand(name: &str) -> bool {
|
fn has_subcommand(name: &str) -> bool {
|
||||||
@@ -51,13 +58,15 @@ impl Subcommand for Cli {
|
|||||||
|
|
||||||
impl Subcommand for FolderCommand {
|
impl Subcommand for FolderCommand {
|
||||||
fn augment_subcommands(cmd: Command) -> Command {
|
fn augment_subcommands(cmd: Command) -> Command {
|
||||||
cmd.subcommand(FolderAddCommand::augment_args(Command::new("add")))
|
cmd.subcommand(FolderAddCommand::augment_args(
|
||||||
.subcommand_required(true)
|
Command::new("add"))
|
||||||
|
).subcommand_required(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn augment_subcommands_for_update(cmd: Command) -> Command {
|
fn augment_subcommands_for_update(cmd: Command) -> Command {
|
||||||
cmd.subcommand(FolderAddCommand::augment_args(Command::new("add")))
|
cmd.subcommand(FolderAddCommand::augment_args(
|
||||||
.subcommand_required(true)
|
Command::new("add"))
|
||||||
|
).subcommand_required(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_subcommand(name: &str) -> bool {
|
fn has_subcommand(name: &str) -> bool {
|
||||||
@@ -77,10 +86,14 @@ impl Cli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn start_tui(&self) -> Result<()> {
|
async fn start_tui(&self) -> Result<()> {
|
||||||
let terminal = ratatui::init();
|
crossterm::terminal::enable_raw_mode()?;
|
||||||
let app = app::App::new();
|
|
||||||
let result = app.run(terminal).await;
|
let mut terminal = ratatui::init();
|
||||||
|
let app = app::App::create().await;
|
||||||
|
let result = app.run(&mut terminal).await;
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
|
|
||||||
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,9 +101,7 @@ impl Cli {
|
|||||||
impl CliSubCommand {
|
impl CliSubCommand {
|
||||||
pub async fn handle(&self) -> Result<()> {
|
pub async fn handle(&self) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
CliSubCommand::Folder(cmd) => {
|
CliSubCommand::Folder(cmd) => cmd.subcommand.handle().await
|
||||||
cmd.subcommand.handle().await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/event.rs
36
src/event.rs
@@ -1,29 +1,28 @@
|
|||||||
use color_eyre::eyre::{Result, eyre};
|
|
||||||
use crossterm;
|
|
||||||
use crossterm::event;
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use futures::StreamExt;
|
use color_eyre::eyre::{Result, eyre};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use crossterm::event::EventStream;
|
||||||
|
use futures::StreamExt;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum Event {
|
pub(crate) enum AppEvent {
|
||||||
Error,
|
Error,
|
||||||
Tick,
|
Tick,
|
||||||
Key(event::KeyEvent),
|
Raw(crossterm::event::Event),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct EventHandler {
|
pub(crate) struct EventHandler {
|
||||||
_tx: UnboundedSender<Event>,
|
_tx: UnboundedSender<AppEvent>,
|
||||||
rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
|
rx: tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
|
||||||
task: Option<JoinHandle<()>>,
|
pub task: JoinHandle<()>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl EventHandler {
|
||||||
pub fn new(tick_rate: Duration) -> Self {
|
pub fn new(tick_rate: Duration) -> Self {
|
||||||
let mut interval = tokio::time::interval(tick_rate);
|
let mut interval = tokio::time::interval(tick_rate);
|
||||||
let mut event_reader = event::EventStream::new();
|
let mut event_reader = EventStream::new();
|
||||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
let _tx = tx.clone();
|
let _tx = tx.clone();
|
||||||
@@ -35,28 +34,23 @@ impl EventHandler {
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
maybe_event = crossterm_event => {
|
maybe_event = crossterm_event => {
|
||||||
if let Some(Err(_)) = maybe_event {
|
if let Some(Err(_)) = maybe_event {
|
||||||
tx.send(Event::Error).unwrap()
|
tx.send(AppEvent::Error).unwrap()
|
||||||
}
|
} else if let Some(Ok(event)) = maybe_event {
|
||||||
else if let Some(Ok(event)) = maybe_event &&
|
tx.send(AppEvent::Raw(event)).unwrap();
|
||||||
let event::Event::Key(key) = event
|
|
||||||
{
|
|
||||||
tx.send(Event::Key(key)).unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = delay => {
|
_ = delay => {
|
||||||
tx.send(Event::Tick).unwrap()
|
tx.send(AppEvent::Tick).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
_tx,
|
_tx, rx, task
|
||||||
rx,
|
|
||||||
task: Some(task),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn next(&mut self) -> Result<Event> {
|
pub(crate) async fn next(&mut self) -> Result<AppEvent> {
|
||||||
self.rx.recv().await.ok_or(eyre!("Unable to get event"))
|
self.rx.recv().await.ok_or(eyre!("Unable to get event"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ mod helpers;
|
|||||||
mod crawler;
|
mod crawler;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
use clap::{command, Command, Parser};
|
use clap::{Parser};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use tokio;
|
use tokio;
|
||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
|
|||||||
1
src/widgets/mod.rs
Normal file
1
src/widgets/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod textarea;
|
||||||
49
src/widgets/textarea.rs
Normal file
49
src/widgets/textarea.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use crossterm::event::{Event};
|
||||||
|
use ratatui::buffer::Buffer;
|
||||||
|
use ratatui::layout::Rect;
|
||||||
|
use ratatui::prelude::StatefulWidget;
|
||||||
|
use ratatui::text::Text;
|
||||||
|
use ratatui::widgets::{Block, Borders, Paragraph, Widget};
|
||||||
|
use tui_input::backend::crossterm::EventHandler;
|
||||||
|
use tui_input::Input;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TextArea {
|
||||||
|
input: Input,
|
||||||
|
title: String,
|
||||||
|
pub active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatefulWidget for TextArea {
|
||||||
|
type State = TextArea;
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
|
||||||
|
where
|
||||||
|
Self: Sized
|
||||||
|
{
|
||||||
|
let input_value = state.input.value().to_string();
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(self.title.clone());
|
||||||
|
let paragraph = Paragraph::new(Text::from(input_value))
|
||||||
|
.block(block);
|
||||||
|
paragraph.render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextArea {
|
||||||
|
pub fn new(title: &str, placeholder_text: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
input: Input::new(placeholder_text.to_string()),
|
||||||
|
title: title.to_string(),
|
||||||
|
active: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_input(&mut self, event: &Event) {
|
||||||
|
let state_result = self.input.handle_event(event);
|
||||||
|
if state_result.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let state = state_result.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user