Path: blob/main/crates/bevy_feathers/src/controls/disclosure_toggle.rs
30636 views
use bevy_app::{App, Plugin, PreUpdate};1use bevy_ecs::{2hierarchy::Children,3lifecycle::RemovedComponents,4query::{Added, Has, Or, With},5schedule::IntoScheduleConfigs,6system::{Query, Res},7};8use bevy_input_focus::tab_navigation::TabIndex;9use bevy_math::Rot2;10use bevy_picking::PickingSystems;11use bevy_scene::{bsn, Scene, SceneComponent};12use bevy_ui::{13px, widget::ImageNode, AlignItems, Checked, Display, InteractionDisabled, JustifyContent, Node,14UiTransform,15};16use bevy_ui_widgets::Checkbox;17use bevy_window::SystemCursorIcon;1819use crate::{20constants::icons, cursor::EntityCursor, display::icon, focus::FocusIndicator, theme::UiTheme,21tokens,22};2324/// A toggle button which shows a chevron that points either right or down, used to expand or25/// collapse a panel. Functionally, this is equivalent to a checkbox, and has a [`Checked`]26/// state.27///28/// This is spawnable by inheriting it as a "scene component".29#[derive(SceneComponent, Default, Clone)]30pub struct FeathersDisclosureToggle;3132impl FeathersDisclosureToggle {33fn scene() -> impl Scene {34bsn!(35Node {36width: px(12),37height: px(12),38display: Display::Flex,39align_items: AlignItems::Center,40justify_content: JustifyContent::Center,41}42Checkbox43EntityCursor::System(SystemCursorIcon::Pointer)44FocusIndicator45TabIndex(0)46Children [47icon(icons::CHEVRON_RIGHT)48]49)50}51}5253fn update_toggle_styles(54mut q_toggle: Query<55(56Has<InteractionDisabled>,57Has<Checked>,58&mut UiTransform,59&Children,60),61(62With<FeathersDisclosureToggle>,63Or<(Added<Checkbox>, Added<Checked>, Added<InteractionDisabled>)>,64),65>,66mut q_icon: Query<&mut ImageNode>,67theme: Res<UiTheme>,68) {69for (disabled, checked, mut transform, children) in q_toggle.iter_mut() {70let Some(child_id) = children.first() else {71continue;72};73let Ok(mut icon_child) = q_icon.get_mut(*child_id) else {74continue;75};76set_toggle_styles(77disabled,78checked,79transform.as_mut(),80&mut icon_child,81&theme,82);83}84}8586fn update_toggle_styles_remove(87mut q_toggle: Query<88(89Has<InteractionDisabled>,90Has<Checked>,91&mut UiTransform,92&Children,93),94With<FeathersDisclosureToggle>,95>,96mut q_icon: Query<&mut ImageNode>,97mut removed_disabled: RemovedComponents<InteractionDisabled>,98mut removed_checked: RemovedComponents<Checked>,99theme: Res<UiTheme>,100) {101removed_disabled102.read()103.chain(removed_checked.read())104.for_each(|ent| {105if let Ok((disabled, checked, mut transform, children)) = q_toggle.get_mut(ent) {106let Some(child_id) = children.first() else {107return;108};109let Ok(mut icon_child) = q_icon.get_mut(*child_id) else {110return;111};112set_toggle_styles(113disabled,114checked,115transform.as_mut(),116&mut icon_child,117&theme,118);119}120});121}122123fn set_toggle_styles(124disabled: bool,125checked: bool,126transform: &mut UiTransform,127image_node: &mut ImageNode,128theme: &Res<'_, UiTheme>,129) {130// It's effectively the same color as the caption of a "plain" variant tool button with an icon.131let icon_color = match disabled {132true => theme.color(&tokens::BUTTON_TEXT_DISABLED),133false => theme.color(&tokens::BUTTON_TEXT),134};135136// Change icon color137if image_node.color != icon_color {138image_node.color = icon_color;139}140141match checked {142true => {143transform.rotation = Rot2::turn_fraction(0.25);144}145false => {146transform.rotation = Rot2::turn_fraction(0.0);147}148};149}150151/// Plugin which registers the systems for updating the toggle switch styles.152pub struct DisclosureTogglePlugin;153154impl Plugin for DisclosureTogglePlugin {155fn build(&self, app: &mut App) {156app.add_systems(157PreUpdate,158(update_toggle_styles, update_toggle_styles_remove).in_set(PickingSystems::Last),159);160}161}162163164