Repository: bevyengine/bevy
File: crates/bevy_ui_widgets/src/radio.rs (Rust)

Code before:
    }

    let event = &ev.event().input;
    if event.state == ButtonState::Pressed
        && !event.repeat
        && (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
    {
        ev.propagate(false);

        trigger_radio_button_and_radio_group_value_change(
            ev.focused_entity,
            &q_group,
            &q_parents,
            &mut commands,
        );
    }
}

fn radio_button_on_click(
    mut ev: On<Pointer<Click>>,
    q_group: Query<(), With<RadioGroup>>,
    q_radio: Query<Has<Checked>, (With<RadioButton>, Without<InteractionDisabled>)>,
    q_parents: Query<&ChildOf>,
    mut commands: Commands,
) {
    let Ok(checked) = q_radio.get(ev.entity) else {
        // Not a radio button
        return;
    };

    ev.propagate(false);

    // Radio button is already checked
    if checked {
        return;
    }

    trigger_radio_button_and_radio_group_value_change(
        ev.entity,
        &q_group,
        &q_parents,
        &mut commands,
    );
}

fn trigger_radio_button_and_radio_group_value_change(
    radio_button: Entity,
    q_group: &Query<(), With<RadioGroup>>,
    q_parents: &Query<&ChildOf>,
    commands: &mut Commands,
) {

Review comment (bug):
Bailing out here means that a disabled radio button doesn't consume the click event, which will propagate to the ancestors. In other words, it will cause the click to trigger on whatever container entity holds the radio button, which I think is probably incorrect.

I think that disabled widgets shouldn't behave as if the widget was absent; rather, they should behave as if the widget was read-only. This means that they still receive click events, they just don't respond to them.

This is why I used `Has<InteractionDisabled>` rather than `Without<InteractionDisabled>` - so that I could detect clicks on disabled buttons and stop propagation on them.

Code after:
        && (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
    {
        ev.propagate(false);

        // Radio button is disabled or already checked
        if disabled || checked {
            return;
        }

        trigger_radio_button_and_radio_group_value_change(
            ev.focused_entity,
            &q_group,
            &q_parents,
            &mut commands,
        );
    }
}

fn radio_button_on_click(
    mut ev: On<Pointer<Click>>,
    q_group: Query<(), With<RadioGroup>>,
    q_radio: Query<(Has<InteractionDisabled>, Has<Checked>), With<RadioButton>>,
    q_parents: Query<&ChildOf>,
    mut commands: Commands,
) {
    let Ok((disabled, checked)) = q_radio.get(ev.entity) else {
        // Not a radio button
        return;
    };

    ev.propagate(false);

    // Radio button is disabled or already checked
    if disabled || checked {
        return;
    }

    trigger_radio_button_and_radio_group_value_change(
        ev.entity,
        &q_group,
        &q_parents,
        &mut commands,
    );
}

fn trigger_radio_button_and_radio_group_value_change(
    radio_button: Entity,
    q_group: &Query<(), With<RadioGroup>>,
    q_parents: &Query<&ChildOf>,
    commands: &mut Commands,
) {