use alloc::vec::Vec;
use azul_core::{
callbacks::FocusTarget,
dom::{DomId, DomNodeId, NodeId},
events::{DefaultAction, DefaultActionResult, EventType, ScrollAmount, ScrollDirection, SyntheticEvent},
styled_dom::NodeHierarchyItemId,
window::{KeyboardState, VirtualKeyCode},
};
use crate::window::DomLayoutResult;
use std::collections::BTreeMap;
pub fn determine_keyboard_default_action(
keyboard_state: &KeyboardState,
focused_node: Option<DomNodeId>,
layout_results: &BTreeMap<DomId, DomLayoutResult>,
prevented: bool,
) -> DefaultActionResult {
if prevented {
return DefaultActionResult::prevented();
}
let current_key = match keyboard_state.current_virtual_keycode.into_option() {
Some(key) => key,
None => return DefaultActionResult::default(),
};
let shift_down = keyboard_state.shift_down();
let ctrl_down = keyboard_state.ctrl_down();
let alt_down = keyboard_state.alt_down();
let action = match current_key {
VirtualKeyCode::Tab => {
if ctrl_down || alt_down {
DefaultAction::None
} else if shift_down {
DefaultAction::FocusPrevious
} else {
DefaultAction::FocusNext
}
}
VirtualKeyCode::Return | VirtualKeyCode::NumpadEnter => {
if let Some(ref focus) = focused_node {
if is_element_activatable(focus, layout_results) {
DefaultAction::ActivateFocusedElement {
target: focus.clone(),
}
} else {
DefaultAction::None
}
} else {
DefaultAction::None
}
}
VirtualKeyCode::Space => {
if let Some(ref focus) = focused_node {
if is_element_activatable(focus, layout_results)
&& !is_text_input(focus, layout_results)
{
DefaultAction::ActivateFocusedElement {
target: focus.clone(),
}
} else {
DefaultAction::None
}
} else {
DefaultAction::None
}
}
VirtualKeyCode::Escape => {
if focused_node.is_some() {
DefaultAction::ClearFocus
} else {
DefaultAction::None
}
}
VirtualKeyCode::Up => {
if focused_node.is_some() && !is_text_input(&focused_node.as_ref().unwrap(), layout_results) {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Up,
amount: ScrollAmount::Line,
}
} else {
DefaultAction::None
}
}
VirtualKeyCode::Down => {
if focused_node.is_some() && !is_text_input(&focused_node.as_ref().unwrap(), layout_results) {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Down,
amount: ScrollAmount::Line,
}
} else {
DefaultAction::None
}
}
VirtualKeyCode::Left => {
if focused_node.is_some() && !is_text_input(&focused_node.as_ref().unwrap(), layout_results) {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Left,
amount: ScrollAmount::Line,
}
} else {
DefaultAction::None
}
}
VirtualKeyCode::Right => {
if focused_node.is_some() && !is_text_input(&focused_node.as_ref().unwrap(), layout_results) {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Right,
amount: ScrollAmount::Line,
}
} else {
DefaultAction::None
}
}
VirtualKeyCode::PageUp => {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Up,
amount: ScrollAmount::Page,
}
}
VirtualKeyCode::PageDown => {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Down,
amount: ScrollAmount::Page,
}
}
VirtualKeyCode::Home => {
if ctrl_down {
DefaultAction::FocusFirst
} else {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Up,
amount: ScrollAmount::Document,
}
}
}
VirtualKeyCode::End => {
if ctrl_down {
DefaultAction::FocusLast
} else {
DefaultAction::ScrollFocusedContainer {
direction: ScrollDirection::Down,
amount: ScrollAmount::Document,
}
}
}
_ => DefaultAction::None,
};
DefaultActionResult::new(action)
}
fn is_element_activatable(node_id: &DomNodeId, layout_results: &BTreeMap<DomId, DomLayoutResult>) -> bool {
let Some(layout) = layout_results.get(&node_id.dom) else {
return false;
};
let Some(internal_id) = node_id.node.into_crate_internal() else {
return false;
};
layout.styled_dom.node_data.as_container()
.get(internal_id)
.map(|node| node.is_activatable())
.unwrap_or(false)
}
fn is_text_input(node_id: &DomNodeId, layout_results: &BTreeMap<DomId, DomLayoutResult>) -> bool {
let Some(layout) = layout_results.get(&node_id.dom) else {
return false;
};
let Some(internal_id) = node_id.node.into_crate_internal() else {
return false;
};
let node_data = layout.styled_dom.node_data.as_container();
let Some(node) = node_data.get(internal_id) else {
return false;
};
use azul_core::events::{EventFilter, FocusEventFilter};
node.get_callbacks()
.iter()
.any(|cb| matches!(cb.event, EventFilter::Focus(FocusEventFilter::TextInput)))
}
pub fn default_action_to_focus_target(action: &DefaultAction) -> Option<FocusTarget> {
match action {
DefaultAction::FocusNext => Some(FocusTarget::Next),
DefaultAction::FocusPrevious => Some(FocusTarget::Previous),
DefaultAction::FocusFirst => Some(FocusTarget::First),
DefaultAction::FocusLast => Some(FocusTarget::Last),
DefaultAction::ClearFocus => Some(FocusTarget::NoFocus),
_ => None,
}
}
pub fn create_activation_click_event(
target: &DomNodeId,
timestamp: azul_core::task::Instant,
) -> SyntheticEvent {
use azul_core::events::{EventData, EventSource};
SyntheticEvent::new(
EventType::Click,
EventSource::Synthetic,
target.clone(),
timestamp,
EventData::None,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tab_focus_next() {
let mut keyboard_state = KeyboardState::default();
keyboard_state.current_virtual_keycode = Some(VirtualKeyCode::Tab).into();
let result = determine_keyboard_default_action(
&keyboard_state,
None,
&BTreeMap::new(),
false,
);
assert!(matches!(result.action, DefaultAction::FocusNext));
assert!(!result.prevented);
}
#[test]
fn test_shift_tab_focus_previous() {
let mut keyboard_state = KeyboardState::default();
keyboard_state.current_virtual_keycode = Some(VirtualKeyCode::Tab).into();
keyboard_state.pressed_virtual_keycodes = vec![VirtualKeyCode::LShift, VirtualKeyCode::Tab].into();
let result = determine_keyboard_default_action(
&keyboard_state,
None,
&BTreeMap::new(),
false,
);
assert!(matches!(result.action, DefaultAction::FocusPrevious));
}
#[test]
fn test_escape_clears_focus() {
let mut keyboard_state = KeyboardState::default();
keyboard_state.current_virtual_keycode = Some(VirtualKeyCode::Escape).into();
let focused = Some(DomNodeId {
dom: DomId { inner: 0 },
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
});
let result = determine_keyboard_default_action(
&keyboard_state,
focused,
&BTreeMap::new(),
false,
);
assert!(matches!(result.action, DefaultAction::ClearFocus));
}
#[test]
fn test_prevented_returns_no_action() {
let mut keyboard_state = KeyboardState::default();
keyboard_state.current_virtual_keycode = Some(VirtualKeyCode::Tab).into();
let result = determine_keyboard_default_action(
&keyboard_state,
None,
&BTreeMap::new(),
true, );
assert!(result.prevented);
assert!(matches!(result.action, DefaultAction::None));
}
}