#[cfg(test)]
mod tests {
    use super::*;
    use iced::keyboard::{Key, Modifiers, key};

    #[test]
    fn generic_codes_are_fixed_width_and_skip_direct_key_prefixes() {
        let reserved = letter_mask(['A', 'C']);
        assert_eq!(code_width(24 * 26, reserved), 2);
        assert_eq!(code_width(24 * 26 + 1, reserved), 3);
        assert_eq!(generic_code(0, 2, reserved), "BA");
        assert_eq!(generic_code(25, 2, reserved), "BZ");
        assert_eq!(generic_code(26, 2, reserved), "DA");
    }

    #[test]
    fn tab_navigation_ignores_events_already_captured_by_an_editor() {
        let tab = Key::Named(key::Named::Tab);
        assert_eq!(
            tab_navigation(&tab, Modifiers::default(), iced::event::Status::Ignored),
            Some(Navigation::Next)
        );
        assert_eq!(
            tab_navigation(&tab, Modifiers::SHIFT, iced::event::Status::Ignored),
            Some(Navigation::Previous)
        );
        assert_eq!(
            tab_navigation(&tab, Modifiers::default(), iced::event::Status::Captured),
            None
        );
    }

    #[test]
    fn access_characters_use_the_unmodified_key() {
        assert_eq!(access_character(&Key::Character("m".into())), Some('M'));
        assert_eq!(access_character(&Key::Character("1".into())), Some('1'));
        assert_eq!(access_character(&Key::Character("µ".into())), None);
    }

    #[test]
    fn access_mode_requires_control_and_option_or_alt_together() {
        assert!(!access_modifiers(Modifiers::ALT));
        assert!(!access_modifiers(Modifiers::CTRL));
        assert!(access_modifiers(Modifiers::CTRL | Modifiers::ALT));
    }

    #[test]
    fn filtered_hints_show_only_the_remaining_code() {
        assert_eq!(filtered_hint("AA", ""), Some("AA"));
        assert_eq!(filtered_hint("AA", "A"), Some("A"));
        assert_eq!(filtered_hint("BA", "A"), None);
        assert_eq!(filtered_hint("AA", "AA"), None);
    }

    #[test]
    fn access_inventory_excludes_disabled_and_offscreen_controls() {
        let mut visible = State {
            enabled: true,
            visible: Cell::new(true),
            ..State::default()
        };
        let mut offscreen = State {
            enabled: true,
            visible: Cell::new(false),
            ..State::default()
        };
        let mut disabled = State {
            enabled: false,
            visible: Cell::new(true),
            ..State::default()
        };
        let mut inventory = AccessKeyInventory::default();
        inventory.custom(None, Rectangle::default(), &mut visible);
        inventory.custom(None, Rectangle::default(), &mut offscreen);
        inventory.custom(None, Rectangle::default(), &mut disabled);
        assert_eq!(inventory.control_count, 1);
        assert_eq!(inventory.generic_count, 1);
    }

    #[test]
    fn key_tip_boxes_stay_inside_their_controls() {
        for (bounds, length, position) in [
            (
                Rectangle::new((0.0, 0.0).into(), (48.0, 48.0).into()),
                1,
                HintPosition::BottomTrailing,
            ),
            (
                Rectangle::new((10.0, 20.0).into(), (60.0, 22.0).into()),
                2,
                HintPosition::Trailing,
            ),
        ] {
            let hint = hint_bounds(bounds, length, position).unwrap();
            assert!(hint.x >= bounds.x && hint.y >= bounds.y);
            assert!(hint.x + hint.width <= bounds.x + bounds.width);
            assert!(hint.y + hint.height <= bounds.y + bounds.height);
        }
    }

