#![feature(impl_trait_in_assoc_type)]
mod reflect_demo;
use bevy_mod_picking::{
backends::raycast::{RaycastBackendSettings, RaycastPickable},
debug::DebugPickingMode,
prelude::*,
DefaultPickingPlugins,
};
use bevy_mod_stylebuilder::*;
use bevy_quill_obsidian::{
colors,
controls::{
Button, ButtonVariant, Checkbox, Dialog, DialogBody, DialogFooter, DialogHeader, ListView,
Slider, Splitter, SplitterDirection, ToolButton, ToolPalette,
},
focus::TabGroup,
prelude::ListRow,
typography, viewport, ObsidianUiPlugin, RoundedCorners,
};
use bevy_quill_obsidian_inspect::InspectorPlugin;
use reflect_demo::{ResourcePropertyInspector, TestStruct, TestStruct2, TestStruct3};
use std::f32::consts::PI;
use bevy::{
asset::io::{file::FileAssetReader, AssetSource},
color::palettes,
prelude::*,
render::{
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat},
},
ui,
};
use bevy_quill::*;
fn style_main(ss: &mut StyleBuilder) {
ss.position(ui::PositionType::Absolute)
.left(0)
.top(0)
.bottom(0)
.right(0)
.border(1)
.border_color(colors::U2)
.display(ui::Display::Flex)
.pointer_events(false);
}
fn style_aside(ss: &mut StyleBuilder) {
ss.display(ui::Display::Flex)
.background_color(colors::U2)
.padding(8)
.gap(8)
.flex_direction(ui::FlexDirection::Column)
.width(200)
.pointer_events(true);
}
fn style_button_row(ss: &mut StyleBuilder) {
ss.gap(8);
}
fn style_button_flex(ss: &mut StyleBuilder) {
ss.flex_grow(1.);
}
fn style_slider(ss: &mut StyleBuilder) {
ss.align_self(ui::AlignSelf::Stretch);
}
fn style_column_group(ss: &mut StyleBuilder) {
ss.display(ui::Display::Flex)
.flex_direction(ui::FlexDirection::Column)
.align_items(ui::AlignItems::FlexStart)
.gap(8);
}
fn style_scroll_area(ss: &mut StyleBuilder) {
ss.flex_grow(1.0);
}
#[derive(Resource)]
pub struct PanelWidth(f32);
#[derive(Resource, Default)]
pub struct SelectedShape(Option<Entity>);
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)]
pub enum EditorState {
#[default]
Preview,
Graph,
Split,
}
#[derive(Resource)]
pub struct PreviewEntities {
camera: Entity,
_overlay: Entity,
}
#[derive(Resource, Default)]
pub struct ClickLog(pub Vec<String>);
fn main() {
App::new()
.register_asset_source(
"demo",
AssetSource::build()
.with_reader(|| Box::new(FileAssetReader::new("examples/complex/assets"))),
)
.init_resource::<SelectedShape>()
.init_resource::<TrackingScopeTracing>()
.init_resource::<ClickLog>()
.insert_resource(TestStruct {
unlit: Some(true),
..default()
})
.insert_resource(TestStruct2 {
nested: TestStruct::default(),
..default()
})
.insert_resource(TestStruct3(true))
.insert_resource(PanelWidth(200.))
.init_resource::<viewport::ViewportInset>()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.add_plugins(DefaultPickingPlugins)
.insert_state(EditorState::Preview)
.insert_resource(DebugPickingMode::Disabled)
.insert_resource(RaycastBackendSettings {
require_markers: true,
..default()
})
.add_plugins(InspectorPlugin)
.add_plugins((
QuillPlugin,
ObsidianUiPlugin,
))
.add_systems(Startup, (setup, setup_ui.pipe(setup_view_root)))
.add_systems(
Update,
(
close_on_esc,
rotate.run_if(in_state(EditorState::Preview)),
rotate.run_if(in_state(EditorState::Split)),
viewport::update_viewport_inset.run_if(in_state(EditorState::Preview)),
viewport::update_viewport_inset.run_if(in_state(EditorState::Split)),
viewport::update_camera_viewport.run_if(in_state(EditorState::Preview)),
viewport::update_camera_viewport.run_if(in_state(EditorState::Split)),
),
)
.add_systems(OnEnter(EditorState::Preview), enter_preview_mode)
.add_systems(OnExit(EditorState::Preview), exit_preview_mode)
.add_systems(OnEnter(EditorState::Split), enter_preview_mode)
.add_systems(OnExit(EditorState::Split), exit_preview_mode)
.run();
}
#[derive(Component)]
struct Shape;
const X_EXTENT: f32 = 14.5;
fn setup_view_root(camera: In<Entity>, mut commands: Commands) {
commands.spawn(DemoUi(*camera).to_root());
}
#[derive(Clone, PartialEq)]
struct DemoUi(Entity);
impl ViewTemplate for DemoUi {
type View = impl View;
fn create(&self, cx: &mut Cx) -> Self::View {
let dialog_open = cx.create_mutable(false);
let checked_1 = cx.create_mutable(false);
let checked_2 = cx.create_mutable(true);
let red = cx.create_mutable::<f32>(128.);
let panel_width = cx.use_resource::<PanelWidth>().0;
let camera = self.0;
cx.insert(TargetCamera(camera));
Element::<NodeBundle>::new()
.named("Main")
.style((typography::text_default, style_main))
.insert_dyn(move |_| (TabGroup::default(), TargetCamera(camera)), ())
.children((
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"),
DialogBody::new().children("Example dialog body text."),
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);
})),
)),
)),
Element::<NodeBundle>::new()
.named("ControlPalette")
.style(style_aside)
.style_dyn(
move |width, sb| {
sb.width(ui::Val::Px(width));
},
panel_width,
)
.children((
ToolPalette::new().columns(3).children((
ToolButton::new()
.children("Preview")
.corners(RoundedCorners::Left)
.variant({
let st = cx.use_resource::<State<EditorState>>();
if *st.get() == EditorState::Preview {
ButtonVariant::Selected
} else {
ButtonVariant::Default
}
})
.on_click(cx.create_callback(
|mut mode: ResMut<NextState<EditorState>>| {
mode.set(EditorState::Preview);
},
)),
ToolButton::new()
.children("Materials")
.corners(RoundedCorners::None)
.variant({
let st = cx.use_resource::<State<EditorState>>();
if *st.get() == EditorState::Graph {
ButtonVariant::Selected
} else {
ButtonVariant::Default
}
})
.on_click(cx.create_callback(
|mut mode: ResMut<NextState<EditorState>>| {
mode.set(EditorState::Graph);
},
)),
ToolButton::new()
.children("Split")
.corners(RoundedCorners::Right)
.variant({
let st = cx.use_resource::<State<EditorState>>();
if *st.get() == EditorState::Split {
ButtonVariant::Selected
} else {
ButtonVariant::Default
}
})
.on_click(cx.create_callback(
|mut mode: ResMut<NextState<EditorState>>| {
mode.set(EditorState::Split);
},
)),
)),
Element::<NodeBundle>::new()
.style(style_button_row)
.children((
Button::new()
.children("Open…")
.on_click(cx.create_callback(move |world: &mut World| {
let mut log = world.get_resource_mut::<ClickLog>().unwrap();
log.0.push("Open clicked".to_string());
dialog_open.set(world, true);
}))
.style(style_button_flex),
Button::new()
.children("Save")
.on_click(cx.create_callback(
move |mut log: ResMut<ClickLog>| {
log.0.push("Save clicked".to_string());
},
))
.style(style_button_flex),
)),
Element::<NodeBundle>::new()
.style(style_column_group)
.children((
Checkbox::new()
.label("Include Author Name")
.checked(checked_1.get(cx))
.on_change(cx.create_callback(
move |checked: In<bool>, world: &mut World| {
let mut log =
world.get_resource_mut::<ClickLog>().unwrap();
log.0
.push(format!("Include Author Name: {}", *checked));
checked_1.set(world, *checked);
},
)),
Checkbox::new()
.label("Include Metadata")
.checked(checked_2.get(cx))
.on_change(cx.create_callback(
move |checked: In<bool>, world: &mut World| {
let mut log =
world.get_resource_mut::<ClickLog>().unwrap();
log.0.push(format!("Include Metadata: {}", *checked));
checked_2.set(world, *checked);
},
)),
)),
Element::<NodeBundle>::new()
.style(style_column_group)
.children(
Slider::new()
.min(0.)
.max(255.)
.value(red.get(cx))
.style(style_slider)
.precision(1)
.on_change(cx.create_callback(
move |value: In<f32>, world: &mut World| {
let mut log =
world.get_resource_mut::<ClickLog>().unwrap();
log.0.push(format!("Slider value: {}", *value));
red.set(world, *value);
},
)),
),
ResourcePropertyInspector::<TestStruct>::new(),
ResourcePropertyInspector::<TestStruct2>::new(),
ResourcePropertyInspector::<TestStruct3>::new(),
LogList,
)),
Splitter::new()
.direction(SplitterDirection::Vertical)
.value(panel_width)
.on_change(cx.create_callback(|value: In<f32>, world: &mut World| {
let mut panel_width = world.get_resource_mut::<PanelWidth>().unwrap();
panel_width.0 = value.max(200.);
})),
CenterPanel,
))
}
}
fn wrapper_style(ss: &mut StyleBuilder) {
ss.display(Display::Flex)
.flex_grow(1.)
.align_self(ui::AlignSelf::Stretch)
.flex_direction(FlexDirection::Column);
}
#[derive(Clone, PartialEq)]
struct CenterPanel;
impl ViewTemplate for CenterPanel {
type View = impl View;
fn create(&self, _cx: &mut Cx) -> Self::View {
Element::<NodeBundle>::new()
.children((
NodeGraphDemo {},
))
.style(wrapper_style)
}
}
#[derive(Clone, PartialEq)]
struct LogList;
impl ViewTemplate for LogList {
type View = impl View;
fn create(&self, cx: &mut Cx) -> Self::View {
let log = cx.use_resource::<ClickLog>();
ListView::new()
.children(For::each(log.0.clone(), |msg| {
ListRow::new(msg.clone()).children(msg.clone())
}))
.style(style_scroll_area)
}
}
#[derive(Clone, PartialEq)]
struct NodeGraphDemo;
impl ViewTemplate for NodeGraphDemo {
type View = impl View;
fn create(&self, _cx: &mut Cx) -> Self::View {
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut images: ResMut<Assets<Image>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let debug_material = materials.add(StandardMaterial {
base_color_texture: Some(images.add(uv_debug_texture())),
..default()
});
let shapes = [
meshes.add(Cuboid::default()),
meshes.add(Capsule3d::default()),
meshes.add(Torus::default()),
meshes.add(Cylinder::default()),
meshes.add(Sphere::default().mesh().ico(5).unwrap()),
meshes.add(Sphere::default().mesh().uv(32, 18)),
];
let num_shapes = shapes.len();
let shapes_parent = commands
.spawn((
SpatialBundle { ..default() },
On::<Pointer<Down>>::run(
|mut event: ListenerMut<Pointer<Down>>,
shapes: Query<&Shape>,
mut selection: ResMut<SelectedShape>| {
if shapes.get(event.target).is_ok() {
selection.0 = Some(event.target);
} else {
selection.0 = None;
}
event.stop_propagation();
},
),
))
.id();
for (i, shape) in shapes.into_iter().enumerate() {
commands
.spawn((
PbrBundle {
mesh: shape,
material: debug_material.clone(),
transform: Transform::from_xyz(
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
2.0,
0.0,
)
.with_rotation(Quat::from_rotation_x(-PI / 4.)),
..default()
},
Shape,
RaycastPickable,
))
.set_parent(shapes_parent);
}
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 10000000.0,
range: 100.,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(8.0, 16.0, 8.0),
..default()
});
commands.spawn(PbrBundle {
mesh: meshes.add(Plane3d::default().mesh().size(50.0, 50.0)),
material: materials.add(Color::from(palettes::css::SILVER)),
..default()
});
}
fn setup_ui(mut commands: Commands) -> Entity {
commands
.spawn((Camera2dBundle {
camera: Camera {
order: 1,
clear_color: ClearColorConfig::None,
..default()
},
camera_2d: Camera2d {},
..default()
},))
.id()
}
fn enter_preview_mode(mut commands: Commands) {
let camera = commands
.spawn((
Camera3dBundle {
transform: Transform::from_xyz(0.0, 6., 12.0)
.looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
..default()
},
viewport::ViewportCamera,
RaycastPickable,
))
.id();
let overlay = commands.spawn_empty().id();
commands.insert_resource(PreviewEntities {
camera,
_overlay: overlay,
});
}
fn exit_preview_mode(mut commands: Commands, preview: Res<PreviewEntities>) {
commands.entity(preview.camera).despawn();
commands.remove_resource::<PreviewEntities>()
}
fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) {
for mut transform in &mut query {
transform.rotate_y(time.delta_seconds() / 2.);
}
}
fn uv_debug_texture() -> Image {
const TEXTURE_SIZE: usize = 8;
let mut palette: [u8; 32] = [
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
];
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
for y in 0..TEXTURE_SIZE {
let offset = TEXTURE_SIZE * y * 4;
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
palette.rotate_right(4);
}
Image::new_fill(
Extent3d {
width: TEXTURE_SIZE as u32,
height: TEXTURE_SIZE as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&texture_data,
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::default(),
)
}
pub fn close_on_esc(input: Res<ButtonInput<KeyCode>>, mut exit: EventWriter<AppExit>) {
if input.just_pressed(KeyCode::Escape) {
exit.send(AppExit::Success);
}
}