diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-01-17 21:52:40 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-01-17 22:18:55 +0000 |
| commit | c9cee3cce7066389657909d4a10938ec16a28645 (patch) | |
| tree | f0b850be02f7c584fa9d89c5b223d1ececddf2a1 /src | |
| parent | add examples (diff) | |
| download | nodetoy-c9cee3cce7066389657909d4a10938ec16a28645.tar.gz nodetoy-c9cee3cce7066389657909d4a10938ec16a28645.zip | |
fix init on load, resizable preview
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 55 | ||||
| -rw-r--r-- | src/preview.rs | 109 |
2 files changed, 72 insertions, 92 deletions
diff --git a/src/main.rs b/src/main.rs index e9eb1e1..0e95c19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,7 +159,7 @@ impl SnarlViewer<AnyNode> for Viewer { pub struct App { snarl: Snarl<AnyNode>, viewer: Viewer, - preview: preview::Custom3d, + preview: preview::Preview, } lazy_static! { @@ -221,7 +221,7 @@ impl App { App { snarl, viewer: Viewer { dirty: true }, - preview: preview::Custom3d::new(cx).expect("Failed to init preview"), + preview: preview::Preview::new(cx).expect("Failed to init preview"), } } } @@ -297,28 +297,37 @@ impl eframe::App for App { }); }); - let mut shader: Option<String> = None; + let shader = if self.viewer.dirty { + let mut buf = String::new(); + match self.snarl.compile(&mut buf) { + Ok(()) => { + info!("{}", buf); + Some(buf) + } + Err(x) => { + error!("Error compiling: {}", x); + None + } + } + } else { + None + }; + + egui::SidePanel::right("preview").show(ctx, |ui| { + egui::Frame::canvas(ui.style()).show(ui, |ui| { + if self.preview.update(ui, shader) { + self.viewer.dirty = false; + } + }); + }); + egui::CentralPanel::default().show(ctx, |ui| { - let response = SnarlWidget::new() + SnarlWidget::new() .id(Id::new("snarl-demo")) .style(default_style()) .show(&mut self.snarl, &mut self.viewer, ui); - let _ = response.changed(); - if self.viewer.dirty { - self.viewer.dirty = false; - let mut buf = String::new(); - match self.snarl.compile(&mut buf) { - Ok(()) => { - info!("{}", buf); - shader = Some(buf); - } - Err(x) => { - error!("Error compiling: {}", x); - } - } - } - + // let _ = response.changed(); ui.input(|i| { if let Some(data) = i.raw.dropped_files.first().and_then(|f| f.bytes.as_ref()) { self.snarl = serde_json::from_slice(data).unwrap(); @@ -326,14 +335,6 @@ impl eframe::App for App { } }); }); - - egui::SidePanel::right("preview").show(ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - egui::Frame::canvas(ui.style()).show(ui, |ui| { - self.preview.custom_painting(ui, shader); - }); - }); - }); } fn save(&mut self, storage: &mut dyn eframe::Storage) { diff --git a/src/preview.rs b/src/preview.rs index f1d4820..5b27144 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -1,30 +1,20 @@ -#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps - -use std::num::NonZeroU64; - use eframe::{ egui_wgpu::wgpu::util::DeviceExt as _, egui_wgpu::{self, wgpu}, }; +use std::num::NonZeroU64; -pub struct Custom3d; +pub struct Preview { + ready: bool, +} -impl Custom3d { +impl Preview { pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> { - // Get the WGPU render state from the eframe creation context. This can also be retrieved - // from `eframe::Frame` when you don't have a `CreationContext` available. - let wgpu_render_state = cc.wgpu_render_state.as_ref()?; - - // Because the graphics pipeline must have the same lifetime as the egui render pass, - // instead of storing the pipeline in our `Custom3d` struct, we insert it into the - // `paint_callback_resources` type map, which is stored alongside the render pass. - let resources = &mut wgpu_render_state.renderer.write().callback_resources; - - let device = &wgpu_render_state.device; - let format: wgpu::TextureFormat = wgpu_render_state.target_format; + let state = cc.wgpu_render_state.as_ref()?; + let resources = &mut state.renderer.write().callback_resources; resources.insert(TriangleRenderResources::init_with_shader( - device, - format, + &state.device, + state.target_format, "#version 450 in vec2 in_uv; out vec4 out_color; @@ -33,32 +23,36 @@ void main() { out_color = vec4(1, 0, 1, 1.0); }", )); - resources.insert(format); - Some(Self) + Some(Self { ready: false }) + } + + pub fn update(&mut self, ui: &mut egui::Ui, frag_shader: Option<String>) -> bool { + ui.set_min_width(150.0); + let size = ui.available_size().min_elem(); + let (rect, _) = ui.allocate_exact_size(egui::Vec2::splat(size), egui::Sense::empty()); + + let have_shader = frag_shader.is_some(); + + ui.painter().add(egui_wgpu::Callback::new_paint_callback( + rect, + CustomTriangleCallback { + frag_shader, + time: ui.ctx().input(|i| i.time) as f32, + }, + )); + + ui.ctx().request_repaint(); + + if !self.ready { + self.ready = true; + false + } else { + have_shader + } } } -// Callbacks in egui_wgpu have 3 stages: -// * prepare (per callback impl) -// * finish_prepare (once) -// * paint (per callback impl) -// -// The prepare callback is called every frame before paint and is given access to the wgpu -// Device and Queue, which can be used, for instance, to update buffers and uniforms before -// rendering. -// If [`egui_wgpu::Renderer`] has [`egui_wgpu::FinishPrepareCallback`] registered, -// it will be called after all `prepare` callbacks have been called. -// You can use this to update any shared resources that need to be updated once per frame -// after all callbacks have been processed. -// -// On both prepare methods you can use the main `CommandEncoder` that is passed-in, -// return an arbitrary number of user-defined `CommandBuffer`s, or both. -// The main command buffer, as well as all user-defined ones, will be submitted together -// to the GPU in a single call. -// -// The paint callback is called after finish prepare and is given access to egui's main render pass, -// which can be used to issue draw commands. struct CustomTriangleCallback { frag_shader: Option<String>, time: f32, @@ -73,16 +67,15 @@ impl egui_wgpu::CallbackTrait for CustomTriangleCallback { _egui_encoder: &mut wgpu::CommandEncoder, resources: &mut egui_wgpu::CallbackResources, ) -> Vec<wgpu::CommandBuffer> { + let triangle_resources: &mut TriangleRenderResources = resources.get_mut().unwrap(); if let Some(code) = &self.frag_shader { - let format: wgpu::TextureFormat = *resources.get().unwrap(); - let triangle_resources: &mut TriangleRenderResources = resources.get_mut().unwrap(); - *triangle_resources = TriangleRenderResources::init_with_shader(device, format, code); - triangle_resources - } else { - let triangle_resources: &mut TriangleRenderResources = resources.get_mut().unwrap(); - triangle_resources + *triangle_resources = TriangleRenderResources::init_with_shader( + device, + triangle_resources.target_format, + code, + ); } - .prepare(device, queue, screen, self.time); + triangle_resources.prepare(device, queue, screen, self.time); Vec::new() } @@ -98,25 +91,10 @@ impl egui_wgpu::CallbackTrait for CustomTriangleCallback { } } -impl Custom3d { - pub fn custom_painting(&mut self, ui: &mut egui::Ui, frag_shader: Option<String>) { - let (rect, _) = ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); - - ui.painter().add(egui_wgpu::Callback::new_paint_callback( - rect, - CustomTriangleCallback { - frag_shader, - time: ui.ctx().input(|i| i.time) as f32, - }, - )); - - ui.ctx().request_repaint(); - } -} - struct TriangleRenderResources { pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, + target_format: wgpu::TextureFormat, uniform_buffer: wgpu::Buffer, } @@ -225,6 +203,7 @@ void main() { Self { pipeline, bind_group, + target_format, uniform_buffer, } } |
