use bevy::{
camera::{visibility::RenderLayers, RenderTarget},
ecs::system::EntityCommands,
image::ImageSampler,
prelude::*,
render::view::{Hdr, ViewTarget},
};
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
pub fn update_canvases(mut canvases: Query<(&mut Canvas, &mut Camera, &mut RenderTarget, &mut Projection)>) {
canvases
.iter_mut()
.for_each(|(mut canvas, mut camera, mut target, mut projection)| {
if let RenderTarget::Image(camera_handle) = target.as_ref() {
if camera_handle.handle != canvas.image {
*target = RenderTarget::Image(canvas.image.clone().into());
projection.set_changed();
}
}
match canvas.mode {
CanvasMode::Continuous => {
camera.clear_color = canvas.clear_color;
camera.is_active = true;
}
CanvasMode::Persistent => {
if canvas.redraw {
camera.clear_color = canvas.clear_color;
} else {
camera.clear_color = ClearColorConfig::None;
}
}
CanvasMode::OnDemand => {
camera.is_active = canvas.redraw;
}
}
canvas.redraw = false;
})
}
#[derive(Default, Reflect)]
pub enum CanvasMode {
#[default]
Continuous,
Persistent,
OnDemand,
}
#[derive(Component, Reflect)]
pub struct Canvas {
pub image: Handle<Image>,
pub width: u32,
pub height: u32,
pub mode: CanvasMode,
pub clear_color: ClearColorConfig,
redraw: bool,
}
impl Canvas {
pub fn create_image(
assets: &mut Assets<Image>,
width: u32,
height: u32,
sampler: ImageSampler,
hdr: bool,
) -> Handle<Image> {
let size = Extent3d {
width,
height,
..default()
};
let mut image = Image {
texture_descriptor: TextureDescriptor {
label: None,
size,
dimension: TextureDimension::D2,
format: if hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
mip_level_count: 1,
sample_count: 1,
usage: TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_DST
| TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
},
sampler,
..default()
};
image.resize(size);
assets.add(image)
}
pub fn resize(&mut self, assets: &mut Assets<Image>, width: u32, height: u32) -> Handle<Image> {
self.width = width;
self.height = height;
let image = assets
.get_mut(&self.image)
.expect("Tried to resize canvas image that does not exist.");
let size = Extent3d {
width,
height,
..default()
};
let mut new_image = image.clone();
new_image.resize(size);
let handle = assets.add(new_image);
self.image = handle.clone();
handle
}
pub fn redraw(&mut self) {
self.redraw = true;
}
}
#[derive(Default)]
pub struct CanvasConfig {
pub clear_color: ClearColorConfig,
pub mode: CanvasMode,
pub width: u32,
pub height: u32,
pub order: isize,
pub sampler: ImageSampler,
pub hdr: bool,
}
impl CanvasConfig {
pub fn new(width: u32, height: u32) -> Self {
Self {
clear_color: ClearColorConfig::Default,
mode: CanvasMode::default(),
width,
height,
order: -1,
sampler: ImageSampler::Default,
hdr: false,
}
}
}
#[derive(Bundle)]
pub struct CanvasBundle {
camera_2d: Camera2d,
camera: Camera,
target: RenderTarget,
canvas: Canvas,
render_layers: RenderLayers,
}
impl CanvasBundle {
pub fn new(image: Handle<Image>, config: CanvasConfig) -> Self {
Self {
camera_2d: Camera2d,
camera: Camera {
order: config.order,
clear_color: config.clear_color,
..default()
},
target: RenderTarget::Image(image.clone().into()),
canvas: Canvas {
image,
width: config.width,
height: config.height,
mode: config.mode,
clear_color: config.clear_color,
redraw: true,
},
render_layers: RenderLayers::none(),
}
}
}
pub trait CanvasCommands<'w> {
fn spawn_canvas(
&mut self,
assets: &mut Assets<Image>,
config: CanvasConfig,
) -> (Handle<Image>, EntityCommands<'_>);
}
impl<'w, 's> CanvasCommands<'w> for Commands<'w, 's> {
fn spawn_canvas(
&mut self,
assets: &mut Assets<Image>,
config: CanvasConfig,
) -> (Handle<Image>, EntityCommands<'_>) {
let handle = Canvas::create_image(
assets,
config.width,
config.height,
config.sampler.clone(),
config.hdr,
);
let hdr = config.hdr;
let mut entity = self.spawn(CanvasBundle::new(handle.clone(), config));
if hdr {
entity.insert(Hdr);
}
(handle, entity)
}
}