//! This experimental example illustrates how to create widgets using the `bevy_ui_widgets` widget set.
//!
//! These widgets have no inherent styling, so this example also shows how to implement custom styles.
//!
//! The patterns shown here are likely to change substantially as the `bevy_ui_widgets` crate
//! matures, so please exercise caution if you are using this as a reference for your own code,
//! and note that there are still "user experience" issues with this API.

use bevy::{
    color::palettes::basic::*,
    input_focus::{
        tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
        FocusCause, InputFocus,
    },
    picking::hover::Hovered,
    prelude::*,
    ui::{Checked, InteractionDisabled, Pressed},
    ui_widgets::{
        checkbox_self_update, observe,
        popover::{Popover, PopoverAlign, PopoverPlacement, PopoverSide},
        Activate, Button, Checkbox, MenuAction, MenuButton, MenuEvent, MenuItem, MenuPopup,
        RadioButton, RadioGroup, Slider, SliderDragState, SliderRange, SliderThumb, SliderValue,
        TrackClick, ValueChange,
    },
};

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, TabNavigationPlugin))
        .insert_resource(DemoWidgetStates {
            slider_value: 50.0,
            slider_click: TrackClick::Snap,
        })
        .add_systems(Startup, setup)
        .add_systems(
            Update,
            (
                update_widget_values,
                update_button_style,
                update_button_style2,
                update_slider_style.after(update_widget_values),
                update_slider_style2.after(update_widget_values),
                update_checkbox_or_radio_style.after(update_widget_values),
                update_checkbox_or_radio_style2.after(update_widget_values),
                update_menu_item_style,
                update_menu_item_style2,
                toggle_disabled,
            ),
        )
        .run();
}

const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
const SLIDER_TRACK: Color = Color::srgb(0.05, 0.05, 0.05);
const SLIDER_THUMB: Color = Color::srgb(0.35, 0.75, 0.35);
const ELEMENT_OUTLINE: Color = Color::srgb(0.45, 0.45, 0.45);
const ELEMENT_FILL: Color = Color::srgb(0.35, 0.75, 0.35);
const ELEMENT_FILL_DISABLED: Color = Color::srgb(0.5019608, 0.5019608, 0.5019608);

/// Marker which identifies buttons with a particular style, in this case the "Demo style".
#[derive(Component)]
struct DemoButton;

/// Marker which identifies sliders with a particular style.
#[derive(Component, Default)]
struct DemoSlider;

/// Marker which identifies the slider's thumb element.
#[derive(Component, Default)]
struct DemoSliderThumb;

/// Marker which identifies checkboxes with a particular style.
#[derive(Component, Default)]
struct DemoCheckbox;

/// Marker which identifies a styled radio button. We'll use this to change the track click
/// behavior.
#[derive(Component, Default)]
struct DemoRadio(TrackClick);

/// Menu anchor marker
#[derive(Component)]
struct DemoMenuAnchor;

/// Menu button styling marker
#[derive(Component)]
struct DemoMenuButton;

/// Menu item styling marker
#[derive(Component)]
struct DemoMenuItem;

/// A struct to hold the state of various widgets shown in the demo.
///
/// While it is possible to use the widget's own state components as the source of truth,
/// in many cases widgets will be used to display dynamic data coming from deeper within the app,
/// using some kind of data-binding. This example shows how to maintain an external source of
/// truth for widget states.
#[derive(Resource)]
struct DemoWidgetStates {
    slider_value: f32,
    slider_click: TrackClick,
}