    #[test]
    fn access_prefix_filters_then_resolves_one_complete_code() {
        let mut first = State {
            code: Some("AA".into()),
            access_mode: true,
            ..State::default()
        };
        let mut second = State {
            code: Some("AB".into()),
            access_mode: true,
            ..State::default()
        };
        let mut prefix = AccessKeyMatches {
            candidate: "A",
            count: 0,
            exact: None,
        };
        prefix.custom(None, Rectangle::default(), &mut first);
        prefix.custom(None, Rectangle::default(), &mut second);
        assert_eq!(prefix.count, 2);
        assert_eq!(prefix.exact, None);

        let mut complete = AccessKeyMatches {
            candidate: "AA",
            count: 0,
            exact: None,
        };
        complete.custom(None, Rectangle::default(), &mut first);
        complete.custom(None, Rectangle::default(), &mut second);
        assert_eq!(complete.count, 1);
        assert_eq!(complete.exact.as_deref(), Some("AA"));
    }
}
use iced::advanced::layout;
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::text::{self, Renderer as _};
use iced::advanced::widget::operation::Focusable;
use iced::advanced::widget::{Id, Operation, Tree, tree};
use iced::advanced::{Clipboard, Layout, Renderer as _, Shell, Widget};
use iced::event;
use iced::keyboard::{self, Key, Modifiers, key};
use iced::mouse;
use iced::widget::{Button, button as iced_button};
use iced::{
    Background, Border, Color, Element, Event, Length, Padding, Pixels, Rectangle, Size, Theme,
    Vector, alignment,
};
use std::any::Any;
use std::cell::Cell;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Navigation {
    Next,
    Previous,
}

pub fn tab_navigation(
    key: &Key,
    modifiers: Modifiers,
    status: event::Status,
) -> Option<Navigation> {
    (status == event::Status::Ignored && matches!(key, Key::Named(key::Named::Tab))).then(|| {
        if modifiers.shift() {
            Navigation::Previous
        } else {
            Navigation::Next
        }
    })
}

fn access_character(key: &Key) -> Option<char> {
    let Key::Character(value) = key else {
        return None;
    };
    let mut characters = value.chars();
    let key = characters.next()?;
    characters
        .next()
        .is_none()
        .then_some(key)
        .filter(char::is_ascii_alphanumeric)
        .map(|key| key.to_ascii_uppercase())
}

fn access_modifiers(modifiers: Modifiers) -> bool {
    modifiers.alt() && modifiers.control()
}

fn letter_mask(keys: impl IntoIterator<Item = char>) -> u32 {
    keys.into_iter().fold(0, |mask, key| {
        let key = key.to_ascii_uppercase();
        if key.is_ascii_uppercase() {
            mask | (1 << (key as u8 - b'A'))
        } else {
            mask
        }
    })
}

fn available_initials(reserved: u32) -> impl Iterator<Item = char> {
    ('A'..='Z').filter(move |key| reserved & (1 << (*key as u8 - b'A')) == 0)
}

fn code_width(count: usize, reserved: u32) -> usize {
    let initials = available_initials(reserved).count().max(1);
    if count <= initials * 26 { 2 } else { 3 }
}

fn generic_code(index: usize, width: usize, reserved: u32) -> String {
    let suffix_capacity = 26_usize.pow((width - 1) as u32);
    let initial = available_initials(reserved)
        .nth(index / suffix_capacity)
        .expect("access-key capacity exceeded");
    let mut code = String::with_capacity(width);
    code.push(initial);
    let suffix = index % suffix_capacity;
    for place in (0..width - 1).rev() {
        let divisor = 26_usize.pow(place as u32);
        code.push((b'A' + ((suffix / divisor) % 26) as u8) as char);
    }
    code
}

fn filtered_hint<'a>(code: &'a str, prefix: &str) -> Option<&'a str> {
    code.strip_prefix(prefix)
        .filter(|remaining| !remaining.is_empty())
}

#[derive(Debug, Clone, Copy, Default)]
pub enum HintPosition {
    #[default]
    Trailing,
    BottomTrailing,
}

fn hint_bounds(bounds: Rectangle, label_len: usize, position: HintPosition) -> Option<Rectangle> {
    let inset = 2.0_f32;
    let width = (12.0 + label_len as f32 * 4.0).min((bounds.width - inset * 2.0).max(0.0));
    let height = 16.0_f32.min((bounds.height - inset * 2.0).max(0.0));
    if width < 12.0 || height < 12.0 {
        return None;
    }
    let x = bounds.x + bounds.width - width - inset;
    let y = match position {
        HintPosition::BottomTrailing => bounds.y + bounds.height - height - inset,
        HintPosition::Trailing => bounds.center_y() - height / 2.0,
    };
    Some(Rectangle {
        x,
        y,
        width,
        height,
    })
}

