use std::fmt::Debug;
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct FocusTransition<T> {
pub leave: Option<T>,
pub entered: Option<T>,
}
pub(crate) fn focus_transition<T: Clone + PartialEq + Debug>(
entered: Option<&T>,
hit: &T,
client_has_pointer: bool,
) -> Option<FocusTransition<T>> {
if entered == Some(hit) {
return None;
}
Some(FocusTransition {
leave: entered.cloned(),
entered: client_has_pointer.then(|| hit.clone()),
})
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ButtonRouting {
Deliver,
Consume,
}
pub(crate) fn button_routing(
pressed: bool,
button: u32,
dismissed_grab: bool,
swallowing: Option<u32>,
) -> (ButtonRouting, Option<u32>) {
if dismissed_grab {
return (ButtonRouting::Consume, Some(button));
}
if pressed {
return (ButtonRouting::Deliver, None);
}
if swallowing == Some(button) {
return (ButtonRouting::Consume, None);
}
(ButtonRouting::Deliver, swallowing)
}
pub(crate) fn keyboard_focus_after_popup_close<T: Clone + PartialEq>(
closing: &T,
holder: Option<&T>,
remaining: &[T],
) -> Option<Option<T>> {
if holder != Some(closing) {
return None;
}
Some(remaining.last().cloned())
}
#[cfg(test)]
mod tests {
use super::{
ButtonRouting, FocusTransition, button_routing, focus_transition,
keyboard_focus_after_popup_close,
};
#[test]
fn already_inside_is_motion_only() {
assert_eq!(focus_transition(Some(&1), &1, true), None);
}
#[test]
fn crossing_between_surfaces_leaves_then_enters() {
assert_eq!(
focus_transition(Some(&1), &2, true),
Some(FocusTransition {
leave: Some(1),
entered: Some(2),
})
);
}
#[test]
fn a_surface_whose_client_cannot_be_told_is_not_recorded() {
assert_eq!(
focus_transition(None, &2, false),
Some(FocusTransition {
leave: None,
entered: None,
})
);
}
#[test]
fn leaving_for_a_surface_that_cannot_be_told_clears_the_old_one() {
assert_eq!(
focus_transition(Some(&1), &2, false),
Some(FocusTransition {
leave: Some(1),
entered: None,
})
);
}
#[test]
fn returning_from_a_pointerless_surface_re_enters() {
let away = focus_transition(Some(&1), &2, false).expect("1 -> 2 is a change");
assert_eq!(away.entered, None, "nothing was entered, so nothing held");
let back = focus_transition(away.entered.as_ref(), &1, true)
.expect("2 -> 1 must not be mistaken for already-inside");
assert_eq!(
back,
FocusTransition {
leave: None,
entered: Some(1),
},
"the surface we left must be entered again, not merely moved over"
);
}
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
#[test]
fn an_ordinary_click_is_delivered() {
assert_eq!(
button_routing(true, BTN_LEFT, false, None),
(ButtonRouting::Deliver, None)
);
assert_eq!(
button_routing(false, BTN_LEFT, false, None),
(ButtonRouting::Deliver, None)
);
}
#[test]
fn the_click_that_closes_a_menu_is_spent_on_closing_it() {
let (routing, swallow) = button_routing(true, BTN_LEFT, true, None);
assert_eq!(routing, ButtonRouting::Consume);
assert_eq!(swallow, Some(BTN_LEFT), "its release must go too");
assert_eq!(
button_routing(false, BTN_LEFT, false, swallow),
(ButtonRouting::Consume, None),
"a client that never saw the press must not see the release"
);
}
#[test]
fn another_buttons_release_is_untouched_while_armed() {
assert_eq!(
button_routing(false, BTN_RIGHT, false, Some(BTN_LEFT)),
(ButtonRouting::Deliver, Some(BTN_LEFT))
);
}
#[test]
fn a_new_press_disarms_a_stale_swallow() {
assert_eq!(
button_routing(true, BTN_LEFT, false, Some(BTN_LEFT)),
(ButtonRouting::Deliver, None)
);
}
#[test]
fn closing_a_popup_that_lacks_focus_changes_nothing() {
assert_eq!(keyboard_focus_after_popup_close(&7, Some(&9), &[9]), None);
assert_eq!(keyboard_focus_after_popup_close(&7, None, &[]), None);
}
#[test]
fn closing_the_last_menu_returns_the_keyboard_to_the_window() {
assert_eq!(
keyboard_focus_after_popup_close(&7, Some(&7), &[]),
Some(None)
);
}
#[test]
fn closing_a_submenu_returns_to_the_menu_beneath_it() {
assert_eq!(
keyboard_focus_after_popup_close(&8, Some(&8), &[7]),
Some(Some(7))
);
}
}