use std::io;
use crossterm::event::KeyEvent;
use ratatui::{
Frame,
buffer::Buffer,
layout::Rect,
style::{Color as RatColor, Modifier},
widgets::Clear,
};
use super::{
chrome::modal_block,
modal::ModalSignal,
style,
terminal::{Tui, TuiEvent},
};
use crate::theme::Skin;
pub(crate) const SCRIM_FACTOR: f32 = 0.4;
pub enum PopupFlow<T> {
Continue,
Done(T),
Cancelled,
}
pub fn popup<S, T>(
tui: &mut Tui,
state: &mut S,
area: impl Fn(Rect, &S) -> Rect,
render_bg: impl Fn(&mut Frame, &S),
render_box: impl Fn(&mut Frame, Rect, &S),
mut handle_key: impl FnMut(&mut S, KeyEvent) -> PopupFlow<T>,
) -> io::Result<ModalSignal<T>> {
loop {
tui.draw(|frame| {
render_bg(frame, state);
dim(frame, SCRIM_FACTOR);
let rect = area(frame.area(), state);
frame.render_widget(Clear, rect);
render_box(frame, rect, state);
})?;
match tui.read_event()? {
TuiEvent::Quit => return Ok(ModalSignal::Quit),
TuiEvent::Resize => {}
TuiEvent::Key(key) => match handle_key(state, key) {
PopupFlow::Continue => {}
PopupFlow::Done(value) => {
return Ok(ModalSignal::Value(value));
}
PopupFlow::Cancelled => return Ok(ModalSignal::Cancelled),
},
}
}
}
pub fn framed(frame: &mut Frame, rect: Rect, skin: &Skin, title: &str) -> Rect {
let block = modal_block(skin, title);
let inner = block.inner(rect);
frame.render_widget(block, rect);
inner
}
pub fn box_width(base: u16, skin: &Skin) -> u16 {
base + 2 * u16::from(skin.is_fancy())
}
pub fn dim(frame: &mut Frame, factor: f32) {
dim_buffer(frame.buffer_mut(), factor);
}
fn dim_buffer(buffer: &mut Buffer, factor: f32) {
for cell in &mut buffer.content {
if matches!(cell.fg, RatColor::Rgb(..)) {
cell.fg = style::darken(cell.fg, factor);
} else {
cell.modifier |= Modifier::DIM;
}
if matches!(cell.bg, RatColor::Rgb(..)) {
cell.bg = style::darken(cell.bg, factor);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dim_darkens_rgb_foreground_and_background() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
buffer.content[0].fg = RatColor::Rgb(100, 100, 100);
buffer.content[0].bg = RatColor::Rgb(200, 200, 200);
dim_buffer(&mut buffer, 0.5);
assert_eq!(buffer.content[0].fg, RatColor::Rgb(50, 50, 50));
assert_eq!(buffer.content[0].bg, RatColor::Rgb(100, 100, 100));
}
#[test]
fn dim_marks_non_rgb_foreground_dim() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
buffer.content[0].fg = RatColor::Reset;
dim_buffer(&mut buffer, 0.5);
assert!(buffer.content[0].modifier.contains(Modifier::DIM));
}
}