/// Update the widget states based on the changing resource.
fn update_widget_values(
    res: Res<DemoWidgetStates>,
    mut sliders: Query<(Entity, &mut Slider), With<DemoSlider>>,
    radios: Query<(Entity, &DemoRadio, Has<Checked>)>,
    mut commands: Commands,
) {
    if res.is_changed() {
        for (slider_ent, mut slider) in sliders.iter_mut() {
            commands
                .entity(slider_ent)
                .insert(SliderValue(res.slider_value));
            slider.track_click = res.slider_click;
        }

        for (radio_id, radio_value, checked) in radios.iter() {
            let will_be_checked = radio_value.0 == res.slider_click;
            if will_be_checked != checked {
                if will_be_checked {
                    commands.entity(radio_id).insert(Checked);
                } else {
                    commands.entity(radio_id).remove::<Checked>();
                }
            }
        }
    }
}

fn setup(mut commands: Commands, assets: Res<AssetServer>) {
    // ui camera
    commands.spawn(Camera2d);
    commands.spawn(demo_root(&assets));
}

fn demo_root(asset_server: &AssetServer) -> impl Bundle {
    (
        Node {
            width: percent(100),
            height: percent(100),
            align_items: AlignItems::Center,
            justify_content: JustifyContent::Center,
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            row_gap: px(10),
            ..default()
        },
        TabGroup::default(),
        children![
            (
                button(asset_server),
                observe(|_activate: On<Activate>| {
                    info!("Button clicked!");
                }),
            ),
            (
                slider(0.0, 100.0, 50.0),
                observe(
                    |value_change: On<ValueChange<f32>>,
                     mut widget_states: ResMut<DemoWidgetStates>| {
                        widget_states.slider_value = value_change.value;
                    },
                )
            ),
            (
                checkbox(asset_server, "Checkbox"),
                observe(checkbox_self_update)
            ),
            (
                radio_group(asset_server),
                observe(
                    |value_change: On<ValueChange<Entity>>,
                     mut widget_states: ResMut<DemoWidgetStates>,
                     q_radios: Query<&DemoRadio>| {
                        if let Ok(radio) = q_radios.get(value_change.value) {
                            widget_states.slider_click = radio.0;
                        }
                    },
                )
            ),
            menu_button(asset_server),
            Text::new("Press 'D' to toggle widget disabled states"),
        ],
    )
}

fn button(asset_server: &AssetServer) -> impl Bundle {
    (
        Node {
            width: px(150),
            height: px(65),
            border: UiRect::all(px(5)),
            border_radius: BorderRadius::MAX,
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Center,
            ..default()
        },
        DemoButton,
        Button,
        Hovered::default(),
        TabIndex(0),
        BorderColor::all(Color::BLACK),
        BackgroundColor(NORMAL_BUTTON),
        children![(
            Text::new("Button"),
            TextFont {
                font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
                font_size: FontSize::Px(33.0),
                ..default()
            },
            TextColor(Color::srgb(0.9, 0.9, 0.9)),
            TextShadow::default(),
        )],
    )
}

fn menu_button(asset_server: &AssetServer) -> impl Bundle {
    (
        Node { ..default() },
        DemoMenuAnchor,
        observe(on_menu_event),
        children![(
            Node {
                width: px(200),
                height: px(65),
                border: UiRect::all(px(5)),
                box_sizing: BoxSizing::BorderBox,
                justify_content: JustifyContent::SpaceBetween,
                align_items: AlignItems::Center,
                padding: UiRect::axes(px(16), px(0)),
                border_radius: BorderRadius::all(px(5)),
                ..default()
            },
            DemoMenuButton,
            Button,
            MenuButton,
            Hovered::default(),
            TabIndex(0),
            BorderColor::all(Color::BLACK),
            BackgroundColor(NORMAL_BUTTON),
            children![
                (
                    Text::new("Menu"),
                    TextFont {
                        font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
                        font_size: FontSize::Px(33.0),
                        ..default()
                    },
                    TextColor(Color::srgb(0.9, 0.9, 0.9)),
                    TextShadow::default(),
                ),
                (
                    Node {
                        width: px(12),
                        height: px(12),
                        ..default()
                    },
                    BackgroundColor(GRAY.into()),
                )
            ],
        )],
    )
}

