use std::rc::Rc;
use crabshell_reactive::{Signal, untrack};
use crate::cx::Cx;
use crate::edit::Keyed;
use crate::input::{Handler, run_isolated};
use crate::text_input::AcceptHandler;
use crate::tree::{Change, NodeId, NodeKind, Tree, borrow};
use crate::window::Window;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyEvent {
pub key: Key,
pub text: Option<String>,
pub modifiers: Modifiers,
pub repeat: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Key {
Enter,
Escape,
Tab,
Backspace,
Delete,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Function(u8),
Character(char),
Other(u32),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Clipboard {
Copy(String),
Paste,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
pub logo: bool,
}
pub(crate) type KeyHandler = Rc<Handler<dyn Fn(&Cx, &KeyEvent) -> bool>>;
#[derive(Default)]
pub(crate) struct Keyboard {
pub(crate) entered: bool,
pub(crate) focused: Option<NodeId>,
pub(crate) handler: Option<KeyHandler>,
}
impl Keyboard {
pub(crate) fn forget(&mut self, removed: &[NodeId]) {
if self.focused.is_some_and(|id| removed.contains(&id)) {
self.focused = None;
}
}
}
impl Window {
pub fn keyboard_enter(&self) {
let mut tree = borrow(&self.tree);
tree.keyboard.entered = true;
let focused = tree.keyboard.focused;
tree.show_focus(focused, true);
}
pub fn keyboard_leave(&self) {
let mut tree = borrow(&self.tree);
tree.keyboard.entered = false;
let focused = tree.keyboard.focused;
tree.show_focus(focused, false);
}
pub fn key(&self, event: &KeyEvent) -> Option<Clipboard> {
let typed = borrow(&self.tree).input_key(event);
match typed {
Some(Typed::Edited(signal, text, clipboard)) => {
signal.try_set(text);
return clipboard;
}
Some(Typed::Clipboard(clipboard)) => return Some(clipboard),
Some(Typed::Moved) => return None,
Some(Typed::Accept(call)) => {
call.run();
return None;
}
None => {}
}
let (button, window) = {
let tree = self.tree.borrow();
(tree.focused_key_call(), tree.window_key_call())
};
for call in [button, window].into_iter().flatten() {
if call.run(event) {
return None;
}
}
let click = borrow(&self.tree).default_key(event);
if let Some(click) = click {
click.run();
}
None
}
pub fn focused(&self) -> Option<String> {
let tree = self.tree.borrow();
let id = tree.keyboard.focused?;
tree.node(id).map(|node| node.path.to_string())
}
}
impl Tree {
fn show_focus(&mut self, id: Option<NodeId>, shown: bool) {
let Some(id) = id else {
return;
};
if matches!(
self.node(id).map(|node| &node.kind),
Some(NodeKind::Input(_))
) {
self.update(id, |node| match &mut node.kind {
NodeKind::Input(input) if input.focused != shown => {
input.focused = shown;
Change::Paint
}
_ => Change::None,
});
} else {
self.update_button_style(id, |button| button.focused = shown);
}
}
pub(crate) fn focus(&mut self, id: Option<NodeId>) {
let old = std::mem::replace(&mut self.keyboard.focused, id);
if old != id {
if let Some(old) = old
&& matches!(self.node(old).map(|node| &node.kind), Some(NodeKind::Input(input)) if input.preedit.is_some())
{
self.update_input(old, |input| {
input.preedit = None;
input.refresh();
});
}
self.show_focus(old, false);
let entered = self.keyboard.entered;
self.show_focus(id, entered);
}
}
fn input_key(&mut self, event: &KeyEvent) -> Option<Typed> {
let id = self.keyboard.focused?;
let NodeKind::Input(input) = &mut self.node_mut(id)?.kind else {
return None;
};
let keyed = input.edit.key(event, !input.password);
let accept = keyed == Keyed::Accept;
let typed = match keyed {
Keyed::Unused => return None,
Keyed::Moved => Typed::Moved,
Keyed::Edited => Typed::Edited(input.signal, input.edit.text.clone(), None),
Keyed::Copy(text) => Typed::Clipboard(Clipboard::Copy(text)),
Keyed::Cut(text) => Typed::Edited(
input.signal,
input.edit.text.clone(),
Some(Clipboard::Copy(text)),
),
Keyed::Paste => Typed::Clipboard(Clipboard::Paste),
Keyed::Accept => {
let handler = untrack(|| input.on_accept.try_with(Option::clone)).flatten();
let text = input.edit.text.clone();
let node = self.node(id)?;
Typed::Accept(AcceptCall {
cx: node.cx.clone(),
path: Rc::clone(&node.path),
handler: handler?,
text,
})
}
};
if !accept {
self.update_input(id, |input| {
input.refresh();
});
}
Some(typed)
}
fn focusable(&self) -> Vec<NodeId> {
let mut found = Vec::new();
let mut pending = vec![self.root];
while let Some(id) = pending.pop() {
if let Some(node) = self.node(id)
&& matches!(node.kind, NodeKind::Button(_) | NodeKind::Input(_))
{
found.push(id);
}
let mut children = self.taffy.children(id).unwrap_or_default();
children.reverse();
pending.extend(children);
}
found
}
fn focused_key_call(&self) -> Option<KeyCall> {
let id = self.keyboard.focused?;
let node = self.node(id)?;
let NodeKind::Button(button) = &node.kind else {
return None;
};
let handler = untrack(|| button.handlers.try_with(|handlers| handlers.key.clone()))??;
Some(KeyCall {
cx: node.cx.clone(),
path: Rc::clone(&node.path),
handler,
})
}
fn window_key_call(&self) -> Option<KeyCall> {
let node = self.node(self.root)?;
Some(KeyCall {
cx: node.cx.clone(),
path: Rc::clone(&node.path),
handler: self.keyboard.handler.clone()?,
})
}
fn default_key(&mut self, event: &KeyEvent) -> Option<crate::input::Call> {
match event.key {
Key::Tab => {
let buttons = self.focusable();
let at = self
.keyboard
.focused
.and_then(|id| buttons.iter().position(|button| *button == id));
let count = buttons.len();
let next = match (at, event.modifiers.shift) {
_ if count == 0 => None,
(None, false) => Some(0),
(None, true) => Some(count - 1),
(Some(at), false) => Some((at + 1) % count),
(Some(at), true) => Some((at + count - 1) % count),
};
self.focus(next.map(|next| buttons[next]));
None
}
Key::Enter | Key::Character(' ') => self.click(self.keyboard.focused?),
_ => None,
}
}
}
enum Typed {
Edited(Signal<String>, String, Option<Clipboard>),
Clipboard(Clipboard),
Moved,
Accept(AcceptCall),
}
struct AcceptCall {
cx: Cx,
path: Rc<str>,
handler: AcceptHandler,
text: String,
}
impl AcceptCall {
fn run(self) {
run_isolated(&self.path, "on_accept", self.handler, |f| {
f(&self.cx, &self.text)
});
}
}
struct KeyCall {
cx: Cx,
path: Rc<str>,
handler: KeyHandler,
}
impl KeyCall {
fn run(self, event: &KeyEvent) -> bool {
run_isolated(&self.path, "on_key", self.handler, |f| f(&self.cx, event)).unwrap_or(false)
}
}