use bevy::prelude::*;
use crate::XamlEnv;
use crate::components::XamlNames;
#[derive(Message, Debug, Clone)]
pub struct PfDialogResult {
pub dialog: Entity,
pub button: String,
}
fn escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
pub fn show_message(world: &mut World, title: &str, body: &str, buttons: &[&str]) -> Entity {
let mut btns = String::new();
for (i, label) in buttons.iter().enumerate() {
btns.push_str(&format!(
r##"<Button x:Name="PfDlgBtn{i}" Content="{}" MinWidth="88" Margin="8,0,0,0"/>"##,
escape(label)
));
}
let xaml = format!(
r##"<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="#66000000">
<Border Background="#FFF7F7F7" BorderBrush="#FF8A8A8A" BorderThickness="1"
CornerRadius="8" Padding="16" MinWidth="320" MaxWidth="480"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel>
<TextBlock Text="{}" FontSize="16" FontWeight="Bold" Foreground="#FF1A1A1A" Margin="0,0,0,8"/>
<TextBlock Text="{}" TextWrapping="Wrap" Foreground="#FF333333" Margin="0,0,0,16"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">{btns}</StackPanel>
</StackPanel>
</Border>
</Grid>"##,
escape(title),
escape(body),
);
let doc = bevy_pf_xaml::parse(&xaml).expect("dialog template is valid XAML");
let root = world.spawn_empty().id();
if let Err(e) = crate::instantiate_document_env(world, root, &doc, &XamlEnv::default()) {
warn!("bevy_pf: dialog failed to instantiate: {e}");
return root;
}
confine_and_focus_dialog(world, root, None);
if let Some(mut node) = world.get_mut::<Node>(root) {
node.position_type = PositionType::Absolute;
node.display = Display::Flex;
node.flex_direction = FlexDirection::Column;
node.align_items = AlignItems::Center;
node.justify_content = JustifyContent::Center;
node.left = Val::Px(0.0);
node.top = Val::Px(0.0);
node.width = Val::Percent(100.0);
node.height = Val::Percent(100.0);
}
world.entity_mut(root).insert((
bevy::ui::GlobalZIndex(i32::MAX - 64),
Interaction::default(),
));
let names: Vec<(usize, Entity)> = world
.get::<XamlNames>(root)
.map(|n| {
(0..buttons.len())
.filter_map(|i| n.get(&format!("PfDlgBtn{i}")).map(|e| (i, e)))
.collect()
})
.unwrap_or_default();
for (i, button_entity) in names {
let label = buttons[i].to_string();
world.entity_mut(button_entity).observe(
move |_: On<Pointer<Click>>, mut commands: Commands| {
let label = label.clone();
commands.queue(move |world: &mut World| {
close_dialog(world, root, &label);
});
},
);
}
root
}
pub fn show_content(
world: &mut World,
title: &str,
content: &crate::XamlScene,
buttons: &[&str],
) -> Entity {
let mut btns = String::new();
for (i, label) in buttons.iter().enumerate() {
btns.push_str(&format!(
r##"<Button x:Name="PfDlgBtn{i}" Content="{}" MinWidth="88" Margin="8,0,0,0"/>"##,
escape(label)
));
}
let xaml = format!(
r##"<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="#66000000">
<Border Background="#FFF7F7F7" BorderBrush="#FF8A8A8A" BorderThickness="1"
CornerRadius="8" Padding="16" MinWidth="320" MaxWidth="520"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel>
<TextBlock Text="{}" FontSize="16" FontWeight="Bold" Foreground="#FF1A1A1A" Margin="0,0,0,10"/>
<Border x:Name="PfDlgContent" Margin="0,0,0,16"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">{btns}</StackPanel>
</StackPanel>
</Border>
</Grid>"##,
escape(title),
);
let doc = bevy_pf_xaml::parse(&xaml).expect("dialog template is valid XAML");
let root = world.spawn_empty().id();
if let Err(e) = crate::instantiate_document_env(world, root, &doc, &XamlEnv::default()) {
warn!("bevy_pf: content dialog failed to instantiate: {e}");
return root;
}
let host = world
.get::<XamlNames>(root)
.and_then(|n| n.get("PfDlgContent"));
let inner_scene = if let Some(host) = host {
let inner = world.spawn_empty().id();
match crate::instantiate_document_env(
world,
inner,
&content.document(),
&XamlEnv::default(),
) {
Ok(_) => {
world.entity_mut(host).add_children(&[inner]);
Some(inner)
}
Err(e) => {
warn!("bevy_pf: content dialog body failed to instantiate: {e}");
world.entity_mut(inner).despawn();
None
}
}
} else {
None
};
confine_and_focus_dialog(world, root, inner_scene);
if let Some(mut node) = world.get_mut::<Node>(root) {
node.position_type = PositionType::Absolute;
node.display = Display::Flex;
node.flex_direction = FlexDirection::Column;
node.align_items = AlignItems::Center;
node.justify_content = JustifyContent::Center;
node.left = Val::Px(0.0);
node.top = Val::Px(0.0);
node.width = Val::Percent(100.0);
node.height = Val::Percent(100.0);
}
world.entity_mut(root).insert((
bevy::ui::GlobalZIndex(i32::MAX - 64),
Interaction::default(),
));
let names: Vec<(usize, Entity)> = world
.get::<XamlNames>(root)
.map(|n| {
(0..buttons.len())
.filter_map(|i| n.get(&format!("PfDlgBtn{i}")).map(|e| (i, e)))
.collect()
})
.unwrap_or_default();
for (i, button_entity) in names {
let label = buttons[i].to_string();
world.entity_mut(button_entity).observe(
move |_: On<Pointer<Click>>, mut commands: Commands| {
let label = label.clone();
commands.queue(move |world: &mut World| {
close_dialog(world, root, &label);
});
},
);
}
root
}
fn confine_and_focus_dialog(world: &mut World, root: Entity, inner_scene: Option<Entity>) {
use bevy::input_focus::tab_navigation::TabGroup;
if let Some(inner) = inner_scene {
world.entity_mut(inner).remove::<TabGroup>();
}
world.entity_mut(root).insert(TabGroup::modal());
if let Some(first) = crate::behaviors::find_editable_in(world, root) {
crate::behaviors::focus_control(world, first);
}
}
pub fn close_dialog(world: &mut World, dialog: Entity, button: &str) {
if world.get_entity(dialog).is_err() {
return; }
world.write_message(PfDialogResult {
dialog,
button: button.to_string(),
});
world.entity_mut(dialog).despawn();
}