#[derive(Default)]
struct State {
    focused: bool,
    enabled: bool,
    visible: Cell<bool>,
    direct_key: Option<char>,
    code: Option<String>,
    prefix: String,
    access_mode: bool,
    pending_activation: bool,
}

impl Focusable for State {
    fn is_focused(&self) -> bool {
        self.focused
    }

    fn focus(&mut self) {
        self.focused = true;
    }

    fn unfocus(&mut self) {
        self.focused = false;
    }
}

#[derive(Default)]
struct AccessKeyInventory {
    generic_count: usize,
    direct_letters: u32,
    control_count: usize,
}

impl Operation for AccessKeyInventory {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
        operate(self);
    }

    fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Any) {
        let Some(state) = state.downcast_ref::<State>() else {
            return;
        };
        if !state.enabled || !state.visible.get() {
            return;
        }
        self.control_count += 1;
        if let Some(key) = state.direct_key {
            self.direct_letters |= letter_mask([key]);
        } else {
            self.generic_count += 1;
        }
    }
}

struct AssignAccessKeys {
    width: usize,
    reserved: u32,
    next_generic: usize,
}

impl Operation for AssignAccessKeys {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
        operate(self);
    }

    fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Any) {
        let Some(state) = state.downcast_mut::<State>() else {
            return;
        };
        state.prefix.clear();
        state.access_mode = state.enabled && state.visible.get();
        state.code = if !state.access_mode {
            None
        } else if let Some(key) = state.direct_key {
            Some(key.to_ascii_uppercase().to_string())
        } else {
            let code = generic_code(self.next_generic, self.width, self.reserved);
            self.next_generic += 1;
            Some(code)
        };
    }
}

struct SetAccessPrefix<'a> {
    prefix: &'a str,
}

impl Operation for SetAccessPrefix<'_> {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
        operate(self);
    }

    fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Any) {
        if let Some(state) = state.downcast_mut::<State>()
            && state.access_mode
        {
            state.prefix.clear();
            state.prefix.push_str(self.prefix);
        }
    }
}

struct CancelAccessKeys;

impl Operation for CancelAccessKeys {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
        operate(self);
    }

    fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Any) {
        if let Some(state) = state.downcast_mut::<State>() {
            state.access_mode = false;
            state.prefix.clear();
        }
    }
}

struct AccessKeyMatches<'a> {
    candidate: &'a str,
    count: usize,
    exact: Option<String>,
}

impl Operation for AccessKeyMatches<'_> {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
        operate(self);
    }

    fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Any) {
        let Some(state) = state.downcast_ref::<State>() else {
            return;
        };
        let Some(code) = state
            .access_mode
            .then_some(state.code.as_deref())
            .flatten()
            .filter(|code| code.starts_with(self.candidate))
        else {
            return;
        };
        self.count += 1;
        if code == self.candidate {
            self.exact = Some(code.to_owned());
        }
    }
}

struct FocusAccessKey<'a> {
    target: &'a str,
    next_is_target: bool,
}

impl Operation for FocusAccessKey<'_> {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
        operate(self);
    }

    fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Any) {
        let Some(state) = state.downcast_mut::<State>() else {
            self.next_is_target = false;
            return;
        };
        self.next_is_target = state.code.as_deref() == Some(self.target);
        state.pending_activation = self.next_is_target;
    }

    fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
        if self.next_is_target {
            state.focus();
        } else {
            state.unfocus();
        }
        self.next_is_target = false;
    }
}

#[derive(Default)]
struct FocusedEditable {
    next_is_control: bool,
    found: bool,
}

impl Operation for FocusedEditable {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
        operate(self);
    }

    fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Any) {
        self.next_is_control = state.is::<State>();
    }

    fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
        self.found |= state.is_focused() && !self.next_is_control;
        self.next_is_control = false;
    }
}

