Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 64 additions & 13 deletions crates/bevy_feathers/src/controls/radio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ use bevy_scene::prelude::*;
use bevy_text::FontWeight;
use bevy_ui::{
AlignItems, BorderRadius, Checked, Display, FlexDirection, InteractionDisabled, JustifyContent,
Node, UiRect, Val,
Node, Pressed, UiRect, Val,
};
use bevy_ui_widgets::RadioButton;
use bevy_ui_widgets::{ActivateOnPress, RadioButton};

use crate::{
constants::{fonts, size},
Expand Down Expand Up @@ -184,20 +184,29 @@ fn update_radio_styles(
Entity,
Has<InteractionDisabled>,
Has<Checked>,
Has<Pressed>,
Has<ActivateOnPress>,
&Hovered,
&ThemeFontColor,
),
(
With<RadioButton>,
Or<(Changed<Hovered>, Added<Checked>, Added<InteractionDisabled>)>,
Or<(
Changed<Hovered>,
Added<Checked>,
Added<Pressed>,
Added<InteractionDisabled>,
)>,
),
>,
q_children: Query<&Children>,
mut q_outline: Query<&ThemeBorderColor, With<RadioOutline>>,
mut q_mark: Query<&ThemeBackgroundColor, With<RadioMark>>,
mut commands: Commands,
) {
for (radio_ent, disabled, checked, hovered, font_color) in q_radioes.iter() {
for (radio_ent, disabled, checked, pressed, activate_on_press, hovered, font_color) in
q_radioes.iter()
{
let Some(outline_ent) = q_children
.iter_descendants(radio_ent)
.find(|en| q_outline.contains(*en))
Expand All @@ -218,7 +227,9 @@ fn update_radio_styles(
mark_ent,
disabled,
checked,
pressed,
hovered.0,
activate_on_press,
outline_border,
mark_color,
font_color,
Expand All @@ -233,6 +244,8 @@ fn update_radio_styles_remove(
Entity,
Has<InteractionDisabled>,
Has<Checked>,
Has<Pressed>,
Has<ActivateOnPress>,
&Hovered,
&ThemeFontColor,
),
Expand All @@ -243,13 +256,26 @@ fn update_radio_styles_remove(
mut q_mark: Query<&ThemeBackgroundColor, With<RadioMark>>,
mut removed_disabled: RemovedComponents<InteractionDisabled>,
mut removed_checked: RemovedComponents<Checked>,
mut remove_pressed: RemovedComponents<Pressed>,
mut remove_activate_on_press: RemovedComponents<ActivateOnPress>,
mut commands: Commands,
) {
removed_disabled
.read()
.chain(removed_checked.read())
.chain(remove_pressed.read())
.chain(remove_activate_on_press.read())
.for_each(|ent| {
if let Ok((radio_ent, disabled, checked, hovered, font_color)) = q_radioes.get(ent) {
if let Ok((
radio_ent,
disabled,
checked,
pressed,
activate_on_press,
hovered,
font_color,
)) = q_radioes.get(ent)
{
let Some(outline_ent) = q_children
.iter_descendants(radio_ent)
.find(|en| q_outline.contains(*en))
Expand All @@ -270,7 +296,9 @@ fn update_radio_styles_remove(
mark_ent,
disabled,
checked,
pressed,
hovered.0,
activate_on_press,
outline_border,
mark_color,
font_color,
Expand All @@ -286,21 +314,44 @@ fn set_radio_styles(
mark_ent: Entity,
disabled: bool,
checked: bool,
pressed: bool,
hovered: bool,
activate_on_press: bool,
outline_border: &ThemeBorderColor,
mark_color: &ThemeBackgroundColor,
font_color: &ThemeFontColor,
commands: &mut Commands,
) {
let outline_border_token = match (disabled, hovered) {
(true, _) => tokens::RADIO_BORDER_DISABLED,
(false, true) => tokens::RADIO_BORDER_HOVER,
_ => tokens::RADIO_BORDER,
let outline_border_token = if checked {
if disabled {
tokens::RADIO_BORDER_CHECKED_DISABLED
} else if pressed && !activate_on_press {
tokens::RADIO_BORDER_CHECKED_PRESSED
} else if hovered {
tokens::RADIO_BORDER_CHECKED_HOVER
} else {
tokens::RADIO_BORDER_CHECKED
}
} else {
if disabled {
tokens::RADIO_BORDER_DISABLED
} else if pressed && !activate_on_press {
tokens::RADIO_BORDER_PRESSED
} else if hovered {
tokens::RADIO_BORDER_HOVER
} else {
tokens::RADIO_BORDER
}
};

let mark_token = match disabled {
true => tokens::RADIO_MARK_DISABLED,
false => tokens::RADIO_MARK,
let mark_token = if disabled {
tokens::RADIO_MARK_DISABLED
} else if pressed && !activate_on_press {
tokens::RADIO_MARK_PRESSED
} else if hovered {
tokens::RADIO_MARK_HOVER
} else {
tokens::RADIO_MARK
};

let font_color_token = match disabled {
Expand All @@ -324,7 +375,7 @@ fn set_radio_styles(
if mark_color.0 != mark_token {
commands
.entity(mark_ent)
.insert(ThemeBorderColor(mark_token));
.insert(ThemeBackgroundColor(mark_token));
}

// Change mark visibility
Expand Down
20 changes: 18 additions & 2 deletions crates/bevy_feathers/src/dark_theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,29 @@ pub fn create_dark_theme() -> ThemeProps {
),
// Radio
(tokens::RADIO_BORDER, palette::GRAY_3),
(tokens::RADIO_BORDER_HOVER, palette::GRAY_3.lighter(0.1)),
(tokens::RADIO_BORDER_HOVER, palette::GRAY_3.lighter(0.05)),
(tokens::RADIO_BORDER_PRESSED, palette::GRAY_3.lighter(0.1)),
(
tokens::RADIO_BORDER_DISABLED,
palette::GRAY_3.with_alpha(0.5),
),
(tokens::RADIO_BORDER_CHECKED, palette::ACCENT),
(
tokens::RADIO_BORDER_CHECKED_HOVER,
palette::ACCENT.lighter(0.05),
),
(
tokens::RADIO_BORDER_CHECKED_PRESSED,
palette::ACCENT.lighter(0.1),
),
(
tokens::RADIO_BORDER_CHECKED_DISABLED,
palette::GRAY_3.with_alpha(0.5),
),
(tokens::RADIO_MARK, palette::ACCENT),
(tokens::RADIO_MARK_DISABLED, palette::ACCENT.with_alpha(0.5)),
(tokens::RADIO_MARK_HOVER, palette::ACCENT.lighter(0.05)),
(tokens::RADIO_MARK_PRESSED, palette::ACCENT.lighter(0.1)),
(tokens::RADIO_MARK_DISABLED, palette::GRAY_3.with_alpha(0.5)),
(tokens::RADIO_TEXT, palette::LIGHT_GRAY_1),
(
tokens::RADIO_TEXT_DISABLED,
Expand Down
25 changes: 22 additions & 3 deletions crates/bevy_feathers/src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,34 @@ pub const CHECKBOX_TEXT_DISABLED: ThemeToken =

// Radio button

/// Radio border around the checkmark
/// Border around the radio button
pub const RADIO_BORDER: ThemeToken = ThemeToken::new_static("feathers.radio.border");
/// Radio border around the checkmark (hovered)
/// Border around the radio button (hovered)
pub const RADIO_BORDER_HOVER: ThemeToken = ThemeToken::new_static("feathers.radio.border.hover");
/// Radio border around the checkmark (disabled)
/// Border around the radio button (pressed)
pub const RADIO_BORDER_PRESSED: ThemeToken =
ThemeToken::new_static("feathers.radio.border.pressed");
/// Border around the radio button (disabled)
pub const RADIO_BORDER_DISABLED: ThemeToken =
ThemeToken::new_static("feathers.radio.border.disabled");
/// Border around the radio button (checked)
pub const RADIO_BORDER_CHECKED: ThemeToken =
ThemeToken::new_static("feathers.radio.border.checked");
/// Border around the radio button (checked+hovered)
pub const RADIO_BORDER_CHECKED_HOVER: ThemeToken =
ThemeToken::new_static("feathers.radio.border.checked.hover");
/// Border around the radio button (checked+pressed)
pub const RADIO_BORDER_CHECKED_PRESSED: ThemeToken =
ThemeToken::new_static("feathers.radio.border.checked.pressed");
/// Border around the radio button (checked+disabled)
pub const RADIO_BORDER_CHECKED_DISABLED: ThemeToken =
ThemeToken::new_static("feathers.radio.border.checked.disabled");
/// Radio check mark
pub const RADIO_MARK: ThemeToken = ThemeToken::new_static("feathers.radio.mark");
/// Radio check mark (hovered)
pub const RADIO_MARK_HOVER: ThemeToken = ThemeToken::new_static("feathers.radio.mark.hover");
/// Radio check mark (pressed)
pub const RADIO_MARK_PRESSED: ThemeToken = ThemeToken::new_static("feathers.radio.mark.pressed");
/// Radio check mark (disabled)
pub const RADIO_MARK_DISABLED: ThemeToken = ThemeToken::new_static("feathers.radio.mark.disabled");
/// Radio label text
Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_ui_widgets/src/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
use bevy_ui::{Checkable, Checked, InteractionDisabled, Pressed};

use crate::ActivateOnPress;
use crate::ValueChange;
use crate::{ActivateOnPress, ValueChange};
use bevy_ecs::entity::Entity;

/// Headless widget implementation for checkboxes. The [`Checked`] component represents the current
Expand Down
100 changes: 90 additions & 10 deletions crates/bevy_ui_widgets/src/radio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ use bevy_ecs::{
entity::Entity,
hierarchy::{ChildOf, Children},
observer::On,
query::{Has, With},
query::{Has, With, Without},
reflect::ReflectComponent,
system::{Commands, Query},
};
use bevy_input::keyboard::{KeyCode, KeyboardInput};
use bevy_input::ButtonState;
use bevy_input_focus::FocusedInput;
use bevy_picking::events::{Click, Pointer};
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
use bevy_reflect::Reflect;
use bevy_ui::{Checkable, Checked, InteractionDisabled};
use bevy_ui::{Checkable, Checked, InteractionDisabled, Pressed};

use crate::ValueChange;
use crate::{ActivateOnPress, ValueChange};

/// Headless widget implementation for a "radio button group". This component is used to group
/// multiple [`RadioButton`] components together, allowing them to behave as a single unit. It
Expand Down Expand Up @@ -188,32 +188,108 @@ fn radio_button_on_key_input(
}

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

ev.propagate(false);
click.propagate(false);

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

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

fn radio_button_on_pointer_down(
mut press: On<Pointer<Press>>,
q_group: Query<(), With<RadioGroup>>,
mut q_radio: Query<
(
Entity,
Has<InteractionDisabled>,
Has<Checked>,
Has<Pressed>,
Has<ActivateOnPress>,
),
With<RadioButton>,
>,
q_parents: Query<&ChildOf>,
mut commands: Commands,
) {
if let Ok((radio, disabled, checked, pressed, activate_on_press)) =
q_radio.get_mut(press.entity)
{
press.propagate(false);
if !disabled && !pressed {
commands.entity(radio).insert(Pressed);
if activate_on_press && !checked {
trigger_radio_button_and_radio_group_value_change(
press.entity,
&q_group,
&q_parents,
&mut commands,
);
}
}
}
}

fn radio_button_on_pointer_up(
mut release: On<Pointer<Release>>,
mut q_radio: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<RadioButton>>,
mut commands: Commands,
) {
if let Ok((radio, disabled, pressed)) = q_radio.get_mut(release.entity) {
release.propagate(false);
if !disabled && pressed {
commands.entity(radio).remove::<Pressed>();
}
}
}

fn radio_button_on_pointer_drag_end(
mut drag_end: On<Pointer<DragEnd>>,
mut q_radio: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<RadioButton>>,
mut commands: Commands,
) {
if let Ok((radio, disabled, pressed)) = q_radio.get_mut(drag_end.entity) {
drag_end.propagate(false);
if !disabled && pressed {
commands.entity(radio).remove::<Pressed>();
}
}
}

fn checkbox_on_pointer_cancel(
mut cancel: On<Pointer<Cancel>>,
mut q_radio: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<RadioButton>>,
mut commands: Commands,
) {
if let Ok((radio, disabled, pressed)) = q_radio.get_mut(cancel.entity) {
cancel.propagate(false);
if !disabled && pressed {
commands.entity(radio).remove::<Pressed>();
}
}
}

fn trigger_radio_button_and_radio_group_value_change(
radio_button: Entity,
q_group: &Query<(), With<RadioGroup>>,
Expand Down Expand Up @@ -247,6 +323,10 @@ impl Plugin for RadioGroupPlugin {
fn build(&self, app: &mut App) {
app.add_observer(radio_group_on_key_input)
.add_observer(radio_button_on_click)
.add_observer(radio_button_on_key_input);
.add_observer(radio_button_on_key_input)
.add_observer(radio_button_on_pointer_down)
.add_observer(radio_button_on_pointer_up)
.add_observer(radio_button_on_pointer_drag_end)
.add_observer(checkbox_on_pointer_cancel);
}
}
Loading