diff --git a/assets/shaders/test.wgsl b/assets/shaders/test.wgsl new file mode 100644 index 0000000..ee73d75 --- /dev/null +++ b/assets/shaders/test.wgsl @@ -0,0 +1,29 @@ +#import bevy_sprite::mesh2d_view_bindings globals +#import bevy_sprite::mesh2d_vertex_output MeshVertexOutput +#import test_export + +struct CustomMaterial { + color: vec4, +}; + +@group(1) @binding(0) +var material: CustomMaterial; +@group(1) @binding(1) +var base_color_texture: texture_2d; +@group(1) @binding(2) +var base_color_sampler: sampler; + +@fragment +fn fragment( + mesh: MeshVertexOutput, +) -> @location(0) vec4 { + +let t_1 = sin(globals.time*2.0)*0.5+0.5; +let t_2 = cos(globals.time*2.0); + +var color: vec4 = vec4(t_1, t_2, t_1, 0.0); + +color.a = test_export::i_say_one(); + + return color * textureSample(base_color_texture, base_color_sampler, mesh.uv); +} diff --git a/assets/shaders/test_export.wgsl b/assets/shaders/test_export.wgsl new file mode 100644 index 0000000..8bc0d44 --- /dev/null +++ b/assets/shaders/test_export.wgsl @@ -0,0 +1,5 @@ +#define_import_path test_export + +fn i_say_one() -> f32 { + return 1.0; + } diff --git a/examples/shader_2d.rs b/examples/shader_2d.rs new file mode 100644 index 0000000..b378238 --- /dev/null +++ b/examples/shader_2d.rs @@ -0,0 +1,145 @@ +use bevy::{ + prelude::*, + reflect::{TypePath, TypeUuid}, + render::render_resource::{AsBindGroup, ShaderRef}, + sprite::{Material2d, MaterialMesh2dBundle}, + window::PrimaryWindow, +}; +use bevy_cosmic_edit::*; + +// Define material +#[derive(AsBindGroup, TypeUuid, TypePath, Debug, Clone, Default)] +#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] +pub struct CustomMaterial { + #[uniform(0)] + color: Color, + #[texture(1)] + #[sampler(2)] + color_texture: Handle, +} + +impl Material2d for CustomMaterial { + fn fragment_shader() -> ShaderRef { + "shaders/test.wgsl".into() + } +} + +// implement required cosmic bits +impl CosmicMaterial2d for CustomMaterial { + fn color_texture(&self) -> &Handle { + // if using different bindings change the names in the impl too + &self.color_texture + } + fn set_color_texture(&mut self, texture: &Handle) -> &mut Self { + self.color_texture = texture.clone_weak(); + self + } +} + +fn setup( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, + asset_server: Res, +) { + commands.spawn(Camera2dBundle::default()); + + // Shaders need to be in loaded to be accessed + commands.spawn(asset_server.load("shaders/test_export.wgsl") as Handle); + + // Sprite editor + let editor = commands + .spawn((CosmicEditBundle { + sprite_bundle: SpriteBundle { + // Sets size of text box + sprite: Sprite { + custom_size: Some(Vec2::new(300., 100.)), + ..default() + }, + // Position of text box + transform: Transform::from_xyz(0., 200., 0.), + ..default() + }, + ..default() + },)) + .id(); + + // TODO: click fns + // + // TODO: set size of sprite from mesh automagically + commands + .spawn(MaterialMesh2dBundle { + mesh: meshes + .add(Mesh::from(shape::Quad::new(Vec2::new(300., 100.)))) + .into(), + material: materials.add(CustomMaterial::default()), + ..default() + }) + .insert(CosmicSource(editor)); + + commands.insert_resource(Focus(Some(editor))); +} + +fn change_active_editor_ui( + mut commands: Commands, + mut interaction_query: Query< + (&Interaction, &CosmicSource), + (Changed, Without), + >, +) { + for (interaction, source) in interaction_query.iter_mut() { + if let Interaction::Pressed = interaction { + commands.insert_resource(Focus(Some(source.0))); + } + } +} + +fn change_active_editor_sprite( + mut commands: Commands, + windows: Query<&Window, With>, + buttons: Res>, + mut cosmic_edit_query: Query< + (&mut Sprite, &GlobalTransform, &Visibility, Entity), + (With, Without), + >, + camera_q: Query<(&Camera, &GlobalTransform)>, +) { + let window = windows.single(); + let (camera, camera_transform) = camera_q.single(); + if buttons.just_pressed(MouseButton::Left) { + for (sprite, node_transform, visibility, entity) in &mut cosmic_edit_query.iter_mut() { + if visibility == Visibility::Hidden { + continue; + } + let size = sprite.custom_size.unwrap_or(Vec2::ONE); + let x_min = node_transform.affine().translation.x - size.x / 2.; + let y_min = node_transform.affine().translation.y - size.y / 2.; + let x_max = node_transform.affine().translation.x + size.x / 2.; + let y_max = node_transform.affine().translation.y + size.y / 2.; + if let Some(pos) = window.cursor_position() { + if let Some(pos) = camera.viewport_to_world_2d(camera_transform, pos) { + if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max { + commands.insert_resource(Focus(Some(entity))) + }; + } + }; + } + } +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(CosmicEditPlugin { + change_cursor: CursorConfig::Default, + ..default() + }) + // This works like a passthrough, passes the material to Material2dPlugin + // once it assigns required fns + .add_plugins(CosmicMaterial2dPlugin::::default()) + .add_systems(Startup, setup) + .add_systems(Update, change_active_editor_ui) + .add_systems(Update, change_active_editor_sprite) + // TODO: change_active_editor_mesh + .run(); +} diff --git a/src/lib.rs b/src/lib.rs index 0b3d479..0569ae5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod cursor; mod input; mod render; +mod shader; use std::{collections::VecDeque, path::PathBuf}; @@ -22,10 +23,13 @@ use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel}; use render::{ blink_cursor, freeze_cursor_blink, hide_inactive_or_readonly_cursor, hide_password_text, on_scale_factor_change, restore_password_text, restore_placeholder_text, set_initial_scale, - show_placeholder, CosmicPadding, CosmicRenderSet, CosmicWidgetSize, CursorBlinkTimer, - CursorVisibility, PasswordValues, SwashCacheState, + show_placeholder, CosmicPadding, CosmicWidgetSize, CursorBlinkTimer, CursorVisibility, + PasswordValues, SwashCacheState, }; +pub use render::CosmicRenderSet; +pub use shader::{add_cosmic_material, CosmicMaterial2d, CosmicMaterial2dPlugin}; + #[cfg(feature = "multicam")] #[derive(Component)] pub struct CosmicPrimaryCamera; diff --git a/src/shader.rs b/src/shader.rs new file mode 100644 index 0000000..9ce3aaf --- /dev/null +++ b/src/shader.rs @@ -0,0 +1,57 @@ +use std::{hash::Hash, marker::PhantomData}; + +use bevy::{ + prelude::*, + sprite::{Material2d, Material2dPlugin}, +}; + +use crate::{CosmicEditor, CosmicRenderSet, CosmicSource}; + +// TODO: document bindings here. Always expect color_texture bound to same place, show example. +// Allows shared shaders. Though API is like water so might be a job for later. +pub trait CosmicMaterial2d { + fn color_texture(&self) -> &Handle; + fn set_color_texture(&mut self, texture: &Handle) -> &mut Self; +} + +pub struct CosmicMaterial2dPlugin(PhantomData); + +impl Default for CosmicMaterial2dPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +// TODO: docs here, explain passthrough to Material2dPlugin +impl Plugin for CosmicMaterial2dPlugin +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + fn build(&self, app: &mut App) { + app.add_plugins(Material2dPlugin::::default()) + .add_systems( + PostUpdate, + add_cosmic_material::.after(CosmicRenderSet::Draw), + ); + } +} + +pub fn add_cosmic_material( + source_q: Query<&Handle, With>, + dest_q: Query<(&Handle, &CosmicSource), Without>, + mut materials: ResMut>, +) { + // TODO: do this once + for (dest_handle, source_entity) in dest_q.iter() { + if let Ok(source_handle) = source_q.get(source_entity.0) { + if let Some(dest_material) = materials.get_mut(dest_handle) { + if dest_material.color_texture() == source_handle { + // TODO: instead of all this looping find a way to check if handle is changed + // earlier in the chain + return; + } + dest_material.set_color_texture(source_handle); + } + } + } +}