#[cfg(not(target_arch = "wasm32"))]
use bevy::asset::io::file::FileAssetReader;
use bevy::camera::RenderTarget;
use bevy::render::renderer::{RenderDevice, RenderQueue};
use bevy::render::view::Msaa;
use bevy::render::view::screenshot::{Screenshot, save_to_disk};
use bevy::{
app::AppExit,
diagnostic::{DiagnosticsStore, FrameCount, FrameTimeDiagnosticsPlugin},
ecs::system::{ParallelCommands, SystemParam},
prelude::*,
window::{Monitor, PrimaryMonitor, PrimaryWindow, WindowRef},
winit::{UpdateMode, WinitSettings},
};
use nannou_core::geom;
use nannou_draw::draw::Draw;
use nannou_draw::text::font::SharedTextCx;
use std::cell::{Cell, RefCell};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use crate::app::{UpdateModeExt, find_project_path};
use crate::camera::{CameraComponents, SetCamera};
use crate::light::{LightComponents, SetLight};
use crate::prelude::render::NannouCamera;
#[cfg(feature = "egui")]
use bevy_egui::EguiContext;
#[derive(SystemParam)]
pub struct App<'w, 's> {
par_commands: ParallelCommands<'w, 's>,
time: Res<'w, Time>,
frame_count: Res<'w, FrameCount>,
diagnostics: Option<Res<'w, DiagnosticsStore>>,
keys: Res<'w, ButtonInput<KeyCode>>,
mouse_buttons: Res<'w, ButtonInput<MouseButton>>,
windows: Query<'w, 's, (Entity, &'static bevy::window::Window)>,
primary_window: Query<'w, 's, Entity, With<PrimaryWindow>>,
draws: Query<'w, 's, &'static Draw>,
monitors: Query<'w, 's, (Entity, &'static Monitor)>,
primary_monitor: Query<'w, 's, Entity, With<PrimaryMonitor>>,
camera_msaa: Query<'w, 's, (&'static RenderTarget, &'static Msaa), With<NannouCamera>>,
#[cfg(feature = "egui")]
egui_cameras: Query<'w, 's, (Entity, &'static RenderTarget), With<NannouCamera>>,
#[cfg(feature = "egui")]
egui_contexts: Query<'w, 's, &'static EguiContext>,
asset_server: Res<'w, AssetServer>,
images: Res<'w, Assets<Image>>,
text_cx: Res<'w, SharedTextCx>,
render_device: Option<Res<'w, RenderDevice>>,
render_queue: Option<Res<'w, RenderQueue>>,
current_view: Local<'s, Cell<Option<Entity>>>,
pending_windows: Local<'s, RefCell<Vec<PendingWindow>>>,
}
type PendingWindow = (Entity, bool, bevy::window::Window);
impl<'w, 's> App<'w, 's> {
pub fn time(&self) -> f32 {
self.time.elapsed_secs()
}
pub fn time_delta(&self) -> f32 {
self.time.delta_secs()
}
pub fn elapsed_frames(&self) -> u64 {
(self.frame_count.0 as u64).saturating_sub(1)
}
pub fn fps(&self) -> f64 {
self.diagnostics
.as_ref()
.and_then(|d| d.get(&FrameTimeDiagnosticsPlugin::FPS))
.and_then(|d| d.smoothed())
.unwrap_or(0.0)
}
pub fn keys(&self) -> ButtonInput<KeyCode> {
self.keys.clone()
}
pub fn mouse_buttons(&self) -> ButtonInput<MouseButton> {
self.mouse_buttons.clone()
}
pub(crate) fn with_window<R>(
&self,
entity: Entity,
f: impl FnOnce(&bevy::window::Window) -> R,
) -> Option<R> {
if let Ok((_, window)) = self.windows.get(entity) {
return Some(f(window));
}
let pending = self.pending_windows.borrow();
pending
.iter()
.rev()
.find(|(e, _, _)| *e == entity)
.map(|(_, _, w)| f(w))
}
pub(crate) fn record_pending_window(
&self,
entity: Entity,
primary: bool,
window: bevy::window::Window,
) {
self.pending_windows
.borrow_mut()
.push((entity, primary, window));
}
pub fn mouse(&self) -> Vec2 {
self.with_window(self.window_id(), |window| {
let screen_position = window.cursor_position().unwrap_or(Vec2::ZERO);
screen_to_points(screen_position, window.width(), window.height())
})
.expect("focused window entity is not a window")
}
pub(crate) fn screen_to_points(&self, window: Entity, screen_position: Vec2) -> Vec2 {
self.with_window(window, |window| {
screen_to_points(screen_position, window.width(), window.height())
})
.unwrap_or(Vec2::ZERO)
}
pub fn window_id(&self) -> Entity {
for (entity, window) in self.windows.iter() {
if window.focused {
return entity;
}
}
if let Ok(entity) = self.primary_window.single() {
return entity;
}
if let Some((entity, _, _)) = self.pending_windows.borrow().last() {
return *entity;
}
self.windows
.iter()
.next()
.map(|(entity, _)| entity)
.expect("no windows are open in the App")
}
pub fn window_ids(&self) -> Vec<Entity> {
self.windows.iter().map(|(entity, _)| entity).collect()
}
pub fn window_count(&self) -> usize {
let query_count = self.windows.iter().count();
let pending = self.pending_windows.borrow();
let pending_count = pending
.iter()
.filter(|(e, _, _)| self.windows.get(*e).is_err())
.count();
query_count + pending_count
}
pub fn window_rect(&self) -> geom::Rect<f32> {
self.with_window(self.window_id(), |window| {
geom::Rect::from_w_h(window.width(), window.height())
})
.expect("focused window entity is not a window")
}
pub fn available_monitors(&self) -> Vec<(Entity, Monitor)> {
self.monitors
.iter()
.map(|(entity, monitor)| (entity, monitor.clone()))
.collect()
}
pub fn primary_monitor(&self) -> Option<Entity> {
self.primary_monitor.single().ok()
}
#[cfg(feature = "egui")]
pub fn egui_for_window(&self, window: Entity) -> bevy_egui::egui::Context {
use bevy::window::WindowRef;
let camera = self
.egui_cameras
.iter()
.find_map(|(camera, target)| match target {
RenderTarget::Window(WindowRef::Entity(entity)) if *entity == window => {
Some(camera)
}
_ => None,
})
.expect("no camera found for window");
self.egui_contexts
.get(camera)
.expect("no egui context for the window's camera")
.get()
.clone()
}
#[cfg(feature = "egui")]
pub fn egui(&self) -> bevy_egui::egui::Context {
self.egui_for_window(self.window_id())
}
pub fn draw(&self) -> Draw {
let window = self.current_view.get().unwrap_or_else(|| self.window_id());
self.draw_for_window(window)
}
pub fn draw_for_window(&self, window: Entity) -> Draw {
self.draws
.get(window)
.expect("no `Draw` found for the given window")
.clone()
}
pub(crate) fn set_current_view(&self, view: Option<Entity>) {
self.current_view.set(view);
}
pub fn asset_server(&self) -> &AssetServer {
&self.asset_server
}
pub fn image_assets(&self) -> &Assets<Image> {
&self.images
}
pub fn modify_image(
&self,
handle: &Handle<Image>,
f: impl FnOnce(&mut Image) + Send + 'static,
) {
let handle = handle.clone();
self.command_scope(move |mut commands| {
commands.queue(move |world: &mut World| {
if let Some(mut image) = world.resource_mut::<Assets<Image>>().get_mut(&handle) {
f(&mut image);
}
});
});
}
pub fn text_layout<'b>(&self, s: &'b str) -> nannou_draw::text::Builder<'b> {
nannou_draw::text::Builder::new(s, self.text_cx.clone())
}
pub fn command_scope<R>(&self, f: impl FnOnce(Commands) -> R) -> R {
self.par_commands.command_scope(f)
}
pub fn quit(&self) {
self.par_commands.command_scope(|mut commands| {
commands.queue(|world: &mut World| {
world.write_message(AppExit::Success);
});
});
}
pub fn set_update_mode(&self, mode: UpdateMode) {
self.set_winit_settings(move |settings| {
settings.focused_mode = mode;
settings.unfocused_mode = mode;
});
}
pub fn set_unfocused_update_mode(&self, mode: UpdateMode) {
self.set_winit_settings(move |settings| settings.unfocused_mode = mode);
}
pub fn set_focused_update_mode(&self, mode: UpdateMode) {
self.set_winit_settings(move |settings| settings.focused_mode = mode);
}
pub fn set_update_rate(&self, hz: f64) {
self.set_update_mode(UpdateMode::rate(hz));
}
fn set_winit_settings(&self, f: impl FnOnce(&mut WinitSettings) + Send + 'static) {
self.par_commands.command_scope(move |mut commands| {
commands.queue(move |world: &mut World| {
f(&mut world.resource_mut::<WinitSettings>());
});
});
}
pub fn exe_name(&self) -> std::io::Result<String> {
let string = std::env::current_exe()?
.file_stem()
.expect("exe path contained no file stem")
.to_string_lossy()
.to_string();
Ok(string)
}
pub fn project_path(&self) -> Result<PathBuf, find_folder::Error> {
find_project_path()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn assets_path(&self) -> PathBuf {
FileAssetReader::get_base_path().join("assets")
}
pub fn new_window<M: 'static>(&self) -> crate::window::Builder<'_, 'w, 's, M> {
self.pending_windows
.borrow_mut()
.retain(|(e, _, _)| self.windows.get(*e).is_err());
crate::window::Builder::new(self)
}
pub fn new_camera(&self) -> CameraBuilder<'_, 'w, 's> {
CameraBuilder {
commands: &self.par_commands,
components: CameraComponents {
transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
projection: OrthographicProjection::default_3d().into(),
..default()
},
}
}
pub fn new_light(&self) -> LightBuilder<'_, 'w, 's> {
LightBuilder {
commands: &self.par_commands,
components: LightComponents {
transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
}
}
pub fn window(&self, entity: Entity) -> Window<'_, 'w, 's> {
Window { app: self, entity }
}
pub fn main_window(&self) -> Window<'_, 'w, 's> {
let entity = self
.primary_window
.single()
.ok()
.or_else(|| {
self.pending_windows
.borrow()
.iter()
.rev()
.find(|(_, primary, _)| *primary)
.map(|(e, _, _)| *e)
})
.unwrap_or_else(|| self.window_id());
Window { app: self, entity }
}
}
pub struct Window<'a, 'w, 's> {
app: &'a App<'w, 's>,
entity: Entity,
}
impl Window<'_, '_, '_> {
pub fn id(&self) -> Entity {
self.entity
}
pub fn scale_factor(&self) -> f32 {
self.app
.with_window(self.entity, |w| w.scale_factor())
.expect("entity is not a window")
}
pub fn size_pixels(&self) -> UVec2 {
self.app
.with_window(self.entity, |w| w.physical_size())
.expect("entity is not a window")
}
pub fn size_points(&self) -> Vec2 {
self.app
.with_window(self.entity, |w| w.size())
.expect("entity is not a window")
}
pub fn rect(&self) -> geom::Rect<f32> {
self.app
.with_window(self.entity, |w| geom::Rect::from_w_h(w.width(), w.height()))
.expect("entity is not a window")
}
fn update_window(&self, f: impl FnOnce(&mut bevy::window::Window) + Send + 'static) {
let entity = self.entity;
self.app.command_scope(move |mut commands| {
commands.queue(move |world: &mut World| {
if let Some(mut window) = world.get_mut::<bevy::window::Window>(entity) {
f(&mut window);
}
});
});
}
pub fn set_title(&self, title: impl Into<String>) {
let title = title.into();
self.update_window(move |w| w.title = title);
}
pub fn set_size_points(&self, width: f32, height: f32) {
self.update_window(move |w| w.resolution.set(width, height));
}
pub fn save_screenshot<P: AsRef<Path> + Send + 'static>(&self, path: P) {
let entity = self.entity;
self.app.command_scope(move |mut commands| {
commands
.spawn(Screenshot::window(entity))
.observe(save_to_disk(path));
});
}
pub fn device(&self) -> &crate::wgpu::Device {
self.app
.render_device
.as_ref()
.expect("the RenderDevice resource is not available")
.wgpu_device()
}
pub fn queue(&self) -> &crate::wgpu::Queue {
self.app
.render_queue
.as_ref()
.expect("the RenderQueue resource is not available")
.deref()
.deref()
.deref()
}
pub fn msaa_samples(&self) -> u32 {
for (render_target, msaa) in self.app.camera_msaa.iter() {
if let RenderTarget::Window(WindowRef::Entity(entity)) = render_target {
if *entity == self.entity {
return msaa.samples();
}
}
}
Msaa::default().samples()
}
}
impl nannou_wgpu::WithDeviceQueuePair for &Window<'_, '_, '_> {
fn with_device_queue_pair<F, O>(self, f: F) -> O
where
F: FnOnce(&crate::wgpu::Device, &crate::wgpu::Queue) -> O,
{
f(self.device(), self.queue())
}
}
pub struct CameraBuilder<'a, 'w, 's> {
commands: &'a ParallelCommands<'w, 's>,
components: CameraComponents,
}
impl SetCamera for CameraBuilder<'_, '_, '_> {
fn map_camera<F>(mut self, f: F) -> Self
where
F: FnOnce(CameraComponents) -> CameraComponents,
{
self.components = f(self.components);
self
}
}
impl CameraBuilder<'_, '_, '_> {
pub fn build(self) -> Entity {
let CameraComponents {
transform,
camera,
hdr,
projection,
tonemapping,
bloom_settings,
render_layers,
render_target,
} = self.components;
self.commands.command_scope(move |mut commands| {
let mut entity = commands.spawn((
transform,
camera,
projection,
tonemapping,
render_layers,
NannouCamera,
));
if let Some(bloom_settings) = bloom_settings {
entity.insert(bloom_settings);
}
if let Some(hdr) = hdr {
entity.insert(hdr);
}
if let Some(render_target) = render_target {
entity.insert(render_target);
}
entity.id()
})
}
}
pub struct LightBuilder<'a, 'w, 's> {
commands: &'a ParallelCommands<'w, 's>,
components: LightComponents,
}
impl SetLight for LightBuilder<'_, '_, '_> {
fn map_light<F>(mut self, f: F) -> Self
where
F: FnOnce(LightComponents) -> LightComponents,
{
self.components = f(self.components);
self
}
}
impl LightBuilder<'_, '_, '_> {
pub fn build(self) -> Entity {
let LightComponents {
transform,
directional_light,
render_layers,
} = self.components;
self.commands.command_scope(move |mut commands| {
commands
.spawn((transform, directional_light, render_layers))
.id()
})
}
}
fn screen_to_points(screen_position: Vec2, width: f32, height: f32) -> Vec2 {
Vec2::new(
screen_position.x - width / 2.0,
-(screen_position.y - height / 2.0),
)
}