/// Adds native focus traversal and keyboard activation to an Iced control.
pub struct FocusableControl<'a, Message> {
    content: Element<'a, Message>,
    enabled: bool,
    hotkey: Option<char>,
    hint_position: HintPosition,
}

impl<'a, Message> FocusableControl<'a, Message> {
    pub fn new(content: impl Into<Element<'a, Message>>, enabled: bool) -> Self {
        Self {
            content: content.into(),
            enabled,
            hotkey: None,
            hint_position: HintPosition::Trailing,
        }
    }

    pub fn hotkey(mut self, hotkey: char) -> Self {
        self.hotkey = Some(hotkey.to_ascii_uppercase());
        self
    }

    pub fn hint_position(mut self, position: HintPosition) -> Self {
        self.hint_position = position;
        self
    }

    fn activate(
        &mut self,
        tree: &mut Tree,
        layout: Layout<'_>,
        renderer: &iced::Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) {
        let cursor = mouse::Cursor::Available(layout.bounds().center());
        for event in [
            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)),
        ] {
            self.content.as_widget_mut().update(
                &mut tree.children[0],
                &event,
                layout,
                cursor,
                renderer,
                clipboard,
                shell,
                viewport,
            );
        }
    }
}

impl<Message> Widget<Message, Theme, iced::Renderer> for FocusableControl<'_, Message> {
    fn tag(&self) -> tree::Tag {
        tree::Tag::of::<State>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(State::default())
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.content)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(std::slice::from_ref(&self.content));
        let state = tree.state.downcast_mut::<State>();
        state.enabled = self.enabled;
        state.direct_key = self.hotkey;
    }

    fn size(&self) -> Size<Length> {
        self.content.as_widget().size()
    }

    fn layout(
        &mut self,
        tree: &mut Tree,
        renderer: &iced::Renderer,
        limits: &layout::Limits,
    ) -> layout::Node {
        self.content
            .as_widget_mut()
            .layout(&mut tree.children[0], renderer, limits)
    }

    fn operate(
        &mut self,
        tree: &mut Tree,
        layout: Layout<'_>,
        renderer: &iced::Renderer,
        operation: &mut dyn Operation,
    ) {
        let state = tree.state.downcast_mut::<State>();
        state.enabled = self.enabled;
        state.direct_key = self.hotkey;
        operation.custom(None, layout.bounds(), state);
        if self.enabled {
            operation.focusable(None, layout.bounds(), state);
        } else {
            state.unfocus();
        }
        operation.container(None, layout.bounds());
        operation.traverse(&mut |operation| {
            self.content.as_widget_mut().operate(
                &mut tree.children[0],
                layout,
                renderer,
                operation,
            );
        });
    }

    fn update(
        &mut self,
        tree: &mut Tree,
        event: &Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &iced::Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) {
        let pending_activation = {
            let state = tree.state.downcast_mut::<State>();
            let pending = state.pending_activation;
            state.pending_activation = false;
            pending
        };
        if pending_activation && self.enabled {
            self.activate(tree, layout, renderer, clipboard, shell, viewport);
            shell.capture_event();
            return;
        }
        self.content.as_widget_mut().update(
            &mut tree.children[0],
            event,
            layout,
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        );
        let state = tree.state.downcast_mut::<State>();
        if shell.is_event_captured() || !self.enabled {
            return;
        }
        let activate = state.focused
            && matches!(
                &event,
                Event::Keyboard(keyboard::Event::KeyPressed {
                    key: Key::Named(key::Named::Enter) | Key::Named(key::Named::Space),
                    ..
                })
            );
        if activate {
            self.activate(tree, layout, renderer, clipboard, shell, viewport);
            shell.capture_event();
        }
    }

    fn mouse_interaction(
        &self,
        tree: &Tree,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &iced::Renderer,
    ) -> mouse::Interaction {
        self.content.as_widget().mouse_interaction(
            &tree.children[0],
            layout,
            cursor,
            viewport,
            renderer,
        )
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut iced::Renderer,
        theme: &Theme,
        style: &renderer::Style,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
    ) {
        self.content.as_widget().draw(
            &tree.children[0],
            renderer,
            theme,
            style,
            layout,
            cursor,
            viewport,
        );
        let state = tree.state.downcast_ref::<State>();
        let bounds = layout.bounds();
        state.visible.set(
            bounds
                .intersection(viewport)
                .is_some_and(|visible| visible.width > 0.0 && visible.height > 0.0),
        );
        if state.focused {
            renderer.fill_quad(
                renderer::Quad {
                    bounds,
                    border: Border {
                        color: Color::from_rgb8(0x00, 0x7F, 0xD4),
                        width: 2.0,
                        radius: 6.0.into(),
                    },
                    ..renderer::Quad::default()
                },
                Background::Color(Color::TRANSPARENT),
            );
        }
        if let Some(label) = state
            .access_mode
            .then_some(state.code.as_deref())
            .flatten()
            .and_then(|code| filtered_hint(code, &state.prefix))
        {
            let Some(badge) = hint_bounds(bounds, label.len(), self.hint_position) else {
                return;
            };
            renderer.fill_quad(
                renderer::Quad {
                    bounds: badge,
                    border: Border {
                        color: Color::BLACK,
                        width: 1.0,
                        radius: 4.0.into(),
                    },
                    ..renderer::Quad::default()
                },
                Background::Color(Color::from_rgb8(0xF2, 0xC1, 0x4E)),
            );
            renderer.fill_text(
                text::Text {
                    content: label.to_owned(),
                    font: renderer.default_font(),
                    size: Pixels(9.0),
                    line_height: text::LineHeight::Relative(1.0),
                    bounds: badge.size(),
                    align_x: alignment::Horizontal::Center.into(),
                    align_y: alignment::Vertical::Center,
                    shaping: text::Shaping::Advanced,
                    wrapping: text::Wrapping::None,
                },
                badge.center(),
                Color::BLACK,
                *viewport,
            );
        }
    }

    fn overlay<'b>(
        &'b mut self,
        tree: &'b mut Tree,
        layout: Layout<'b>,
        renderer: &iced::Renderer,
        viewport: &Rectangle,
        translation: Vector,
    ) -> Option<overlay::Element<'b, Message, Theme, iced::Renderer>> {
        self.content.as_widget_mut().overlay(
            &mut tree.children[0],
            layout,
            renderer,
            viewport,
            translation,
        )
    }
}

