Notifications now include the source entity. This is useful for callbacks that are responsible for more than one widget. Part of #19236 This is an incremental change only: I have not altered the fundamental nature of callbacks, as this is still in discussion. The only change here is to include the source entity id with the notification. The existing examples don't leverage this new field, but that will change when I work on the color sliders PR. I have been careful not to use the word "events" in describing the notification message structs because they are not capital-E `Events` at this time. That may change depending on the outcome of discussions. @alice-i-cecile
310 lines
9.5 KiB
Rust
310 lines
9.5 KiB
Rust
use bevy_app::{Plugin, PreUpdate};
|
|
use bevy_core_widgets::{Callback, CoreCheckbox, ValueChange};
|
|
use bevy_ecs::{
|
|
bundle::Bundle,
|
|
children,
|
|
component::Component,
|
|
entity::Entity,
|
|
hierarchy::{ChildOf, Children},
|
|
lifecycle::RemovedComponents,
|
|
query::{Added, Changed, Has, Or, With},
|
|
schedule::IntoScheduleConfigs,
|
|
spawn::{Spawn, SpawnRelated, SpawnableList},
|
|
system::{Commands, In, Query},
|
|
};
|
|
use bevy_input_focus::tab_navigation::TabIndex;
|
|
use bevy_math::Rot2;
|
|
use bevy_picking::{hover::Hovered, PickingSystems};
|
|
use bevy_render::view::Visibility;
|
|
use bevy_ui::{
|
|
AlignItems, BorderRadius, Checked, Display, FlexDirection, InteractionDisabled, JustifyContent,
|
|
Node, PositionType, UiRect, UiTransform, Val,
|
|
};
|
|
use bevy_winit::cursor::CursorIcon;
|
|
|
|
use crate::{
|
|
constants::{fonts, size},
|
|
font_styles::InheritableFont,
|
|
handle_or_path::HandleOrPath,
|
|
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
|
|
tokens,
|
|
};
|
|
|
|
/// Parameters for the checkbox template, passed to [`checkbox`] function.
|
|
#[derive(Default)]
|
|
pub struct CheckboxProps {
|
|
/// Change handler
|
|
pub on_change: Callback<In<ValueChange<bool>>>,
|
|
}
|
|
|
|
/// Marker for the checkbox frame (contains both checkbox and label)
|
|
#[derive(Component, Default, Clone)]
|
|
struct CheckboxFrame;
|
|
|
|
/// Marker for the checkbox outline
|
|
#[derive(Component, Default, Clone)]
|
|
struct CheckboxOutline;
|
|
|
|
/// Marker for the checkbox check mark
|
|
#[derive(Component, Default, Clone)]
|
|
struct CheckboxMark;
|
|
|
|
/// Template function to spawn a checkbox.
|
|
///
|
|
/// # Arguments
|
|
/// * `props` - construction properties for the checkbox.
|
|
/// * `overrides` - a bundle of components that are merged in with the normal checkbox components.
|
|
/// * `label` - the label of the checkbox.
|
|
pub fn checkbox<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundle>(
|
|
props: CheckboxProps,
|
|
overrides: B,
|
|
label: C,
|
|
) -> impl Bundle {
|
|
(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Row,
|
|
justify_content: JustifyContent::Start,
|
|
align_items: AlignItems::Center,
|
|
column_gap: Val::Px(4.0),
|
|
..Default::default()
|
|
},
|
|
CoreCheckbox {
|
|
on_change: props.on_change,
|
|
},
|
|
CheckboxFrame,
|
|
Hovered::default(),
|
|
CursorIcon::System(bevy_window::SystemCursorIcon::Pointer),
|
|
TabIndex(0),
|
|
ThemeFontColor(tokens::CHECKBOX_TEXT),
|
|
InheritableFont {
|
|
font: HandleOrPath::Path(fonts::REGULAR.to_owned()),
|
|
font_size: 14.0,
|
|
},
|
|
overrides,
|
|
Children::spawn((
|
|
Spawn((
|
|
Node {
|
|
width: size::CHECKBOX_SIZE,
|
|
height: size::CHECKBOX_SIZE,
|
|
border: UiRect::all(Val::Px(2.0)),
|
|
..Default::default()
|
|
},
|
|
CheckboxOutline,
|
|
BorderRadius::all(Val::Px(4.0)),
|
|
ThemeBackgroundColor(tokens::CHECKBOX_BG),
|
|
ThemeBorderColor(tokens::CHECKBOX_BORDER),
|
|
children![(
|
|
// Cheesy checkmark: rotated node with L-shaped border.
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
left: Val::Px(4.0),
|
|
top: Val::Px(0.0),
|
|
width: Val::Px(6.),
|
|
height: Val::Px(11.),
|
|
border: UiRect {
|
|
bottom: Val::Px(2.0),
|
|
right: Val::Px(2.0),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
},
|
|
UiTransform::from_rotation(Rot2::FRAC_PI_4),
|
|
CheckboxMark,
|
|
ThemeBorderColor(tokens::CHECKBOX_MARK),
|
|
)],
|
|
)),
|
|
label,
|
|
)),
|
|
)
|
|
}
|
|
|
|
fn update_checkbox_styles(
|
|
q_checkboxes: Query<
|
|
(
|
|
Entity,
|
|
Has<InteractionDisabled>,
|
|
Has<Checked>,
|
|
&Hovered,
|
|
&ThemeFontColor,
|
|
),
|
|
(
|
|
With<CheckboxFrame>,
|
|
Or<(Changed<Hovered>, Added<Checked>, Added<InteractionDisabled>)>,
|
|
),
|
|
>,
|
|
q_children: Query<&Children>,
|
|
mut q_outline: Query<(&ThemeBackgroundColor, &ThemeBorderColor), With<CheckboxOutline>>,
|
|
mut q_mark: Query<&ThemeBorderColor, With<CheckboxMark>>,
|
|
mut commands: Commands,
|
|
) {
|
|
for (checkbox_ent, disabled, checked, hovered, font_color) in q_checkboxes.iter() {
|
|
let Some(outline_ent) = q_children
|
|
.iter_descendants(checkbox_ent)
|
|
.find(|en| q_outline.contains(*en))
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(mark_ent) = q_children
|
|
.iter_descendants(checkbox_ent)
|
|
.find(|en| q_mark.contains(*en))
|
|
else {
|
|
continue;
|
|
};
|
|
let (outline_bg, outline_border) = q_outline.get_mut(outline_ent).unwrap();
|
|
let mark_color = q_mark.get_mut(mark_ent).unwrap();
|
|
set_checkbox_colors(
|
|
checkbox_ent,
|
|
outline_ent,
|
|
mark_ent,
|
|
disabled,
|
|
checked,
|
|
hovered.0,
|
|
outline_bg,
|
|
outline_border,
|
|
mark_color,
|
|
font_color,
|
|
&mut commands,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn update_checkbox_styles_remove(
|
|
q_checkboxes: Query<
|
|
(
|
|
Entity,
|
|
Has<InteractionDisabled>,
|
|
Has<Checked>,
|
|
&Hovered,
|
|
&ThemeFontColor,
|
|
),
|
|
With<CheckboxFrame>,
|
|
>,
|
|
q_children: Query<&Children>,
|
|
mut q_outline: Query<(&ThemeBackgroundColor, &ThemeBorderColor), With<CheckboxOutline>>,
|
|
mut q_mark: Query<&ThemeBorderColor, With<CheckboxMark>>,
|
|
mut removed_disabled: RemovedComponents<InteractionDisabled>,
|
|
mut removed_checked: RemovedComponents<Checked>,
|
|
mut commands: Commands,
|
|
) {
|
|
removed_disabled
|
|
.read()
|
|
.chain(removed_checked.read())
|
|
.for_each(|ent| {
|
|
if let Ok((checkbox_ent, disabled, checked, hovered, font_color)) =
|
|
q_checkboxes.get(ent)
|
|
{
|
|
let Some(outline_ent) = q_children
|
|
.iter_descendants(checkbox_ent)
|
|
.find(|en| q_outline.contains(*en))
|
|
else {
|
|
return;
|
|
};
|
|
let Some(mark_ent) = q_children
|
|
.iter_descendants(checkbox_ent)
|
|
.find(|en| q_mark.contains(*en))
|
|
else {
|
|
return;
|
|
};
|
|
let (outline_bg, outline_border) = q_outline.get_mut(outline_ent).unwrap();
|
|
let mark_color = q_mark.get_mut(mark_ent).unwrap();
|
|
set_checkbox_colors(
|
|
checkbox_ent,
|
|
outline_ent,
|
|
mark_ent,
|
|
disabled,
|
|
checked,
|
|
hovered.0,
|
|
outline_bg,
|
|
outline_border,
|
|
mark_color,
|
|
font_color,
|
|
&mut commands,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
fn set_checkbox_colors(
|
|
checkbox_ent: Entity,
|
|
outline_ent: Entity,
|
|
mark_ent: Entity,
|
|
disabled: bool,
|
|
checked: bool,
|
|
hovered: bool,
|
|
outline_bg: &ThemeBackgroundColor,
|
|
outline_border: &ThemeBorderColor,
|
|
mark_color: &ThemeBorderColor,
|
|
font_color: &ThemeFontColor,
|
|
commands: &mut Commands,
|
|
) {
|
|
let outline_border_token = match (disabled, hovered) {
|
|
(true, _) => tokens::CHECKBOX_BORDER_DISABLED,
|
|
(false, true) => tokens::CHECKBOX_BORDER_HOVER,
|
|
_ => tokens::CHECKBOX_BORDER,
|
|
};
|
|
|
|
let outline_bg_token = match (disabled, checked) {
|
|
(true, true) => tokens::CHECKBOX_BG_CHECKED_DISABLED,
|
|
(true, false) => tokens::CHECKBOX_BG_DISABLED,
|
|
(false, true) => tokens::CHECKBOX_BG_CHECKED,
|
|
(false, false) => tokens::CHECKBOX_BG,
|
|
};
|
|
|
|
let mark_token = match disabled {
|
|
true => tokens::CHECKBOX_MARK_DISABLED,
|
|
false => tokens::CHECKBOX_MARK,
|
|
};
|
|
|
|
let font_color_token = match disabled {
|
|
true => tokens::CHECKBOX_TEXT_DISABLED,
|
|
false => tokens::CHECKBOX_TEXT,
|
|
};
|
|
|
|
// Change outline background
|
|
if outline_bg.0 != outline_bg_token {
|
|
commands
|
|
.entity(outline_ent)
|
|
.insert(ThemeBackgroundColor(outline_bg_token));
|
|
}
|
|
|
|
// Change outline border
|
|
if outline_border.0 != outline_border_token {
|
|
commands
|
|
.entity(outline_ent)
|
|
.insert(ThemeBorderColor(outline_border_token));
|
|
}
|
|
|
|
// Change mark color
|
|
if mark_color.0 != mark_token {
|
|
commands
|
|
.entity(mark_ent)
|
|
.insert(ThemeBorderColor(mark_token));
|
|
}
|
|
|
|
// Change mark visibility
|
|
commands.entity(mark_ent).insert(match checked {
|
|
true => Visibility::Visible,
|
|
false => Visibility::Hidden,
|
|
});
|
|
|
|
// Change font color
|
|
if font_color.0 != font_color_token {
|
|
commands
|
|
.entity(checkbox_ent)
|
|
.insert(ThemeFontColor(font_color_token));
|
|
}
|
|
}
|
|
|
|
/// Plugin which registers the systems for updating the checkbox styles.
|
|
pub struct CheckboxPlugin;
|
|
|
|
impl Plugin for CheckboxPlugin {
|
|
fn build(&self, app: &mut bevy_app::App) {
|
|
app.add_systems(
|
|
PreUpdate,
|
|
(update_checkbox_styles, update_checkbox_styles_remove).in_set(PickingSystems::Last),
|
|
);
|
|
}
|
|
}
|