Skip to content

Commit

Permalink
simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
jacob-pro committed Dec 29, 2023
1 parent b891eeb commit be46ea6
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 300 deletions.
13 changes: 0 additions & 13 deletions app/src/gui/epi.rs

This file was deleted.

294 changes: 292 additions & 2 deletions app/src/gui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,292 @@
pub mod epi;
pub mod wgpu_app;
use crate::common::APP_NAME;
use crate::UserEvent;
use egui_winit::egui;
use egui_winit::egui::Visuals;
use egui_winit::winit;
use egui_winit::winit::event::{Event, WindowEvent};
use std::sync::Arc;
use std::time::Instant;
use winit::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};


/// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
pub trait App {
/// Called each time the UI needs repainting, which may be many times per second.
///
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
///
/// The [`egui::Context`] can be cloned and saved if you like.
///
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
fn update(&mut self, ctx: &egui::Context);
}

#[derive(Debug)]
pub enum NextPaint {
/// Wait for an event
Wait,

/// Causes a synchronous repaint inside the event handler. This should only
/// be used in special situations if the window must be repainted while
/// handling a specific event. This occurs on Windows when handling resizes.
///
/// `RepaintNow` creates a new frame synchronously, and should therefore
/// only be used for extremely urgent repaints.
RepaintNow,

/// Queues a repaint for once the event loop handles its next redraw. Exists
/// so that multiple input events can be handled in one frame. Does not
/// cause any delay like `RepaintNow`.
RepaintNext,

/// Repaint at a particular time
RepaintAt(Instant),

/// Exit the event loop
Exit,
}

struct WgpuWinitRunning {
painter: egui_wgpu::winit::Painter,
app: Box<dyn App>,
window: winit::window::Window,
follow_system_theme: bool,
egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
egui_winit: egui_winit::State,
}

impl Drop for WgpuWinitRunning {
fn drop(&mut self) {
self.painter.destroy();
}
}

pub type AppCreator = Box<dyn Fn() -> Box<dyn App>>;

pub struct WgpuWinitApp {
repaint_proxy: Arc<std::sync::Mutex<EventLoopProxy<UserEvent>>>,
app: AppCreator,
running: Option<WgpuWinitRunning>,
}

impl WgpuWinitApp {
pub fn new(event_loop: &EventLoop<UserEvent>, app: AppCreator) -> Self {
Self {
repaint_proxy: Arc::new(std::sync::Mutex::new(event_loop.create_proxy())),
running: None,
app,
}
}

pub fn launch_window(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
) -> anyhow::Result<NextPaint> {
let window = winit::window::WindowBuilder::new()
.with_decorations(true)
.with_resizable(true)
.with_transparent(false)
.with_title(APP_NAME)
.with_inner_size(winit::dpi::PhysicalSize {
width: 800,
height: 600,
})
.build(&event_loop)?;
window.set_ime_allowed(true);

let mut painter =
egui_wgpu::winit::Painter::new(egui_wgpu::WgpuConfiguration::default(), 1, None, false);

pollster::block_on(painter.set_window(Some(&window)))?;

let mut egui_winit = egui_winit::State::new(event_loop);

let max_texture_side = painter.max_texture_side().unwrap_or(2048);
egui_winit.set_max_texture_side(max_texture_side);

let native_pixels_per_point = window.scale_factor() as f32;
egui_winit.set_pixels_per_point(native_pixels_per_point);

let egui_ctx = egui::Context::default();

let system_theme = window.theme();
egui_ctx.set_visuals(
system_theme
.map(visuals_from_winit_theme)
.unwrap_or(Visuals::light()),
);

let event_loop_proxy = self.repaint_proxy.clone();
egui_ctx.set_request_repaint_callback(move |info| {
let when = Instant::now() + info.after;
let frame_nr = info.current_frame_nr;
event_loop_proxy
.lock()
.unwrap()
.send_event(UserEvent::RequestRepaint { when, frame_nr })
.ok();
});

self.running = Some(WgpuWinitRunning {
painter,
app: (self.app)(),
window,
follow_system_theme: system_theme.is_some(),
egui_ctx,
pending_full_output: Default::default(),
egui_winit,
});

Ok(NextPaint::RepaintNow)
}

pub fn frame_nr(&self) -> u64 {
self.running.as_ref().map_or(0, |r| r.egui_ctx.frame_nr())
}

pub fn window(&self) -> Option<&winit::window::Window> {
self.running.as_ref().map(|r| &r.window)
}

pub fn paint(&mut self) -> NextPaint {
if let Some(running) = &mut self.running {
let raw_input = running.egui_winit.take_egui_input(&running.window);

// Run user code:
let full_output = running.egui_ctx.run(raw_input, |egui_ctx| {
running.app.update(egui_ctx);
});

running.pending_full_output.append(full_output);
let full_output = std::mem::take(&mut running.pending_full_output);

let egui::FullOutput {
platform_output,
repaint_after,
textures_delta,
shapes,
} = full_output;

running.egui_winit.handle_platform_output(
&running.window,
&running.egui_ctx,
platform_output,
);

let clipped_primitives = { running.egui_ctx.tessellate(shapes) };

let clear_color =
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).to_normalized_gamma_f32();

running.painter.paint_and_update_textures(
running.egui_ctx.pixels_per_point(),
clear_color,
&clipped_primitives,
&textures_delta,
false,
);

if repaint_after.is_zero() {
NextPaint::RepaintNext
} else if let Some(repaint_after_instant) = Instant::now().checked_add(repaint_after) {
NextPaint::RepaintAt(repaint_after_instant)
} else {
NextPaint::Wait
}
} else {
NextPaint::Wait
}
}

