use bevy::prelude::*;
use bevy::reflect::Reflect;
use bevy_pf::components::PfComboBox;
use bevy_pf::prelude::*;
use bevy_pf::themes::{THEMES, apply_theme};
#[derive(Reflect, Default)]
struct Row {
name: String,
kind: String,
}
#[derive(Reflect, Default)]
struct Vm {
rows: Vec<Row>,
}
fn primary_window() -> Window {
#[allow(unused_mut)]
let mut window = Window {
title: "bevy_pf theme gallery".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)
.add_systems(Startup, setup)
.add_systems(Update, switch_theme)
.run();
}
fn scene() -> String {
let items: String = THEMES
.iter()
.map(|t| format!(r##"<ComboBoxItem Content="{}"/>"##, t.name))
.collect();
format!(
r##"<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="bevy_pf theme gallery">
<DockPanel>
<ToolBar DockPanel.Dock="Top">
<TextBlock Text="Theme:" VerticalAlignment="Center" Margin="4,0,8,0"/>
<ComboBox x:Name="ThemePick" Width="200" SelectedIndex="0">{items}</ComboBox>
</ToolBar>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem><TextBlock x:Name="Status" Text="theme: fluent-light"/></StatusBarItem>
</StatusBar>
<Grid Margin="16" ColumnDefinitions="280, 16, *">
<StackPanel>
<TextBlock Text="Inputs" FontSize="18" FontWeight="Bold" Margin="0,0,0,8"/>
<Button Content="A themed button" Width="220" HorizontalAlignment="Left"/>
<ToggleButton Content="Toggle" Width="220" Margin="0,6,0,0" HorizontalAlignment="Left"/>
<TextBox Text="Themed input" Width="220" Margin="0,6,0,0" HorizontalAlignment="Left"/>
<CheckBox Content="Themed check" IsChecked="True" Margin="0,8,0,0"/>
<DatePicker Width="220" Margin="0,8,0,0" HorizontalAlignment="Left"/>
<ProgressBar Minimum="0" Maximum="100" Value="64" Width="220" Height="12" Margin="0,10,0,0" HorizontalAlignment="Left"/>
<ProgressBar IsIndeterminate="True" Width="220" Height="12" Margin="0,6,0,0" HorizontalAlignment="Left"/>
<Hyperlink Content="A themed hyperlink" NavigateUri="https://github.com/edgarhsanchez/bevy_pf" Margin="0,10,0,0"/>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="Data" FontSize="18" FontWeight="Bold" Margin="0,0,0,8"/>
<ListView ItemsSource="{{Binding rows}}" Height="220">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{{Binding name}}" Width="2*"/>
<GridViewColumn Header="Kind" DisplayMemberBinding="{{Binding kind}}" Width="*"/>
</GridView>
</ListView.View>
</ListView>
<Calendar Margin="0,12,0,0" HorizontalAlignment="Left"/>
</StackPanel>
</Grid>
</DockPanel>
</Window>"##
)
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.queue(|world: &mut World| {
apply_theme(world, THEMES[0].slug).expect("built-in theme applies");
});
let vm = Bindable::new(Vm {
rows: vec![
Row {
name: "fluent-light".into(),
kind: "light".into(),
},
Row {
name: "nord".into(),
kind: "dark".into(),
},
Row {
name: "dracula".into(),
kind: "dark".into(),
},
Row {
name: "catppuccin-mocha".into(),
kind: "dark".into(),
},
Row {
name: "solarized-light".into(),
kind: "light".into(),
},
],
});
let scene = XamlScene::parse(scene()).expect("gallery scene is valid XAML");
commands.spawn_xaml_bound(scene, vm);
}
fn switch_theme(
combos: Query<&PfComboBox, Changed<PfComboBox>>,
ui: PfQuery,
mut texts: Query<&mut bevy::ui::widget::Text>,
mut commands: Commands,
mut last: Local<Option<usize>>,
) {
let Some(combo) = combos.iter().next() else {
return;
};
let Some(index) = combo.selected else {
return;
};
if *last == Some(index) {
return;
}
*last = Some(index);
let theme = THEMES[index.min(THEMES.len() - 1)];
commands.queue(move |world: &mut World| {
if let Err(e) = apply_theme(world, theme.slug) {
warn!("bevy_pf: theme swap failed: {e}");
}
});
if let Some(status) = ui.by_name("Status")
&& let Some(text_entity) = ui.first_text_in(status)
&& let Ok(mut text) = texts.get_mut(text_entity)
{
text.0 = format!("theme: {}", theme.slug);
}
}