impl<'a, Message: 'a> From<FocusableControl<'a, Message>> for Element<'a, Message> {
    fn from(control: FocusableControl<'a, Message>) -> Self {
        Element::new(control)
    }
}

pub fn focusable<'a, Message: 'a>(
    content: impl Into<Element<'a, Message>>,
    enabled: bool,
) -> FocusableControl<'a, Message> {
    FocusableControl::new(content, enabled)
}

#[derive(Default)]
struct AccessKeyScopeState {
    active: bool,
    access_modifiers_down: bool,
    cancelled_until_modifiers_release: bool,
    prefix: String,
}

/// Owns one access-key namespace for the visible application surface.
pub struct AccessKeyScope<'a, Message> {
    content: Element<'a, Message>,
}

impl<'a, Message> AccessKeyScope<'a, Message> {
    fn new(content: impl Into<Element<'a, Message>>) -> Self {
        Self {
            content: content.into(),
        }
    }

    fn operate(
        &mut self,
        tree: &mut Tree,
        layout: Layout<'_>,
        renderer: &iced::Renderer,
        operation: &mut dyn Operation,
    ) {
        self.content
            .as_widget_mut()
            .operate(&mut tree.children[0], layout, renderer, operation);
    }

    fn begin(&mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &iced::Renderer) -> bool {
        let mut focused_editable = FocusedEditable::default();
        self.operate(tree, layout, renderer, &mut focused_editable);
        if focused_editable.found {
            return false;
        }

        let mut inventory = AccessKeyInventory::default();
        self.operate(tree, layout, renderer, &mut inventory);
        if inventory.control_count == 0 {
            return false;
        }
        let mut assign = AssignAccessKeys {
            width: code_width(inventory.generic_count, inventory.direct_letters),
            reserved: inventory.direct_letters,
            next_generic: 0,
        };
        self.operate(tree, layout, renderer, &mut assign);
        true
    }

