Skip to content

Commit

Permalink
Refactor modals (#42)
Browse files Browse the repository at this point in the history
Now modals receive a lambda that returns an Action. This makes modals
infinitely more reusable than the previous ones. To illustrate, this is
now possible:

```rust
let add_modal = Box::new(
    InputModal::new()
        .style(Style::default().fg(Color::BrightRed))
        .on_commit(|input| Action::AddSong(input))
);

let rename_modal = Box::new(
    InputModal::new()
        .style(Style::default().fg(Color::Blue))
        .on_commit(|input| Action::RenameSongTo(input))
);
```

No special case handling for each type of modal!

Also, modals now send Action::CloseModal when appropriate, which closes
any open modal. Previously each modal had its own match case to close
itself...
  • Loading branch information
LeoRiether authored Feb 10, 2024
1 parent fddb99d commit a6b71f0
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 289 deletions.
2 changes: 1 addition & 1 deletion tori/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl<'a> App<'a> {
crossterm_event = channel.crossterm_rx.recv() => {
if let Some(ev) = crossterm_event {
let mut state = state.lock().await;
match handle_event(&mut state, ev) {
match handle_event(&mut state, channel.tx.clone(), ev) {
Ok(Some(a)) => channel.tx.send(a).expect("Failed to send action"),
Ok(None) => {}
Err(e) => state.notify_err(e.to_string()),
Expand Down
49 changes: 31 additions & 18 deletions tori/src/app/modal/confirmation_modal.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{get_modal_chunk, Message, Modal};
use super::{get_modal_chunk, Modal};

use crossterm::event::{Event, KeyCode};
use tui::{
Expand All @@ -8,39 +8,56 @@ use tui::{
widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget},
};

use crate::error::Result;
use crate::{
error::Result,
events::{channel::Tx, Action},
};

/// A confirmation modal box that asks for user yes/no input
#[derive(Debug, Default)]
pub struct ConfirmationModal {
pub struct ConfirmationModal<C> {
title: String,
style: Style,
on_yes: Option<C>,
}

impl ConfirmationModal {
impl<C> ConfirmationModal<C> {
pub fn new(title: &str) -> Self {
Self {
title: format!("\n{} (y/n)", title),
style: Style::default().fg(Color::LightBlue),
on_yes: None,
}
}
}

impl Modal for ConfirmationModal {
fn apply_style(&mut self, style: Style) {
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}

fn handle_event(&mut self, event: Event) -> Result<Message> {
pub fn on_yes(mut self, action: C) -> Self {
self.on_yes = Some(action);
self
}
}

impl<C> Modal for ConfirmationModal<C>
where
C: FnOnce() -> Action + Send + Sync,
{
fn handle_event(&mut self, tx: Tx, event: Event) -> Result<Option<Action>> {
use KeyCode::*;
if let Event::Key(event) = event {
return match event.code {
Backspace | Esc | Char('q') | Char('n') | Char('N') => Ok(Message::Quit),
Enter | Char('y') | Char('Y') => Ok(Message::Commit("y".into())),
_ => Ok(Message::Nothing),
};
return Ok(match event.code {
Backspace | Esc | Char('q') | Char('n') | Char('N') => Some(Action::CloseModal),
Enter | Char('y') | Char('Y') => {
tx.send(Action::CloseModal);
self.on_yes.take().map(|f| f())
}
_ => None,
});
}
Ok(Message::Nothing)
Ok(None)
}

fn render(&self, area: Rect, buf: &mut Buffer) {
Expand All @@ -60,8 +77,4 @@ impl Modal for ConfirmationModal {
Clear.render(chunk, buf);
paragraph.render(chunk, buf);
}

fn mode(&self) -> ! {
todo!()
}
}
28 changes: 13 additions & 15 deletions tori/src/app/modal/help_modal.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use super::{get_modal_chunk, Message, Modal};
use super::Modal;

use crossterm::event::Event;
use tui::{
layout::{Alignment, Constraint},
prelude::*,
style::{Color, Style},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Clear, Paragraph, Row, Table, Widget},
prelude::*,
};
use unicode_width::UnicodeWidthStr;

use crate::{
config::{shortcuts::InputStr, Config},
error::Result,
events::Command,
events::{channel::Tx, Action, Command},
};

/// A modal box that asks for user input
Expand Down Expand Up @@ -60,19 +60,21 @@ impl HelpModal {
}

impl Modal for HelpModal {
fn apply_style(&mut self, _style: Style) {}

fn handle_event(&mut self, event: Event) -> Result<Message> {
fn handle_event(&mut self, tx: Tx, event: Event) -> Result<Option<Action>> {
if let Event::Key(_) = event {
return Ok(Message::Quit);
return Ok(Some(Action::CloseModal));
}
Ok(Message::Nothing)
Ok(None)
}

fn render(&self, area: Rect, buf: &mut Buffer) {
let mut chunk = get_modal_chunk(area);
chunk.y = 3;
chunk.height = area.height.saturating_sub(6);
let width = (area.width / 2).max(70).min(area.width);
let mut chunk = Rect {
x: area.width.saturating_sub(width) / 2,
width,
y: 3,
height: area.height.saturating_sub(6),
};

let block = Block::default()
.title(" Help ")
Expand Down Expand Up @@ -100,8 +102,4 @@ impl Modal for HelpModal {
chunk.height -= 3;
table.render(chunk, buf);
}

fn mode(&self) -> ! {
todo!()
}
}
24 changes: 8 additions & 16 deletions tori/src/app/modal/hotkey_modal.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
use crate::{
config::shortcuts::InputStr,
error::Result,
events::{channel::Tx, Action},
};
use crossterm::event::Event as CrosstermEvent;
use crossterm::event::{Event, KeyCode};
use tui::{
layout::Alignment,
prelude::*,
style::{Color, Style},
widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget},
prelude::*,
};

use super::Modal;

///////////////////////////////
// HotkeyModal //
///////////////////////////////
/// Shows what keys the user is pressing
#[derive(Debug, Default)]
pub struct HotkeyModal {
text: String,
}

impl Modal for HotkeyModal {
fn apply_style(&mut self, _style: Style) {}

fn handle_event(&mut self, event: CrosstermEvent) -> Result<super::Message> {
if let CrosstermEvent::Key(key) = event {
if let crossterm::event::KeyCode::Esc = key.code {
return Ok(super::Message::Quit);
fn handle_event(&mut self, tx: Tx, event: Event) -> Result<Option<Action>> {
if let Event::Key(key) = event {
if let KeyCode::Esc = key.code {
return Ok(Some(Action::CloseModal));
}
self.text = InputStr::from(key).0;
}
Ok(super::Message::Nothing)
Ok(None)
}

fn render(&self, area: Rect, buf: &mut Buffer) {
Expand All @@ -53,8 +49,4 @@ impl Modal for HotkeyModal {
Clear.render(chunk, buf);
paragraph.render(chunk, buf);
}

fn mode(&self) -> ! {
todo!()
}
}
Loading

0 comments on commit a6b71f0

Please sign in to comment.