pub fn on_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event: &Event<'_, UserEvent>,
) -> anyhow::Result<NextPaint> {
Ok(match event {
// When the window is closed, we destroy the Window
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
self.running = None;
NextPaint::Wait
}

Event::Resumed if self.running.is_none() => self.launch_window(event_loop)?,

Event::WindowEvent { event, .. } => {
if let Some(running) = &mut self.running {
// On Windows, if a window is resized by the user, it should repaint synchronously, inside the
// event handler.
//
// If this is not done, the compositor will assume that the window does not want to redraw,
// and continue ahead.
//
// In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver
// new frames to the compositor in time.
//
// The flickering is technically glutin or glow's fault, but we should be responding properly
// to resizes anyway, as doing so avoids dropping frames.
//
// See: https://github.com/emilk/egui/issues/903
let mut repaint_asap = false;

match &event {
WindowEvent::Resized(physical_size) => {
repaint_asap = true;

// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
// See: https://github.com/rust-windowing/winit/issues/208
// This solves an issue where the app would panic when minimizing on Windows.
if physical_size.width > 0 && physical_size.height > 0 {
running
.painter
.on_window_resized(physical_size.width, physical_size.height);
}
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
repaint_asap = true;
running
.painter
.on_window_resized(new_inner_size.width, new_inner_size.height);
}
WindowEvent::CloseRequested => {
log::debug!("Received WindowEvent::CloseRequested");
return Ok(NextPaint::Exit);
}
WindowEvent::ThemeChanged(winit_theme) if running.follow_system_theme => {
let visuals = visuals_from_winit_theme(*winit_theme);
running.egui_ctx.set_visuals(visuals);
}
_ => {}
};

let event_response = running.egui_winit.on_event(&running.egui_ctx, event);

if event_response.repaint {
if repaint_asap {
NextPaint::RepaintNow
} else {
NextPaint::RepaintNext
}
} else {
NextPaint::Wait
}
} else {
NextPaint::Wait
}
}

_ => NextPaint::Wait,
})
}
}

fn visuals_from_winit_theme(theme: winit::window::Theme) -> Visuals {
match theme {
winit::window::Theme::Dark => Visuals::dark(),
winit::window::Theme::Light => Visuals::light(),
}
}
Loading

0 comments on commit be46ea6

Please sign in to comment.