fn update_button_style(
    mut buttons: Query<
        (
            Has<Pressed>,
            &Hovered,
            Has<InteractionDisabled>,
            &mut BackgroundColor,
            &mut BorderColor,
            &Children,
        ),
        (
            Or<(
                Changed<Pressed>,
                Changed<Hovered>,
                Added<InteractionDisabled>,
            )>,
            With<DemoButton>,
        ),
    >,
    mut text_query: Query<&mut Text>,
) {
    for (pressed, hovered, disabled, mut color, mut border_color, children) in &mut buttons {
        let mut text = text_query.get_mut(children[0]).unwrap();
        set_button_style(
            disabled,
            hovered.get(),
            pressed,
            &mut color,
            &mut border_color,
            &mut text,
        );
    }
}

/// Supplementary system to detect removed marker components
fn update_button_style2(
    mut buttons: Query<
        (
            Has<Pressed>,
            &Hovered,
            Has<InteractionDisabled>,
            &mut BackgroundColor,
            &mut BorderColor,
            &Children,
        ),
        With<DemoButton>,
    >,
    mut removed_depressed: RemovedComponents<Pressed>,
    mut removed_disabled: RemovedComponents<InteractionDisabled>,
    mut text_query: Query<&mut Text>,
) {
    removed_depressed
        .read()
        .chain(removed_disabled.read())
        .for_each(|entity| {
            if let Ok((pressed, hovered, disabled, mut color, mut border_color, children)) =
                buttons.get_mut(entity)
            {
                let mut text = text_query.get_mut(children[0]).unwrap();
                set_button_style(
                    disabled,
                    hovered.get(),
                    pressed,
                    &mut color,
                    &mut border_color,
                    &mut text,
                );
            }
        });
}

fn set_button_style(
    disabled: bool,
    hovered: bool,
    pressed: bool,
    color: &mut BackgroundColor,
    border_color: &mut BorderColor,
    text: &mut Text,
) {
    match (disabled, hovered, pressed) {
        // Disabled button
        (true, _, _) => {
            **text = "Disabled".to_string();
            *color = NORMAL_BUTTON.into();
            border_color.set_all(GRAY);
        }

        // Pressed and hovered button
        (false, true, true) => {
            **text = "Press".to_string();
            *color = PRESSED_BUTTON.into();
            border_color.set_all(RED);
        }

        // Hovered, unpressed button
        (false, true, false) => {
            **text = "Hover".to_string();
            *color = HOVERED_BUTTON.into();
            border_color.set_all(WHITE);
        }

        // Unhovered button (either pressed or not).
        (false, false, _) => {
            **text = "Button".to_string();
            *color = NORMAL_BUTTON.into();
            border_color.set_all(BLACK);
        }
    }
}

/// Create a demo slider
fn slider(min: f32, max: f32, value: f32) -> impl Bundle {
    (
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Stretch,
            justify_items: JustifyItems::Center,
            column_gap: px(4),
            height: px(12),
            width: percent(30),
            ..default()
        },
        Name::new("Slider"),
        Hovered::default(),
        DemoSlider,
        Slider {
            track_click: TrackClick::Snap,
            ..Default::default()
        },
        SliderValue(value),
        SliderRange::new(min, max),
        TabIndex(0),
        Children::spawn((
            // Slider background rail
            Spawn((
                Node {
                    height: px(6),
                    border_radius: BorderRadius::all(px(3)),
                    ..default()
                },
                BackgroundColor(SLIDER_TRACK), // Border color for the slider
            )),
            // Invisible track to allow absolute placement of thumb entity. This is narrower than
            // the actual slider, which allows us to position the thumb entity using simple
            // percentages, without having to measure the actual width of the slider thumb.
            Spawn((
                Node {
                    display: Display::Flex,
                    position_type: PositionType::Absolute,
                    left: px(0),
                    // Track is short by 12px to accommodate the thumb.
                    right: px(12),
                    top: px(0),
                    bottom: px(0),
                    ..default()
                },
                children![(
                    // Thumb
                    DemoSliderThumb,
                    SliderThumb,
                    Node {
                        display: Display::Flex,
                        width: px(12),
                        height: px(12),
                        position_type: PositionType::Absolute,
                        left: percent(0), // This will be updated by the slider's value
                        border_radius: BorderRadius::MAX,
                        ..default()
                    },
                    BackgroundColor(SLIDER_THUMB),
                )],
            )),
        )),
    )
}

