use bevy::ecs::lifecycle::HookContext;
use bevy::ecs::world::DeferredWorld;
use bevy::input_focus::AutoFocus;
use bevy::input_focus::tab_navigation::TabGroup;
use bevy::prelude::*;
use bevy::ui_widgets::Activate;
use crate::cancel::Cancel;
use crate::nav::Dialog;
use crate::skin::Role;
use crate::widgets::{button, heading, label, menu, panel};
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component, Debug, Clone)]
#[component(on_add = build)]
#[require(
Node = scrim(),
Role = Role::Scrim,
Dialog,
GlobalZIndex = GlobalZIndex(100),
TabGroup = TabGroup { order: 0, modal: true },
)]
pub struct Confirm {
title: String,
body: String,
choices: Vec<String>,
safe: usize,
}
fn scrim() -> Node {
Node {
position_type: PositionType::Absolute,
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
}
}
impl Confirm {
pub fn new<I, S>(title: impl Into<String>, body: impl Into<String>, choices: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let choices: Vec<String> = choices.into_iter().map(Into::into).collect();
Self {
title: title.into(),
body: body.into(),
safe: choices.len().saturating_sub(1),
choices,
}
}
pub fn safe(mut self, index: usize) -> Self {
self.safe = index.min(self.choices.len().saturating_sub(1));
self
}
}
pub fn confirm<I, S>(title: impl Into<String>, body: impl Into<String>, choices: I) -> Confirm
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Confirm::new(title, body, choices)
}
#[derive(EntityEvent, Clone, Debug, PartialEq, Eq, Reflect)]
#[reflect(Event, Clone, Debug)]
pub struct ConfirmChosen {
#[event_target]
pub entity: Entity,
pub index: usize,
pub cancelled: bool,
}
#[derive(Component, Clone, Copy, Debug, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, PartialEq, Clone)]
pub struct ConfirmButton {
pub dialog: Entity,
pub index: usize,
}
fn build(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
world.commands().queue(move |world: &mut World| {
let Some(confirm) = world.get::<Confirm>(entity).cloned() else {
return;
};
let card = world.spawn((panel(), ChildOf(entity))).id();
world.spawn((heading(confirm.title), ChildOf(card)));
if !confirm.body.is_empty() {
world.spawn((label(confirm.body), ChildOf(card)));
}
let row = world.spawn((menu(), ChildOf(card))).id();
for (index, text) in confirm.choices.into_iter().enumerate() {
let choice = world
.spawn((
button(text),
ConfirmButton {
dialog: entity,
index,
},
ChildOf(row),
))
.id();
if index == confirm.safe {
world.entity_mut(choice).insert(AutoFocus);
}
}
});
}
pub(crate) fn confirm_on_activate(
activate: On<Activate>,
buttons: Query<&ConfirmButton>,
dialogs: Query<(), With<Confirm>>,
mut commands: Commands,
) {
let Ok(button) = buttons.get(activate.entity) else {
return;
};
if dialogs.get(button.dialog).is_err() {
return;
}
answer(&mut commands, button.dialog, button.index, false);
}
pub(crate) fn confirm_on_cancel(
mut cancel: On<Cancel>,
dialogs: Query<&Confirm>,
mut commands: Commands,
) {
let Ok(confirm) = dialogs.get(cancel.entity) else {
return;
};
cancel.propagate(false);
answer(&mut commands, cancel.entity, confirm.safe, true);
}
fn answer(commands: &mut Commands, dialog: Entity, index: usize, cancelled: bool) {
commands.trigger(ConfirmChosen {
entity: dialog,
index,
cancelled,
});
commands.entity(dialog).despawn();
}