    fn cancel(&mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &iced::Renderer) {
        self.operate(tree, layout, renderer, &mut CancelAccessKeys);
    }
}

impl<Message> Widget<Message, Theme, iced::Renderer> for AccessKeyScope<'_, Message> {
    fn tag(&self) -> tree::Tag {
        tree::Tag::of::<AccessKeyScopeState>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(AccessKeyScopeState::default())
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.content)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(std::slice::from_ref(&self.content));
    }

    fn size(&self) -> Size<Length> {
        self.content.as_widget().size()
    }

    fn layout(
        &mut self,
        tree: &mut Tree,
        renderer: &iced::Renderer,
        limits: &layout::Limits,
    ) -> layout::Node {
        self.content
            .as_widget_mut()
            .layout(&mut tree.children[0], renderer, limits)
    }

    fn operate(
        &mut self,
        tree: &mut Tree,
        layout: Layout<'_>,
        renderer: &iced::Renderer,
        operation: &mut dyn Operation,
    ) {
        operation.container(None, layout.bounds());
        operation.traverse(&mut |operation| {
            self.content.as_widget_mut().operate(
                &mut tree.children[0],
                layout,
                renderer,
                operation,
            );
        });
    }

    fn update(
        &mut self,
        tree: &mut Tree,
        event: &Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &iced::Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) {
        let modifier_update = match &event {
            Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers))
            | Event::Keyboard(keyboard::Event::KeyPressed { modifiers, .. })
            | Event::Keyboard(keyboard::Event::KeyReleased { modifiers, .. }) => Some(*modifiers),
            _ => None,
        };
        if let Some(modifiers) = modifier_update {
            let (were_down, cancelled) = {
                let state = tree.state.downcast_ref::<AccessKeyScopeState>();
                (
                    state.access_modifiers_down,
                    state.cancelled_until_modifiers_release,
                )
            };
            let are_down = access_modifiers(modifiers);
            if are_down && !were_down && !cancelled {
                let active = self.begin(tree, layout, renderer);
                let state = tree.state.downcast_mut::<AccessKeyScopeState>();
                state.active = active;
                state.prefix.clear();
            } else if !are_down && were_down {
                self.cancel(tree, layout, renderer);
                let state = tree.state.downcast_mut::<AccessKeyScopeState>();
                state.active = false;
                state.prefix.clear();
                state.cancelled_until_modifiers_release = false;
            }
            tree.state
                .downcast_mut::<AccessKeyScopeState>()
                .access_modifiers_down = are_down;
        }

        if let Event::Keyboard(keyboard::Event::KeyPressed {
            key: Key::Named(key::Named::Escape),
            ..
        }) = &event
            && tree.state.downcast_ref::<AccessKeyScopeState>().active
        {
            self.cancel(tree, layout, renderer);
            let state = tree.state.downcast_mut::<AccessKeyScopeState>();
            state.active = false;
            state.prefix.clear();
            state.cancelled_until_modifiers_release = true;
            shell.capture_event();
            return;
        }

        if let Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) = &event
            && access_modifiers(*modifiers)
            && let Some(key) = access_character(key)
        {
            let (mut active, cancelled, prefix) = {
                let state = tree.state.downcast_ref::<AccessKeyScopeState>();
                (
                    state.active,
                    state.cancelled_until_modifiers_release,
                    state.prefix.clone(),
                )
            };
            if cancelled {
                shell.capture_event();
                return;
            }
            if !active {
                active = self.begin(tree, layout, renderer);
                if !active {
                    self.content.as_widget_mut().update(
                        &mut tree.children[0],
                        event,
                        layout,
                        cursor,
                        renderer,
                        clipboard,
                        shell,
                        viewport,
                    );
                    return;
                }
                let state = tree.state.downcast_mut::<AccessKeyScopeState>();
                state.active = true;
                state.prefix.clear();
            }

            let candidate = format!("{prefix}{key}");
            let mut matches = AccessKeyMatches {
                candidate: &candidate,
                count: 0,
                exact: None,
            };
            self.operate(tree, layout, renderer, &mut matches);
            let count = matches.count;
            let exact = matches.exact.take();
            drop(matches);
            if count == 0 {
                shell.capture_event();
                return;
            }

            self.operate(
                tree,
                layout,
                renderer,
                &mut SetAccessPrefix { prefix: &candidate },
            );
            tree.state.downcast_mut::<AccessKeyScopeState>().prefix = candidate;

            if let Some(target) = exact {
                self.operate(
                    tree,
                    layout,
                    renderer,
                    &mut FocusAccessKey {
                        target: &target,
                        next_is_target: false,
                    },
                );
                self.content.as_widget_mut().update(
                    &mut tree.children[0],
                    event,
                    layout,
                    cursor,
                    renderer,
                    clipboard,
                    shell,
                    viewport,
                );
                self.cancel(tree, layout, renderer);
                let state = tree.state.downcast_mut::<AccessKeyScopeState>();
                state.active = false;
                state.prefix.clear();
                state.cancelled_until_modifiers_release = true;
            }
            shell.capture_event();
            return;
        }

        if matches!(event, Event::Mouse(mouse::Event::ButtonPressed(_)))
            && tree.state.downcast_ref::<AccessKeyScopeState>().active
        {
            self.cancel(tree, layout, renderer);
            let state = tree.state.downcast_mut::<AccessKeyScopeState>();
            state.active = false;
            state.prefix.clear();
        }

        self.content.as_widget_mut().update(
            &mut tree.children[0],
            event,
            layout,
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        );
    }

    fn mouse_interaction(
        &self,
        tree: &Tree,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &iced::Renderer,
    ) -> mouse::Interaction {
        self.content.as_widget().mouse_interaction(
            &tree.children[0],
            layout,
            cursor,
            viewport,
            renderer,
        )
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut iced::Renderer,
        theme: &Theme,
        style: &renderer::Style,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
    ) {
        self.content.as_widget().draw(
            &tree.children[0],
            renderer,
            theme,
            style,
            layout,
            cursor,
            viewport,
        );
    }

    fn overlay<'b>(
        &'b mut self,
        tree: &'b mut Tree,
        layout: Layout<'b>,
        renderer: &iced::Renderer,
        viewport: &Rectangle,
        translation: Vector,
    ) -> Option<overlay::Element<'b, Message, Theme, iced::Renderer>> {
        self.content.as_widget_mut().overlay(
            &mut tree.children[0],
            layout,
            renderer,
            viewport,
            translation,
        )
    }
}