/// Update the visuals of the slider based on the slider state.
fn update_slider_style(
    sliders: Query<
        (
            Entity,
            &SliderValue,
            &SliderRange,
            &Hovered,
            &SliderDragState,
            Has<InteractionDisabled>,
        ),
        (
            Or<(
                Changed<SliderValue>,
                Changed<SliderRange>,
                Changed<Hovered>,
                Changed<SliderDragState>,
                Added<InteractionDisabled>,
            )>,
            With<DemoSlider>,
        ),
    >,
    children: Query<&Children>,
    mut thumbs: Query<(&mut Node, &mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
) {
    for (slider_ent, value, range, hovered, drag_state, disabled) in sliders.iter() {
        for child in children.iter_descendants(slider_ent) {
            if let Ok((mut thumb_node, mut thumb_bg, is_thumb)) = thumbs.get_mut(child)
                && is_thumb
            {
                thumb_node.left = percent(range.thumb_position(value.0) * 100.0);
                thumb_bg.0 = thumb_color(disabled, hovered.0 | drag_state.dragging);
            }
        }
    }
}

fn update_slider_style2(
    sliders: Query<
        (Entity, &Hovered, &SliderDragState, Has<InteractionDisabled>),
        With<DemoSlider>,
    >,
    children: Query<&Children>,
    mut thumbs: Query<(&mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
    mut removed_disabled: RemovedComponents<InteractionDisabled>,
) {
    removed_disabled.read().for_each(|entity| {
        if let Ok((slider_ent, hovered, drag_state, disabled)) = sliders.get(entity) {
            for child in children.iter_descendants(slider_ent) {
                if let Ok((mut thumb_bg, is_thumb)) = thumbs.get_mut(child)
                    && is_thumb
                {
                    thumb_bg.0 = thumb_color(disabled, hovered.0 | drag_state.dragging);
                }
            }
        }
    });
}

fn thumb_color(disabled: bool, hovered: bool) -> Color {
    match (disabled, hovered) {
        (true, _) => ELEMENT_FILL_DISABLED,

        (false, true) => SLIDER_THUMB.lighter(0.3),

        _ => SLIDER_THUMB,
    }
}

/// Create a demo checkbox
fn checkbox(asset_server: &AssetServer, caption: &str) -> impl Bundle {
    (
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Row,
            justify_content: JustifyContent::FlexStart,
            align_items: AlignItems::Center,
            align_content: AlignContent::Center,
            column_gap: px(4),
            ..default()
        },
        Name::new("Checkbox"),
        Hovered::default(),
        DemoCheckbox,
        Checkbox,
        TabIndex(0),
        Children::spawn((
            Spawn((
                // Checkbox outer
                Node {
                    display: Display::Flex,
                    width: px(16),
                    height: px(16),
                    border: UiRect::all(px(2)),
                    border_radius: BorderRadius::all(px(3)),
                    ..default()
                },
                BorderColor::all(ELEMENT_OUTLINE), // Border color for the checkbox
                children![
                    // Checkbox inner
                    (
                        Node {
                            display: Display::Flex,
                            width: px(8),
                            height: px(8),
                            position_type: PositionType::Absolute,
                            left: px(2),
                            top: px(2),
                            ..default()
                        },
                        BackgroundColor(ELEMENT_FILL),
                    ),
                ],
            )),
            Spawn((
                Text::new(caption),
                TextFont {
                    font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
                    font_size: FontSize::Px(20.0),
                    ..default()
                },
            )),
        )),
    )
}

// Update the element's styles.
fn update_checkbox_or_radio_style(
    mut q_checkbox: Query<
        (Has<Checked>, &Hovered, Has<InteractionDisabled>, &Children),
        (
            Or<(With<DemoCheckbox>, With<DemoRadio>)>,
            Or<(
                Added<DemoCheckbox>,
                Changed<Hovered>,
                Added<Checked>,
                Added<InteractionDisabled>,
            )>,
        ),
    >,
    mut q_border_color: Query<
        (&mut BorderColor, &mut Children),
        (Without<DemoCheckbox>, Without<DemoRadio>),
    >,
    mut q_bg_color: Query<&mut BackgroundColor, (Without<DemoCheckbox>, Without<Children>)>,
) {
    for (checked, Hovered(is_hovering), is_disabled, children) in q_checkbox.iter_mut() {
        let Some(border_id) = children.first() else {
            continue;
        };

        let Ok((mut border_color, border_children)) = q_border_color.get_mut(*border_id) else {
            continue;
        };

        let Some(mark_id) = border_children.first() else {
            warn!("Checkbox does not have a mark entity.");
            continue;
        };

        let Ok(mut mark_bg) = q_bg_color.get_mut(*mark_id) else {
            warn!("Checkbox mark entity lacking a background color.");
            continue;
        };

        set_checkbox_or_radio_style(
            is_disabled,
            *is_hovering,
            checked,
            &mut border_color,
            &mut mark_bg,
        );
    }
}

fn update_checkbox_or_radio_style2(
    mut q_checkbox: Query<
        (Has<Checked>, &Hovered, Has<InteractionDisabled>, &Children),
        Or<(With<DemoCheckbox>, With<DemoRadio>)>,
    >,
    mut q_border_color: Query<
        (&mut BorderColor, &mut Children),
        (Without<DemoCheckbox>, Without<DemoRadio>),
    >,
    mut q_bg_color: Query<
        &mut BackgroundColor,
        (Without<DemoCheckbox>, Without<DemoRadio>, Without<Children>),
    >,
    mut removed_checked: RemovedComponents<Checked>,
    mut removed_disabled: RemovedComponents<InteractionDisabled>,
) {
    removed_checked
        .read()
        .chain(removed_disabled.read())
        .for_each(|entity| {
            if let Ok((checked, Hovered(is_hovering), is_disabled, children)) =
                q_checkbox.get_mut(entity)
            {
                let Some(border_id) = children.first() else {
                    return;
                };

                let Ok((mut border_color, border_children)) = q_border_color.get_mut(*border_id)
                else {
                    return;
                };

                let Some(mark_id) = border_children.first() else {
                    warn!("Checkbox does not have a mark entity.");
                    return;
                };

                let Ok(mut mark_bg) = q_bg_color.get_mut(*mark_id) else {
                    warn!("Checkbox mark entity lacking a background color.");
                    return;
                };

                set_checkbox_or_radio_style(
                    is_disabled,
                    *is_hovering,
                    checked,
                    &mut border_color,
                    &mut mark_bg,
                );
            }
        });
}

fn set_checkbox_or_radio_style(
    disabled: bool,
    hovering: bool,
    checked: bool,
    border_color: &mut BorderColor,
    mark_bg: &mut BackgroundColor,
) {
    let color: Color = if disabled {
        // If the element is disabled, use a lighter color
        ELEMENT_OUTLINE.with_alpha(0.2)
    } else if hovering {
        // If hovering, use a lighter color
        ELEMENT_OUTLINE.lighter(0.2)
    } else {
        // Default color for the element
        ELEMENT_OUTLINE
    };

    // Update the background color of the element
    border_color.set_all(color);

    let mark_color: Color = match (disabled, checked) {
        (true, true) => ELEMENT_FILL_DISABLED,
        (false, true) => ELEMENT_FILL,
        (_, false) => Srgba::NONE.into(),
    };

    if mark_bg.0 != mark_color {
        // Update the color of the element
        mark_bg.0 = mark_color;
    }
}

/// Create a demo radio group
fn radio_group(asset_server: &AssetServer) -> impl Bundle {
    (
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            align_items: AlignItems::Start,
            column_gap: px(4),
            ..default()
        },
        Name::new("RadioGroup"),
        RadioGroup,
        TabIndex::default(),
        children![
            (radio(asset_server, TrackClick::Drag, "Slider Drag"),),
            (radio(asset_server, TrackClick::Step, "Slider Step"),),
            (radio(asset_server, TrackClick::Snap, "Slider Snap"),)
        ],
    )
}

