use eframe::{EframePumpStatus, UserEvent, egui}; use std::{cell::Cell, io, os::fd::AsRawFd as _, rc::Rc, time::Duration}; use tokio::task::LocalSet; use winit::event_loop::{ControlFlow, EventLoop}; pub fn run() -> io::Result<()> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; let mut eventloop = EventLoop::::with_user_event().build().unwrap(); eventloop.set_control_flow(ControlFlow::Poll); let mut winit_app = eframe::create_native( "External Eventloop Application", options, Box::new(|_| Ok(Box::::default())), &eventloop, ); let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap(); let local = LocalSet::new(); local.block_on(&rt, async { let eventloop_fd = tokio::io::unix::AsyncFd::new(eventloop.as_raw_fd())?; let mut control_flow = ControlFlow::Poll; loop { let mut guard = match control_flow { ControlFlow::Poll => None, ControlFlow::Wait => Some(eventloop_fd.readable().await?), ControlFlow::WaitUntil(deadline) => { tokio::time::timeout_at(deadline.into(), eventloop_fd.readable()) .await .ok() .transpose()? } }; match winit_app.pump_eframe_app(&mut eventloop, None) { EframePumpStatus::Continue(next) => control_flow = next, EframePumpStatus::Exit(code) => { log::info!("exit code: {code}"); break; } } if let Some(mut guard) = guard.take() { guard.clear_ready(); } } Ok::<_, io::Error>(()) }) } struct MyApp { value: Rc>, spin: bool, blinky: bool, } impl Default for MyApp { fn default() -> Self { Self { value: Rc::new(Cell::new(42)), spin: false, blinky: false, } } } impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("My External Eventloop Application"); ui.horizontal(|ui| { if ui.button("Increment Now").clicked() { self.value.set(self.value.get() + 1); } if ui.button("Increment Later").clicked() { let value = self.value.clone(); let ctx = ctx.clone(); tokio::task::spawn_local(async move { tokio::time::sleep(Duration::from_secs(1)).await; value.set(value.get() + 1); ctx.request_repaint(); }); } }); ui.label(format!("Value: {}", self.value.get())); if ui.button("Toggle Spinner").clicked() { self.spin = !self.spin; } if ui.button("Toggle Blinky").clicked() { self.blinky = !self.blinky; } if self.spin { ui.spinner(); } if self.blinky { let now = ui.ctx().input(|i| i.time); let blink = now % 1.0 < 0.5; egui::Frame::new() .inner_margin(3) .corner_radius(5) .fill(if blink { egui::Color32::RED } else { egui::Color32::TRANSPARENT }) .show(ui, |ui| { ui.label("Blinky!"); }); ctx.request_repaint_after_secs((0.5 - (now % 0.5)) as f32); } }); } }