use bevy::prelude::*;
use bevy_cobweb::prelude::*;
use crate::prelude::*;
#[derive(Component, Default)]
struct RadioButtonManager
{
selected: Option<Entity>,
}
impl RadioButtonManager
{
fn swap_selected(&mut self, c: &mut Commands, next: Entity)
{
if let Some(prev) = self.selected {
c.react().entity_event(prev, Deselect);
}
self.selected = Some(next);
}
fn try_clear(&mut self, entity: Entity) -> bool
{
let Some(prev) = &self.selected else { return false };
if *prev != entity {
return false;
}
self.selected = None;
true
}
}
#[derive(Component)]
struct RadioButtonHandlers
{
press_token: RevokeToken,
select_token: RevokeToken,
}
impl RadioButtonHandlers
{
fn revoke(self, rc: &mut ReactCommands)
{
rc.revoke(self.press_token);
rc.revoke(self.select_token);
}
}
#[derive(Reflect, Default, PartialEq, Copy, Clone)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct RadioGroup;
impl Instruction for RadioGroup
{
fn apply(self, entity: Entity, world: &mut World)
{
let Ok(mut emut) = world.get_entity_mut(entity) else { return };
emut.insert_if_new(RadioButtonManager::default());
}
fn revert(entity: Entity, world: &mut World)
{
let Ok(mut emut) = world.get_entity_mut(entity) else { return };
emut.remove::<RadioButtonManager>();
}
}
#[derive(Reflect, Default, PartialEq, Copy, Clone)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct RadioButton;
impl Instruction for RadioButton
{
fn apply(self, entity: Entity, world: &mut World)
{
let Ok(e) = world.get_entity(entity) else { return };
if e.contains::<RadioButtonHandlers>() {
return;
}
let press_token = world.react(|rc| {
rc.on_revokable(
entity_event::<PointerPressed>(entity),
move |mut c: Commands, states: PseudoStateParam| {
states.try_select(&mut c, entity);
},
)
});
let select_token = world.react(|rc| rc.on_revokable(
entity_event::<Select>(entity),
move |mut c: Commands, mut managers: Query<&mut RadioButtonManager>, parents: Query<&ChildOf>| {
let mut search_entity = entity;
loop {
if let Ok(mut manager) = managers.get_mut(search_entity) {
manager.swap_selected(&mut c, entity);
break;
}
let Ok(child_of) = parents.get(search_entity) else {
tracing::warn!("failed selecting radio button {entity:?}; no RadioGroup found in ancestors");
break;
};
search_entity = child_of.parent();
}
}
));
world
.entity_mut(entity)
.insert(RadioButtonHandlers { press_token, select_token });
Interactive.apply(entity, world);
}
fn revert(entity: Entity, world: &mut World)
{
let mut search_entity = entity;
loop {
if let Some(mut manager) = world.get_mut::<RadioButtonManager>(search_entity) {
if manager.try_clear(entity) {
world.react(|rc| rc.entity_event(entity, Deselect));
}
break;
}
let Some(child_of) = world.get::<ChildOf>(search_entity) else { break };
search_entity = child_of.parent();
}
let Ok(mut emut) = world.get_entity_mut(entity) else { return };
let Some(handlers) = emut.take::<RadioButtonHandlers>() else { return };
world.react(|rc| handlers.revoke(rc));
Interactive::revert(entity, world);
}
}
pub(crate) struct CobwebRadioButtonPlugin;
impl Plugin for CobwebRadioButtonPlugin
{
fn build(&self, app: &mut App)
{
app.register_instruction_type::<RadioGroup>()
.register_instruction_type::<RadioButton>();
}
}