/// Create a demo radio button
fn radio(asset_server: &AssetServer, value: TrackClick, caption: &str) -> impl Bundle {
    (
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Row,
            justify_content: JustifyContent::FlexStart,
            align_items: AlignItems::Center,
            align_content: AlignContent::Center,
            column_gap: px(4),
            ..default()
        },
        Name::new("RadioButton"),
        Hovered::default(),
        DemoRadio(value),
        RadioButton,
        Children::spawn((
            Spawn((
                // Radio outer
                Node {
                    display: Display::Flex,
                    width: px(16),
                    height: px(16),
                    border: UiRect::all(px(2)),
                    border_radius: BorderRadius::MAX,
                    ..default()
                },
                BorderColor::all(ELEMENT_OUTLINE), // Border color for the radio button
                children![
                    // Radio inner
                    (
                        Node {
                            display: Display::Flex,
                            width: px(8),
                            height: px(8),
                            position_type: PositionType::Absolute,
                            left: px(2),
                            top: px(2),
                            border_radius: BorderRadius::MAX,
                            ..default()
                        },
                        BackgroundColor(ELEMENT_FILL),
                    ),
                ],
            )),
            Spawn((
                Text::new(caption),
                TextFont {
                    font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
                    font_size: FontSize::Px(20.0),
                    ..default()
                },
            )),
        )),
    )
}

