summaryrefslogtreecommitdiff
path: root/src/gtk.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/gtk.rs')
-rw-r--r--src/gtk.rs171
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: &gtk::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())
+}