use bevy::{
math::Vec4,
prelude::{
default, Assets, Changed, Color, Handle, HandleUntyped, Image, Mesh, Or, Plugin, Query,
Res, Shader, Vec2,
},
reflect::TypeUuid,
render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
render_resource::{
AsBindGroup, AsBindGroupShaderType, RenderPipelineDescriptor, ShaderRef, ShaderType,
SpecializedMeshPipelineError,
},
},
sprite::{Material2d, Material2dKey, Material2dPlugin},
};
use crate::{TerminalFont, TerminalLayout};
use super::{
font::TerminalFontPlugin,
mesh_data::{ATTRIBUTE_COLOR_BG, ATTRIBUTE_COLOR_FG, ATTRIBUTE_UV},
BuiltInFontHandles,
TileScaling,
};
pub const TERMINAL_MATERIAL_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3142086811234592509);
pub struct TerminalMaterialPlugin;
impl Plugin for TerminalMaterialPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_plugin(TerminalFontPlugin);
app.add_plugin(Material2dPlugin::<TerminalMaterial>::default());
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().expect(
"Error initializing TerminalPlugin. Ensure TerminalPlugin is added AFTER
DefaultPlugins during app initialization. (issue #1255)",
);
shaders.set_untracked(
TERMINAL_MATERIAL_SHADER_HANDLE,
Shader::from_wgsl(include_str!("terminal.wgsl")),
);
let fonts = app
.world
.get_resource::<BuiltInFontHandles>()
.expect("Couldn't get font handles");
let font = fonts.get(&TerminalFont::default());
let material = TerminalMaterial::from(font.clone());
app.world
.resource_mut::<Assets<TerminalMaterial>>()
.set_untracked(Handle::<TerminalMaterial>::default(), material);
}
}
#[derive(AsBindGroup, Debug, Clone, TypeUuid)]
#[uuid = "e228a534-e3ca-2e1e-ab9d-4d8bc1ad8c19"]
#[uniform(0, TerminalMaterialUniform)]
pub struct TerminalMaterial {
pub bg_clip_color: Color,
#[texture(1)]
#[sampler(2)]
pub texture: Option<Handle<Image>>,
}
impl Default for TerminalMaterial {
fn default() -> Self {
Self {
bg_clip_color: Color::BLACK,
texture: None,
}
}
}
impl From<Handle<Image>> for TerminalMaterial {
fn from(image: Handle<Image>) -> Self {
TerminalMaterial {
texture: Some(image),
..default()
}
}
}
bitflags::bitflags! {
#[repr(transparent)]
pub struct TerminalMaterialFlags: u32 {
const TEXTURE = (1 << 0);
const NONE = 0;
const UNINITIALIZED = 0xFFFF;
}
}
#[derive(Clone, Default, ShaderType)]
struct TerminalMaterialUniform {
pub color: Vec4,
pub flags: u32,
}
impl AsBindGroupShaderType<TerminalMaterialUniform> for TerminalMaterial {
fn as_bind_group_shader_type(&self, _: &RenderAssets<Image>) -> TerminalMaterialUniform {
let mut flags = TerminalMaterialFlags::NONE;
if self.texture.is_some() {
flags |= TerminalMaterialFlags::TEXTURE;
}
TerminalMaterialUniform {
color: self.bg_clip_color.as_linear_rgba_f32().into(),
flags: flags.bits(),
}
}
}
impl Material2d for TerminalMaterial {
fn fragment_shader() -> ShaderRef {
TERMINAL_MATERIAL_SHADER_HANDLE.typed().into()
}
fn vertex_shader() -> ShaderRef {
TERMINAL_MATERIAL_SHADER_HANDLE.typed().into()
}
fn specialize(
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
_: Material2dKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
let vertex_layout = layout.get_layout(&[
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
ATTRIBUTE_UV.at_shader_location(1),
ATTRIBUTE_COLOR_BG.at_shader_location(2),
ATTRIBUTE_COLOR_FG.at_shader_location(3),
])?;
descriptor.vertex.buffers = vec![vertex_layout];
Ok(())
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn material_change(
materials: Res<Assets<TerminalMaterial>>,
images: Res<Assets<Image>>,
mut q_term: Query<
(&Handle<TerminalMaterial>, &mut TerminalLayout),
Or<(Changed<Handle<TerminalMaterial>>, Changed<TerminalFont>)>,
>,
) {
for (handle, mut layout) in &mut q_term {
if let Some(material) = materials.get(handle) {
if let Some(image) = material.texture.clone() {
if let Some(image) = images.get(&image) {
let font_size = image.size() / 16.0;
layout.pixels_per_tile = font_size.as_uvec2();
layout.tile_size = match layout.scaling {
TileScaling::World => {
let aspect = font_size.x / font_size.y;
Vec2::new(aspect, 1.0)
}
TileScaling::Pixels => font_size,
};
}
}
}
}
}