fn on_menu_event(
    menu_event: On<MenuEvent>,
    q_anchor: Single<(Entity, &Children), With<DemoMenuAnchor>>,
    q_popup: Query<Entity, With<MenuPopup>>,
    assets: Res<AssetServer>,
    mut focus: ResMut<InputFocus>,
    mut commands: Commands,
) {
    let (anchor, children) = q_anchor.into_inner();
    let popup = children.iter().find_map(|c| q_popup.get(c).ok());
    info!("Menu action: {:?}", menu_event.action);
    match menu_event.action {
        MenuAction::Open(_) => {
            if popup.is_none() {
                spawn_menu(anchor, assets, commands);
            }
        }
        MenuAction::Toggle => match popup {
            Some(popup) => commands.entity(popup).despawn(),
            None => spawn_menu(anchor, assets, commands),
        },
        MenuAction::CloseAll => {
            if let Some(popup) = popup {
                commands.entity(popup).despawn();
            }
        }
        MenuAction::FocusRoot => {
            focus.set(anchor, FocusCause::Navigated);
        }
    }
}

fn spawn_menu(anchor: Entity, assets: Res<AssetServer>, mut commands: Commands) {
    let menu = commands
        .spawn((
            Node {
                display: Display::Flex,
                flex_direction: FlexDirection::Column,
                min_height: px(10.),
                min_width: percent(100),
                border: UiRect::all(px(1)),
                position_type: PositionType::Absolute,
                ..default()
            },
            MenuPopup::default(),
            BorderColor::all(GREEN),
            BackgroundColor(GRAY.into()),
            BoxShadow::new(
                Srgba::BLACK.with_alpha(0.9).into(),
                px(0),
                px(0),
                px(1),
                px(4),
            ),
            GlobalZIndex(100),
            Popover {
                positions: vec![
                    PopoverPlacement {
                        side: PopoverSide::Bottom,
                        align: PopoverAlign::Start,
                        gap: 2.0,
                    },
                    PopoverPlacement {
                        side: PopoverSide::Top,
                        align: PopoverAlign::Start,
                        gap: 2.0,
                    },
                ],
                window_margin: 10.0,
            },
            OverrideClip,
            children![
                menu_item(&assets),
                menu_item(&assets),
                menu_item(&assets),
                menu_item(&assets)
            ],
        ))
        .id();
    commands.entity(anchor).add_child(menu);
}

