//! Example of a simple UI layout #![feature(impl_trait_in_assoc_type)] use bevy::{color::palettes, prelude::*, ui}; use bevy_mod_picking::DefaultPickingPlugins; use bevy_mod_stylebuilder::*; use bevy_quill::*; use bevy_quill_obsidian::{ colors, controls::{ Button, ButtonVariant, Checkbox, ColorGradient, Dialog, DialogFooter, DialogHeader, GradientSlider, MenuButton, MenuDivider, MenuItem, MenuPopup, Slider, SpinBox, Swatch, }, ObsidianUiPlugin, }; fn style_test(ss: &mut StyleBuilder) { ss.display(Display::Flex) .flex_direction(FlexDirection::Column) .position(ui::PositionType::Absolute) .padding(3) .left(0) .right(0) .top(0) .bottom(0) .row_gap(4) .background_color(colors::U2); } fn style_row(ss: &mut StyleBuilder) { ss.display(Display::Flex) .flex_direction(FlexDirection::Row) .align_items(ui::AlignItems::Center) .column_gap(4); } fn style_swatch(ss: &mut StyleBuilder) { ss.width(24).height(24); } fn main() { App::new() .add_plugins(( DefaultPlugins, DefaultPickingPlugins, QuillPlugin, ObsidianUiPlugin, )) .add_systems(Startup, setup_view_root) .add_systems(Update, close_on_esc) .run(); } fn setup_view_root(mut commands: Commands) { let camera = commands .spawn((Camera2dBundle { camera: Camera::default(), camera_2d: Camera2d {}, ..default() },)) .id(); commands.spawn(ButtonsDemo { camera }.to_root()); } #[derive(Clone, PartialEq)] struct ButtonsDemo { camera: Entity, } impl ViewTemplate for ButtonsDemo { type View = impl View; fn create(&self, cx: &mut Cx) -> Self::View { let checked = cx.create_mutable(true); let disabled = cx.create_mutable(false); let dialog_open = cx.create_mutable(false); let on_checked = cx.create_callback(move |value: In, world: &mut World| { checked.set(world, *value); // info!("Checked: {}", *value); }); let spin_value = cx.create_mutable::(50.); let slider_value = cx.create_mutable::(50.); let color_value = cx.create_mutable::(Srgba::new(1.0, 0.0, 0.0, 1.0)); let color = color_value.get(cx); Element::::new() .insert_dyn(TargetCamera, self.camera) .style(style_test) .children(( "Checkbox", Element::::new().style(style_row).children(( Checkbox::new() .checked(checked.get(cx)) .on_change(on_checked) .label("Checkbox"), Checkbox::new() .disabled(disabled.get(cx)) .checked(checked.get(cx)) .on_change(on_checked) .label("Checkbox (disabled)"), Checkbox::new() .checked(disabled.get(cx)) .on_change( cx.create_callback(move |value: In, world: &mut World| { disabled.set(world, *value); // info!("Disabled: {}", *value); }), ) .label("Disable"), )), "Swatch", Element::::new().style(style_row).children(( Swatch::new(palettes::css::RED).style(style_swatch), Swatch::new(colors::U1).style(style_swatch), Swatch::new(Srgba::new(0.5, 1.0, 0.0, 0.5)).style(style_swatch), )), "Spinbox", Element::::new() .style(style_row) .children((SpinBox::new() .style(|sb: &mut StyleBuilder| { sb.width(100); }) .value(spin_value.get(cx)) .on_change(cx.create_callback( move |value: In, world: &mut World| { spin_value.set(world, *value); }, )),)), "Slider", Element::::new().style(style_row).children(( " Normal:", Slider::new() .range(0. ..=100.) .style(|sb: &mut StyleBuilder| { sb.width(100); }) .value(slider_value.get(cx)) .on_change( cx.create_callback(move |value: In, world: &mut World| { slider_value.set(world, *value); }), ), " Compact:", Slider::new() .range(0. ..=100.) .style(|sb: &mut StyleBuilder| { sb.width(50); }) .value(slider_value.get(cx)) .on_change( cx.create_callback(move |value: In, world: &mut World| { slider_value.set(world, *value); }), ), " Precision:", Slider::new() .range(0. ..=100.) .style(|sb: &mut StyleBuilder| { sb.width(100); }) .precision(2) .value(slider_value.get(cx)) .on_change( cx.create_callback(move |value: In, world: &mut World| { slider_value.set(world, *value); }), ), " Labeled:", Slider::new() .range(0. ..=100.) .style(|sb: &mut StyleBuilder| { sb.width(100); }) .label("Gain") .value(slider_value.get(cx)) .on_change( cx.create_callback(move |value: In, world: &mut World| { slider_value.set(world, *value); }), ), " Custom Format:", Slider::new() .range(0. ..=100.) .style(|sb: &mut StyleBuilder| { sb.width(100); }) .formatted_value(format!("{:.0} dB", slider_value.get(cx))) .value(slider_value.get(cx)) .on_change( cx.create_callback(move |value: In, world: &mut World| { slider_value.set(world, *value); }), ), )), "GradientSlider", GradientSlider::new() .gradient(ColorGradient::new(&[ Srgba::new(color.red, 0.0, color.blue, 1.0), Srgba::new(color.red, 1.0, color.blue, 1.0), ])) .min(0.) .max(255.) .value(color.green * 255.0) .style(|sb: &mut StyleBuilder| { sb.width(100); }) .precision(1) .on_change( cx.create_callback(move |value: In, world: &mut World| { color_value.update(world, |mut state| state.green = *value / 255.0); }), ), "Dialog", Element::::new().style(style_row).children(( Button::new() .on_click(cx.create_callback(move |world: &mut World| { dialog_open.set(world, true); })) .children("Open..."), Dialog::new() .width(ui::Val::Px(400.)) .open(dialog_open.get(cx)) .on_close(cx.create_callback(move |world: &mut World| { dialog_open.set(world, false); })) .children(( DialogHeader::new().children("Dialog Header"), DialogFooter::new().children(( Button::new() .children("Cancel") .on_click(cx.create_callback(move |world: &mut World| { dialog_open.set(world, false); })), Button::new() .children("Close") .variant(ButtonVariant::Primary) .autofocus(true) .on_click(cx.create_callback(move |world: &mut World| { dialog_open.set(world, false); })), )), )), )), "MenuButton", Element::::new() .style(style_row) .children((MenuButton::new().children("Menu").popup( MenuPopup::new().children(( MenuItem::new() .label("Alpha Male") .on_click(cx.create_callback(|| { println!("Alpha item clicked"); })), MenuItem::new() .label("Beta Test") .on_click(cx.create_callback(|| { println!("Beta item clicked"); })), MenuItem::new() .label("Delta Sleep") .on_click(cx.create_callback(|| { println!("Delta item clicked"); })), MenuItem::new() .label("Gamma Ray") .on_click(cx.create_callback(|| { println!("Gamma item clicked"); })), MenuDivider, MenuItem::new() .label("Omega Point") .on_click(cx.create_callback(|| { println!("Omega item clicked"); })), )), ),)), )) } } pub fn close_on_esc(input: Res>, mut exit: EventWriter) { if input.just_pressed(KeyCode::Escape) { exit.send(AppExit::Success); } }