summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs293
1 files changed, 293 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..d818b04
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,293 @@
+use egui_wgpu::{RendererOptions, ScreenDescriptor};
+use egui_winit::egui::{self, Context};
+use pixels::{Pixels, SurfaceTexture};
+use std::time::{Duration, Instant};
+use winit::{
+ application::ApplicationHandler,
+ event::{ElementState, MouseButton, WindowEvent},
+ event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
+ window::Window,
+};
+
+const WIDTH: u32 = 320;
+const HEIGHT: u32 = 240;
+const FPS: u64 = 60;
+const FRAME_DURATION: Duration = Duration::from_micros(1_000_000 / FPS);
+
+pub type Error = Box<dyn std::error::Error>;
+pub type Result<T> = std::result::Result<T, Error>;
+
+struct Clicked {
+ x: usize,
+ y: usize,
+ s: f64,
+}
+
+struct App {
+ // core
+ egui_renderer: Option<egui_wgpu::Renderer>,
+ egui_state: Option<egui_winit::State>,
+ egui_context: Option<egui::Context>,
+ window: Option<&'static Window>,
+ pixels: Option<Pixels<'static>>,
+ last_frame: Instant,
+
+ // input
+ last_mouse_pos: Option<(f64, f64)>,
+
+ // experiemnts
+ clicked: Vec<Clicked>,
+
+ // debug
+ red_level: u8,
+ green_level: u8,
+ blue_level: u8,
+}
+
+impl App {
+ fn set_px(f: &mut [u8], x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) {
+ let idx = ((y * WIDTH + x) * 4) as usize;
+ f[idx] = r;
+ f[idx + 1] = g;
+ f[idx + 2] = b;
+ f[idx + 3] = a;
+ }
+}
+
+impl Default for App {
+ fn default() -> Self {
+ Self {
+ egui_context: None,
+ egui_state: None,
+ egui_renderer: None,
+ window: None,
+ pixels: None,
+ last_frame: Instant::now(),
+
+ last_mouse_pos: Some((0.0, 0.0)),
+
+ clicked: Vec::new(),
+
+ red_level: 0xFF,
+ green_level: 0xFF,
+ blue_level: 0xFF,
+ }
+ }
+}
+
+impl ApplicationHandler for App {
+ fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+ let window = event_loop
+ .create_window(Window::default_attributes().with_title("pxs"))
+ .unwrap();
+
+ let size = window.inner_size();
+ let window_ref: &'static Window = Box::leak(Box::new(window));
+ let surface = SurfaceTexture::new(size.width, size.height, window_ref);
+
+ let pixels = Pixels::new(WIDTH, HEIGHT, surface).unwrap();
+
+ let egui_context = Context::default();
+ let egui_state = egui_winit::State::new(
+ egui_context.clone(),
+ egui_context.viewport_id(),
+ window_ref,
+ None,
+ None,
+ None,
+ );
+ let egui_renderer =
+ egui_wgpu::Renderer::new(pixels.device(), pixels.render_texture_format(), {
+ RendererOptions {
+ msaa_samples: 1,
+ ..RendererOptions::default()
+ }
+ });
+
+ self.egui_context = Some(egui_context);
+ self.egui_state = Some(egui_state);
+ self.egui_renderer = Some(egui_renderer);
+ self.window = Some(window_ref);
+ self.pixels = Some(pixels);
+ }
+
+ fn window_event(
+ &mut self,
+ event_loop: &ActiveEventLoop,
+ _: winit::window::WindowId,
+ event: WindowEvent,
+ ) {
+ if let Some(pixels) = &mut self.pixels
+ && let Some(window) = self.window
+ && let Some(egui_context) = &self.egui_context
+ && let Some(egui_state) = &mut self.egui_state
+ && let Some(egui_renderer) = &mut self.egui_renderer
+ {
+ // TODO how to use the response?
+ let _ = egui_state.on_window_event(window, &event);
+
+ match event {
+ WindowEvent::CursorMoved { position, .. } => {
+ self.last_mouse_pos = Some((position.x, position.y));
+ }
+ WindowEvent::MouseInput { state, button, .. } => {
+ if state == ElementState::Pressed
+ && button == MouseButton::Left
+ && let Some(last_mouse_pos) = self.last_mouse_pos
+ {
+ let coords = pixels
+ .window_pos_to_pixel((last_mouse_pos.0 as f32, last_mouse_pos.1 as f32))
+ .unwrap();
+ self.clicked.push(Clicked {
+ x: coords.0,
+ y: coords.1,
+ s: 25.0,
+ });
+ }
+ }
+ WindowEvent::Resized(size) => {
+ // Important: resize the surface when the window's size change
+ pixels.resize_surface(size.width, size.height).unwrap();
+ }
+ WindowEvent::CloseRequested => {
+ event_loop.exit();
+ }
+ WindowEvent::RedrawRequested => {
+ // pixels logic
+ let frame = pixels.frame_mut();
+ frame.fill(0);
+
+ for y in 0..HEIGHT {
+ for x in 0..WIDTH {
+ let active = x % 2 == 0
+ && y % 2 == 0
+ && self.clicked.iter().all(|c| {
+ (((x as i64) - (c.x as i64)).pow(2)
+ + ((y as i64) - (c.y as i64)).pow(2))
+ .isqrt() as f64
+ > c.s
+ });
+
+ Self::set_px(
+ frame,
+ x,
+ y,
+ if active { self.red_level } else { 0x00 },
+ if active { self.green_level } else { 0x00 },
+ if active { self.blue_level } else { 0x00 },
+ 0xFF,
+ );
+ }
+ }
+
+ for i in 0..self.clicked.len() {
+ self.clicked[i].s -= 0.3;
+ if self.clicked[i].s <= 0.0 {
+ self.clicked.remove(i);
+ break;
+ }
+ }
+
+ // egui logic
+ let raw_input = egui_state.take_egui_input(window);
+ let full_output = egui_context.run_ui(raw_input, |ui| {
+ ui.heading("Debug");
+ ui.label(
+ self.clicked
+ .iter()
+ .map(|c| format!("x={x},y={y},s={s}", x = c.x, y = c.y, s = c.s))
+ .collect::<Vec<String>>()
+ .join("\n"),
+ );
+ ui.add(egui::Slider::new(&mut self.red_level, 0..=120).text("Red"));
+ ui.add(egui::Slider::new(&mut self.green_level, 0..=120).text("Green"));
+ ui.add(egui::Slider::new(&mut self.blue_level, 0..=120).text("Blue"));
+ });
+
+ egui_state.handle_platform_output(window, full_output.platform_output);
+
+ let clipped_primitives =
+ egui_context.tessellate(full_output.shapes, full_output.pixels_per_point);
+
+ let pixels_per_point = full_output.pixels_per_point;
+
+ let size = window.inner_size();
+ let screen_descriptor = ScreenDescriptor {
+ size_in_pixels: [size.width, size.height],
+ pixels_per_point,
+ };
+
+ let queue = pixels.queue();
+ let device = pixels.device();
+
+ for (id, delta) in &full_output.textures_delta.set {
+ egui_renderer.update_texture(&device, &queue, *id, delta);
+ }
+
+ let _ = pixels.render_with(|encoder, render_target, context| {
+ // 1. pixels' own blit of the framebuffer to the surface
+ context.scaling_renderer.render(encoder, render_target);
+
+ // 2. prep egui's GPU-side buffers for this frame's draws
+ egui_renderer.update_buffers(
+ device,
+ &queue,
+ encoder,
+ &clipped_primitives,
+ &screen_descriptor,
+ );
+
+ // 3. open a pass on the same target, preserving the blit
+ let mut pass = encoder
+ .begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("egui pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: render_target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load, // keep the blit, don't clear
+ store: wgpu::StoreOp::Store,
+ },
+ depth_slice: None,
+ })],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ multiview_mask: None,
+ })
+ .forget_lifetime(); // bridge wgpu 30's pass-lifetime requirement
+
+ egui_renderer.render(&mut pass, &clipped_primitives, &screen_descriptor);
+ drop(pass);
+
+ Ok(())
+ });
+ }
+ _ => {}
+ }
+ }
+ }
+
+ fn about_to_wait(&mut self, _: &ActiveEventLoop) {
+ let now = Instant::now();
+ // Limit to 60 FPS
+ if now - self.last_frame >= FRAME_DURATION {
+ self.last_frame = now;
+ self.window
+ .expect("Bug - Window should exist")
+ .request_redraw();
+ }
+ }
+}
+
+fn main() -> Result<()> {
+ env_logger::init();
+
+ let event_loop = EventLoop::new()?;
+ event_loop.set_control_flow(ControlFlow::Poll);
+
+ let mut app = App::default();
+ event_loop.run_app(&mut app)?;
+
+ Ok(())
+}