fn menu_item(asset_server: &AssetServer) -> impl Bundle {
    (
        Node {
            padding: UiRect::axes(px(8), px(2)),
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Start,
            ..default()
        },
        DemoMenuItem,
        MenuItem,
        Hovered::default(),
        TabIndex(0),
        BackgroundColor(NORMAL_BUTTON),
        children![(
            Text::new("Menu Item"),
            TextFont {
                font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
                font_size: FontSize::Px(33.0),
                ..default()
            },
            TextColor(Color::srgb(0.9, 0.9, 0.9)),
            TextShadow::default(),
        )],
    )
}

fn update_menu_item_style(
    mut buttons: Query<
        (
            Has<Pressed>,
            &Hovered,
            Has<InteractionDisabled>,
            &mut BackgroundColor,
        ),
        (
            Or<(
                Changed<Pressed>,
                Changed<Hovered>,
                Added<InteractionDisabled>,
            )>,
            With<DemoMenuItem>,
        ),
    >,
) {
    for (pressed, hovered, disabled, mut color) in &mut buttons {
        set_menu_item_style(disabled, hovered.get(), pressed, &mut color);
    }
}

/// Supplementary system to detect removed marker components
fn update_menu_item_style2(
    mut buttons: Query<
        (
            Has<Pressed>,
            &Hovered,
            Has<InteractionDisabled>,
            &mut BackgroundColor,
        ),
        With<DemoMenuItem>,
    >,
    mut removed_depressed: RemovedComponents<Pressed>,
    mut removed_disabled: RemovedComponents<InteractionDisabled>,
) {
    removed_depressed
        .read()
        .chain(removed_disabled.read())
        .for_each(|entity| {
            if let Ok((pressed, hovered, disabled, mut color)) = buttons.get_mut(entity) {
                set_menu_item_style(disabled, hovered.get(), pressed, &mut color);
            }
        });
}

fn set_menu_item_style(disabled: bool, hovered: bool, pressed: bool, color: &mut BackgroundColor) {
    match (disabled, hovered, pressed) {
        // Pressed and hovered menu item
        (false, true, true) => {
            *color = PRESSED_BUTTON.into();
        }

        // Hovered, unpressed menu item
        (false, true, false) => {
            *color = HOVERED_BUTTON.into();
        }

        // Unhovered menu item (either pressed or not).
        _ => {
            *color = NORMAL_BUTTON.into();
        }
    }
}

fn toggle_disabled(
    input: Res<ButtonInput<KeyCode>>,
    mut interaction_query: Query<
        (Entity, Has<InteractionDisabled>),
        Or<(
            With<Button>,
            With<MenuButton>,
            With<Slider>,
            With<Checkbox>,
            With<RadioButton>,
        )>,
    >,
    mut commands: Commands,
) {
    if input.just_pressed(KeyCode::KeyD) {
        for (entity, disabled) in &mut interaction_query {
            if disabled {
                info!("Widget enabled");
                commands.entity(entity).remove::<InteractionDisabled>();
            } else {
                info!("Widget disabled");
                commands.entity(entity).insert(InteractionDisabled);
            }
        }
    }
}
