mod state;
mod ui;
pub use state::{DialogData, DialogResult};
pub use ui::{
DialogButtonRenderer, DialogPurposeClass, DialogPurposeStyle, DialogTheme, render_dialog,
render_dialog_with_button_renderer,
};
use crate::focus::{FocusController, FocusIntent, FocusManager, OverlayFocus};
use crate::input::{InputPipeline, InputRegistry, KeyMap, PipelineResponse};
use crate::keybindings::{
BuiltinNavigationPreset, NavigationAction, NavigationPreset, NavigationPresetError,
};
use crossterm::event::KeyEvent;
pub const GENERAL_KEYBINDING_SECTION: &str = "general";
pub const DIALOG_MODE: &str = "dialog";
impl<D> DialogData<D> {
pub fn show_intent<O>(self) -> FocusIntent<O, DialogData<D>> {
let buttons = self.buttons.len();
FocusIntent::ShowModal {
data: self,
count: buttons,
}
}
}
pub fn current_dialog<O, D>(focus: &FocusManager<O, DialogData<D>>) -> Option<&DialogData<D>> {
match focus.overlay() {
Some(OverlayFocus::Modal { data, .. }) => Some(data),
_ => None,
}
}
pub fn active_button<O, D>(focus: &FocusManager<O, DialogData<D>>) -> Option<usize> {
match focus.overlay() {
Some(OverlayFocus::Modal { index, .. }) => Some(*index),
_ => None,
}
}
pub fn selection<O, D: Clone>(focus: &FocusManager<O, DialogData<D>>) -> Option<DialogResult<D>> {
match focus.overlay() {
Some(OverlayFocus::Modal { data, index, .. }) => Some(DialogResult::Selected {
purpose: data.purpose.clone(),
index: *index,
}),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DialogKey<D> {
Ignored,
Consumed,
Resolved(DialogResult<D>),
}
#[derive(Debug, Clone)]
pub struct DialogKeyBindings {
input: InputPipeline<NavigationAction>,
}
impl Default for DialogKeyBindings {
fn default() -> Self {
Self::builtin(BuiltinNavigationPreset::Vim)
}
}
impl DialogKeyBindings {
pub fn builtin(preset: BuiltinNavigationPreset) -> Self {
let mut map = KeyMap::new(DIALOG_MODE);
preset
.preset()
.bind_section_to_map(GENERAL_KEYBINDING_SECTION, &mut map)
.expect("built-in dialog keybinding preset is valid");
let mut registry = InputRegistry::empty();
registry.add_map(map);
Self {
input: InputPipeline::new(registry, 1000),
}
}
pub fn from_preset_toml(source: &str) -> Result<Self, NavigationPresetError> {
let preset = NavigationPreset::from_toml(source)?;
let mut map = KeyMap::new(DIALOG_MODE);
preset.bind_section_to_map(GENERAL_KEYBINDING_SECTION, &mut map)?;
let mut registry = InputRegistry::empty();
registry.add_map(map);
Ok(Self {
input: InputPipeline::new(registry, 1000),
})
}
pub fn remap_preset_toml(&mut self, source: &str) -> Result<(), NavigationPresetError> {
*self = Self::from_preset_toml(source)?;
Ok(())
}
}
pub fn handle_key<O: Clone + PartialEq, D: Clone>(
focus: &mut FocusManager<O, DialogData<D>>,
key: KeyEvent,
) -> DialogKey<D> {
let mut bindings = DialogKeyBindings::default();
handle_key_with_bindings(focus, key, &mut bindings)
}
pub fn handle_key_with_bindings<O: Clone + PartialEq, D: Clone>(
focus: &mut FocusManager<O, DialogData<D>>,
key: KeyEvent,
bindings: &mut DialogKeyBindings,
) -> DialogKey<D> {
if current_dialog(focus).is_none() {
return DialogKey::Ignored;
}
match bindings.input.process(key, &[DIALOG_MODE], false) {
PipelineResponse::Execute(NavigationAction::FocusNext) => {
focus.apply_focus_intent(FocusIntent::Next);
DialogKey::Consumed
}
PipelineResponse::Execute(NavigationAction::FocusPrev) => {
focus.apply_focus_intent(FocusIntent::Prev);
DialogKey::Consumed
}
PipelineResponse::Execute(NavigationAction::Activate) => {
let result = selection(focus).unwrap_or(DialogResult::Dismissed);
focus.apply_focus_intent(FocusIntent::ClearOverlay);
DialogKey::Resolved(result)
}
PipelineResponse::Execute(NavigationAction::LeaveSection) => {
focus.apply_focus_intent(FocusIntent::ClearOverlay);
DialogKey::Resolved(DialogResult::Dismissed)
}
PipelineResponse::Wait(_) | PipelineResponse::Cancel => DialogKey::Consumed,
_ => DialogKey::Consumed,
}
}