use bevy::prelude::*;
use bevy::reflect::Reflect;
use bevy_pf::components::{PfFrame, PfListBox};
use bevy_pf::prelude::*;
#[derive(Reflect, Default, Clone)]
struct Expense {
expense_type: String,
expense_amount: String,
}
#[derive(Reflect, Default, Clone)]
struct Person {
name: String,
department: String,
expenses: Vec<Expense>,
}
#[derive(Reflect, Default)]
struct Vm {
people: Vec<Person>,
selected_name: String,
selected_department: String,
selected_expenses: Vec<Expense>,
}
fn expense(t: &str, amount: &str) -> Expense {
Expense {
expense_type: t.into(),
expense_amount: amount.into(),
}
}
fn dataset() -> Vec<Person> {
vec![
Person {
name: "Mike".into(),
department: "Legal".into(),
expenses: vec![expense("Lunch", "50"), expense("Transportation", "50")],
},
Person {
name: "Lisa".into(),
department: "Marketing".into(),
expenses: vec![expense("Document printing", "50"), expense("Gift", "125")],
},
Person {
name: "John".into(),
department: "Engineering".into(),
expenses: vec![
expense("Magazine subscription", "50"),
expense("New machine", "600"),
expense("Software", "500"),
],
},
Person {
name: "Mary".into(),
department: "Finance".into(),
expenses: vec![expense("Dinner", "100")],
},
]
}
#[derive(Resource)]
struct VmHandle(Bindable);
fn primary_window() -> Window {
#[allow(unused_mut)]
let mut window = Window {
title: "ExpenseIt".to_string(),
..Default::default()
};
#[cfg(target_arch = "wasm32")]
{
window.canvas = Some("#bevy-canvas".to_string());
window.fit_canvas_to_parent = true;
}
window
}
fn main() {
App::new()
.add_plugins({
let plugins = DefaultPlugins.set(WindowPlugin {
primary_window: Some(primary_window()),
..Default::default()
});
#[cfg(target_arch = "wasm32")]
let plugins = plugins.disable::<bevy::audio::AudioPlugin>();
plugins
})
.add_plugins(PfUiPlugin)
.register_page(
"ExpenseItHome.xaml",
xaml!(
r##"<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ExpenseIt - Home">
<Grid Margin="10,0,10,10" Background="#FFF6F8FC"
ColumnDefinitions="230, *" RowDefinitions="*, Auto, *, Auto">
<Label Grid.Column="1" Style="{StaticResource HeaderTextStyle}" Content="View Expense Report"/>
<Border Grid.Column="1" Grid.Row="1" Style="{StaticResource ListHeaderStyle}">
<Label FontWeight="ExtraBold" Style="{StaticResource ListHeaderTextStyle}" Content="Names"/>
</Border>
<ListBox x:Name="peopleListBox" Grid.Column="1" Grid.Row="2"
ItemsSource="{Binding people}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="ViewButton" Grid.Column="1" Grid.Row="3"
Style="{StaticResource ButtonStyle}" Content="View"/>
</Grid>
</Page>"##
),
)
.register_page(
"ExpenseReportPage.xaml",
xaml!(
r##"<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ExpenseIt - View Expense Report">
<Grid Background="#FFF6F8FC" ColumnDefinitions="230, *" RowDefinitions="Auto, *">
<Label Grid.Column="1" Style="{StaticResource HeaderTextStyle}" Content="Expense Report For:"/>
<Grid Margin="10" Grid.Column="1" Grid.Row="1"
ColumnDefinitions="*, *" RowDefinitions="Auto, Auto, *">
<StackPanel Grid.ColumnSpan="2" Orientation="Horizontal">
<Label Style="{StaticResource LabelStyle}" Content="Name:"/>
<Label Style="{StaticResource LabelStyle}" Content="{Binding selected_name}"/>
</StackPanel>
<StackPanel Grid.ColumnSpan="2" Grid.Row="1" Orientation="Horizontal">
<Label Style="{StaticResource LabelStyle}" Content="Department:"/>
<Label Style="{StaticResource LabelStyle}" Content="{Binding selected_department}"/>
</StackPanel>
<Grid Grid.ColumnSpan="2" Grid.Row="2" VerticalAlignment="Top" HorizontalAlignment="Left">
<DataGrid Width="360" BorderThickness="2" BorderBrush="Black"
ItemsSource="{Binding selected_expenses}"
AutoGenerateColumns="False" HeadersVisibility="Column"
CanUserResizeColumns="False" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ExpenseType" Binding="{Binding expense_type}" Width="2*"/>
<DataGridTextColumn Header="Amount" Binding="{Binding expense_amount}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
<Hyperlink Grid.Row="2" Grid.Column="1" VerticalAlignment="Bottom"
NavigateUri="ExpenseItHome.xaml" Content="Back to names"/>
</Grid>
</Grid>
</Page>"##
),
)
.add_systems(Startup, setup)
.add_systems(Update, wire_pages)
.run();
}
const STYLES: &str = r##"<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="HeaderTextStyle" TargetType="Label">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontFamily" Value="Trebuchet MS"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="#183862"/>
</Style>
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="0,0,0,5"/>
</Style>
<Style x:Key="ListHeaderStyle" TargetType="Border">
<Setter Property="Padding" Value="5"/>
<Setter Property="Background" Value="#3274CD"/>
</Style>
<Style x:Key="ListHeaderTextStyle" TargetType="Label">
<Setter Property="Foreground" Value="White"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="Width" Value="125"/>
<Setter Property="Margin" Value="0,10,0,0"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
</ResourceDictionary>"##;
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.queue(|world: &mut World| {
let doc = bevy_pf_xaml::parse(STYLES).expect("styles parse");
let warnings = bevy_pf::instantiate::set_application_resources(
world,
&doc,
&bevy_pf::XamlEnv::default(),
);
for w in warnings {
warn!("ExpenseIt styles: {w}");
}
});
let vm = Bindable::new(Vm {
people: dataset(),
..Default::default()
});
commands.spawn_xaml_bound(
xaml!(
r##"<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ExpenseIt">
<Frame x:Name="MainFrame" Source="ExpenseItHome.xaml"/>
</Window>"##
),
vm.clone(),
);
commands.insert_resource(VmHandle(vm));
}
fn wire_pages(mut navigated: MessageReader<PfNavigated>, ui: PfQuery, mut commands: Commands) {
for nav in navigated.read() {
if nav.source != "ExpenseItHome.xaml" {
continue;
}
let Some(button) = ui.by_name("ViewButton") else {
continue;
};
commands
.entity(button)
.observe(|_: On<Pointer<Click>>, mut commands: Commands| {
commands.queue(|world: &mut World| {
let Some((list, selected)) = world
.query::<(Entity, &bevy_pf::components::PfName, &PfListBox)>()
.iter(world)
.find(|(_, n, _)| n.0 == "peopleListBox")
.map(|(e, _, l)| (e, l.selected))
else {
return;
};
let index = selected.and_then(|sel| {
world
.get::<Children>(list)
.and_then(|c| c.iter().position(|e| e == sel))
});
let Some(index) = index else { return }; if let Some(vm) = world.get_resource::<VmHandle>() {
let vm = vm.0.clone();
vm.update(move |m: &mut Vm| {
if let Some(p) = m.people.get(index).cloned() {
m.selected_name = p.name;
m.selected_department = p.department;
m.selected_expenses = p.expenses;
}
});
}
let frame = world
.query::<(Entity, &bevy_pf::components::PfName, &PfFrame)>()
.iter(world)
.find(|(_, n, _)| n.0 == "MainFrame")
.map(|(e, _, _)| e);
if let Some(frame) = frame {
bevy_pf::navigation::navigate(world, frame, "ExpenseReportPage.xaml");
}
});
});
}
}