diff options
Diffstat (limited to 'src/gtk.rs')
| -rw-r--r-- | src/gtk.rs | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/gtk.rs b/src/gtk.rs new file mode 100644 index 0000000..94fb5f1 --- /dev/null +++ b/src/gtk.rs @@ -0,0 +1,171 @@ +use anyhow::{anyhow, Result}; +use gtk4::{self as gtk, glib, cairo}; +use gtk4::prelude::*; +use glib::{clone, spawn_future_local}; +use std::thread; +use std::sync::{Arc, Mutex}; +use crate::sync::Signal; +use std::future::poll_fn; +use std::task::Poll; + +pub struct FbPool{ + size: usize, + pool: Vec<cairo::ImageSurfaceDataOwned>, +} +impl FbPool{ + pub fn new(size: usize) -> Self{ + FbPool{ + size, + pool: Vec::with_capacity(size), + } + } + pub fn put(&mut self, buf: cairo::ImageSurfaceDataOwned){ + if self.pool.len() < self.size{ + self.pool.push(buf); + } + } + pub fn get(&mut self, w: usize, h: usize) -> Result<cairo::ImageSurface>{ + while let Some(i) = self.pool.pop(){ + let i = i.into_inner(); + if i.width() as usize == w && i.height() as usize == h{ + return Ok(i); + } + } + Ok(cairo::ImageSurface::create( + cairo::Format::Rgb24, + w.try_into()?, h.try_into()?)?) + } +} +pub trait FbSourceOnce{ + fn get(self, w: usize, h: usize) -> Result<cairo::ImageSurface>; +} +impl FbSourceOnce for &Mutex<FbPool>{ + fn get(self, w: usize, h: usize) -> Result<cairo::ImageSurface>{ + self.lock().map_err(|e| anyhow!("{}", e))?.get(w, h) + } +} + +pub struct Packet<T>{ + pub image: cairo::ImageSurfaceDataOwned, + pub attr: T, +} +pub trait Overray: Send + 'static{} +pub trait Source: Send + 'static{ + type Attr: Overray; + fn next(&mut self, fbpool: impl FbSourceOnce) -> Result<Packet<Self::Attr>>; +} +impl Overray for (){} + +struct AppState<T>{ + next: Mutex<Option<Packet<T>>>, + update: Mutex<Signal>, + abort: Mutex<Signal>, + fbpool: Mutex<FbPool>, +} + +fn sourcing_loop<Attr: Overray>( + apps: &AppState<Attr>, + src: &mut impl Source<Attr=Attr> +) -> Result<()>{ + loop{ + let p = src.next(&apps.fbpool)?; + let old = apps.next.lock() + .map_err(|e| anyhow!("{}", e))? + .replace(p); + apps.update.lock().map_err(|e| anyhow!("{}", e))?.wake(); + if let Some(old) = old{ + apps.fbpool.lock() + .map_err(|e| anyhow!("{}", e))? + .put(old.image); + } + } +} + +fn activate<Attr: Overray>(app: >k::Application, apps: Arc<AppState<Attr>>){ + let draw = gtk::DrawingArea::new(); + let mut frame_cache: Option<cairo::ImageSurface> = None; + draw.set_draw_func(clone!{ + #[strong] apps, + move |_draw, ctx, canvas_w, canvas_h|{ + ctx.set_source_rgb(0., 0., 0.); + ctx.paint().unwrap(); + + if let Some(newfb) = apps.next.lock().unwrap().take(){ + if let Some(lastframe) = frame_cache.take(){ + apps.fbpool.lock().unwrap() + .put(lastframe.take_data().unwrap()); + } + frame_cache = Some(newfb.image.into_inner()); + } + if let Some(image) = frame_cache.clone(){ + let ipat = cairo::SurfacePattern::create(&image); + let scale = ((canvas_w as f64) / (image.width() as f64)).min( + (canvas_h as f64) / (image.height() as f64)); + ctx.scale(scale, scale); + ctx.set_source(&ipat).unwrap(); + ctx.paint().unwrap(); + } + } + }); + spawn_future_local(poll_fn(clone!{ + #[strong] apps, + #[strong] draw, + move |ctx|{ + loop{ + match apps.update.lock().unwrap().poll(ctx){ + Poll::Ready(_) => { + draw.queue_draw(); + }, + pending => return pending, + } + } + } + })); + spawn_future_local(poll_fn(clone!{ + #[strong] apps, + #[strong] app, + move |ctx|{ + loop{ + match apps.abort.lock().unwrap().poll(ctx){ + Poll::Ready(_) => { + app.quit(); + }, + pending => return pending, + } + } + } + })); + + let win = gtk::ApplicationWindow::builder() + .application(app) + .child(&draw) + .build(); + win.present(); +} + +pub fn main(src: impl Source + 'static) -> Result<glib::ExitCode>{ + let apps = Arc::new(AppState{ + next: Mutex::new(None), + update: Mutex::new(Signal::new()), + abort: Mutex::new(Signal::new()), + fbpool: Mutex::new(FbPool::new(4)), + }); + + thread::spawn(clone!{ + #[strong] apps, + move ||{ + let mut src = src; + let res = sourcing_loop(&apps, &mut src); + apps.abort.lock().unwrap().wake(); + res.unwrap(); + } + }); + + let app = gtk::Application::builder() + .build(); + app.connect_activate(clone!{ + #[strong] apps, + move |app| activate(app, apps.clone()) + }); + Ok(app.run()) +} |