impl<'a, Message: 'a> From<AccessKeyScope<'a, Message>> for Element<'a, Message> {
    fn from(scope: AccessKeyScope<'a, Message>) -> Self {
        Element::new(scope)
    }
}

pub fn scope<'a, Message: 'a>(
    content: impl Into<Element<'a, Message>>,
) -> AccessKeyScope<'a, Message> {
    AccessKeyScope::new(content)
}

/// Keeps covered content out of keyboard traversal while a blocking overlay is open.
pub struct SuspendedKeyboard<'a, Message> {
    content: Element<'a, Message>,
}

impl<Message> Widget<Message, Theme, iced::Renderer> for SuspendedKeyboard<'_, Message> {
    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.content)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(std::slice::from_ref(&self.content));
    }

    fn size(&self) -> Size<Length> {
        self.content.as_widget().size()
    }

    fn layout(
        &mut self,
        tree: &mut Tree,
        renderer: &iced::Renderer,
        limits: &layout::Limits,
    ) -> layout::Node {
        self.content
            .as_widget_mut()
            .layout(&mut tree.children[0], renderer, limits)
    }

    fn operate(
        &mut self,
        _tree: &mut Tree,
        _layout: Layout<'_>,
        _renderer: &iced::Renderer,
        _operation: &mut dyn Operation,
    ) {
    }

    fn update(
        &mut self,
        tree: &mut Tree,
        event: &Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &iced::Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) {
        if matches!(event, Event::Keyboard(_)) {
            shell.capture_event();
        } else {
            self.content.as_widget_mut().update(
                &mut tree.children[0],
                event,
                layout,
                cursor,
                renderer,
                clipboard,
                shell,
                viewport,
            );
        }
    }

    fn mouse_interaction(
        &self,
        tree: &Tree,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &iced::Renderer,
    ) -> mouse::Interaction {
        self.content.as_widget().mouse_interaction(
            &tree.children[0],
            layout,
            cursor,
            viewport,
            renderer,
        )
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut iced::Renderer,
        theme: &Theme,
        style: &renderer::Style,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
    ) {
        self.content.as_widget().draw(
            &tree.children[0],
            renderer,
            theme,
            style,
            layout,
            cursor,
            viewport,
        );
    }

    fn overlay<'b>(
        &'b mut self,
        tree: &'b mut Tree,
        layout: Layout<'b>,
        renderer: &iced::Renderer,
        viewport: &Rectangle,
        translation: Vector,
    ) -> Option<overlay::Element<'b, Message, Theme, iced::Renderer>> {
        self.content.as_widget_mut().overlay(
            &mut tree.children[0],
            layout,
            renderer,
            viewport,
            translation,
        )
    }
}

