use std::cell::Cell;
use std::rc::Rc;
use gtk4::prelude::*;
use gtk4::{EventControllerKey, PropagationPhase};
use crate::core::command::Command;
use crate::core::key::Key;
use crate::core::msg::Msg;
use crate::core::runtime::Mailbox;
use crate::core::state::Mode;
use crate::ui::window::Ui;
#[derive(Clone, Copy)]
pub struct UiView {
pub mode: Mode,
pub completion_active: bool,
}
pub type ModeMirror = Rc<Cell<UiView>>;
pub fn install(ui: &Ui, mailbox: &Mailbox) -> ModeMirror {
let mirror: ModeMirror = Rc::new(Cell::new(UiView {
mode: Mode::Normal,
completion_active: false,
}));
let controller = EventControllerKey::new();
controller.set_propagation_phase(PropagationPhase::Capture);
let mb = mailbox.clone();
let view = mirror.clone();
controller.connect_key_pressed(move |_, keyval, _, mods| {
let Some(key) = to_key(keyval, mods) else {
return glib::Propagation::Proceed;
};
if key.sym == "Escape" && !key.ctrl && !key.alt {
mb.send(Msg::Command(Command::ModeLeave));
return glib::Propagation::Stop;
}
let snapshot = view.get();
match snapshot.mode {
Mode::Command => {
if key.sym == "Tab" && !key.ctrl && !key.alt {
mb.send(if key.shift {
Msg::CompletionPrev
} else {
Msg::CompletionNext
});
return glib::Propagation::Stop;
}
if key.sym == "space" && !key.ctrl && !key.alt && snapshot.completion_active {
mb.send(Msg::CompletionApply);
return glib::Propagation::Stop;
}
glib::Propagation::Proceed
}
Mode::Insert => glib::Propagation::Proceed,
Mode::Normal | Mode::Hint => {
mb.send(Msg::Key(key));
glib::Propagation::Stop
}
}
});
ui.window.add_controller(controller);
let mb = mailbox.clone();
ui.commandline
.connect_activate(move |_| mb.send(Msg::Command(Command::Accept)));
let mb = mailbox.clone();
ui.commandline
.connect_changed(move |e| mb.send(Msg::CommandLineChanged(e.text().to_string())));
mirror
}
fn to_key(keyval: gdk4::Key, mods: gdk4::ModifierType) -> Option<Key> {
use gdk4::ModifierType;
let ctrl = mods.contains(ModifierType::CONTROL_MASK);
let alt = mods.contains(ModifierType::ALT_MASK);
if let Some(sym) = named_sym(keyval) {
let shift = mods.contains(ModifierType::SHIFT_MASK);
return Some(Key {
sym,
ctrl,
alt,
shift,
});
}
let c = keyval.to_unicode()?;
if c.is_control() {
return None;
}
Some(Key {
sym: c.to_string(),
ctrl,
alt,
shift: false,
})
}
fn named_sym(keyval: gdk4::Key) -> Option<String> {
let name = match keyval {
gdk4::Key::Escape => "Escape",
gdk4::Key::Return | gdk4::Key::KP_Enter => "Return",
gdk4::Key::Tab | gdk4::Key::ISO_Left_Tab => "Tab",
gdk4::Key::space => "space",
gdk4::Key::BackSpace => "BackSpace",
gdk4::Key::Delete => "Delete",
gdk4::Key::Insert => "Insert",
gdk4::Key::Up => "Up",
gdk4::Key::Down => "Down",
gdk4::Key::Left => "Left",
gdk4::Key::Right => "Right",
gdk4::Key::Page_Up => "PgUp",
gdk4::Key::Page_Down => "PgDown",
gdk4::Key::Home => "Home",
gdk4::Key::End => "End",
gdk4::Key::F1 => "F1",
gdk4::Key::F2 => "F2",
gdk4::Key::F3 => "F3",
gdk4::Key::F4 => "F4",
gdk4::Key::F5 => "F5",
gdk4::Key::F6 => "F6",
gdk4::Key::F7 => "F7",
gdk4::Key::F8 => "F8",
gdk4::Key::F9 => "F9",
gdk4::Key::F10 => "F10",
gdk4::Key::F11 => "F11",
gdk4::Key::F12 => "F12",
_ => return None,
};
Some(name.to_string())
}