impl<'a, Message: 'a> From<SuspendedKeyboard<'a, Message>> for Element<'a, Message> {
    fn from(suspended: SuspendedKeyboard<'a, Message>) -> Self {
        Element::new(suspended)
    }
}

pub fn suspend<'a, Message: 'a>(
    content: impl Into<Element<'a, Message>>,
) -> SuspendedKeyboard<'a, Message> {
    SuspendedKeyboard {
        content: content.into(),
    }
}

pub struct KeyboardButton<'a, Message> {
    button: Button<'a, Message>,
    enabled: bool,
    hotkey: Option<char>,
    hint_position: HintPosition,
}

impl<'a, Message: Clone + 'a> KeyboardButton<'a, Message> {
    pub fn width(mut self, width: impl Into<Length>) -> Self {
        self.button = self.button.width(width);
        self
    }

    pub fn height(mut self, height: impl Into<Length>) -> Self {
        self.button = self.button.height(height);
        self
    }

    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
        self.button = self.button.padding(padding);
        self
    }

    pub fn on_press(mut self, message: Message) -> Self {
        self.button = self.button.on_press(message);
        self.enabled = true;
        self
    }

    pub fn on_press_with(mut self, message: impl Fn() -> Message + 'a) -> Self {
        self.button = self.button.on_press_with(message);
        self.enabled = true;
        self
    }

    pub fn on_press_maybe(mut self, message: Option<Message>) -> Self {
        self.enabled = message.is_some();
        self.button = self.button.on_press_maybe(message);
        self
    }

    pub fn clip(mut self, clip: bool) -> Self {
        self.button = self.button.clip(clip);
        self
    }

    pub fn style(
        mut self,
        style: impl Fn(&Theme, iced::widget::button::Status) -> iced::widget::button::Style + 'a,
    ) -> Self {
        self.button = self.button.style(style);
        self
    }

    pub fn hotkey(mut self, hotkey: char) -> Self {
        self.hotkey = Some(hotkey);
        self
    }

    pub fn hint_position(mut self, position: HintPosition) -> Self {
        self.hint_position = position;
        self
    }
}

impl<'a, Message: Clone + 'a> From<KeyboardButton<'a, Message>> for Element<'a, Message> {
    fn from(button: KeyboardButton<'a, Message>) -> Self {
        let control = FocusableControl::new(button.button, button.enabled)
            .hint_position(button.hint_position);
        match button.hotkey {
            Some(hotkey) => control.hotkey(hotkey).into(),
            None => control.into(),
        }
    }
}

pub fn button<'a, Message: Clone + 'a>(
    content: impl Into<Element<'a, Message>>,
) -> KeyboardButton<'a, Message> {
    KeyboardButton {
        button: iced_button(content),
        enabled: false,
        hotkey: None,
        hint_position: HintPosition::Trailing,
    }
}
