Skip to main content

graphix_package_gui/
lib.rs

1#![doc(
2    html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3    html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5use async_trait::async_trait;
6use graphix_compiler::{
7    env::Env,
8    expr::{ExprId, ModPath},
9    typ::Type,
10};
11use graphix_package::CustomDisplay;
12use graphix_rt::{CompExp, GXExt, GXHandle};
13use log::error;
14use netidx::publisher::Value;
15use std::{marker::PhantomData, sync::LazyLock};
16use tokio::sync::oneshot;
17use triomphe::Arc;
18use types::SizeV;
19use winit::{event_loop::EventLoopProxy, window::WindowId};
20
21mod clipboard;
22mod convert;
23mod event_loop;
24mod render;
25pub(crate) mod theme;
26mod types;
27pub(crate) mod widgets;
28mod window;
29
30#[cfg(test)]
31mod test;
32
33pub(crate) enum ToGui {
34    Update(ExprId, Value),
35    ResizeTimer(WindowId, SizeV),
36    Stop(oneshot::Sender<()>),
37}
38
39struct Gui<X: GXExt> {
40    proxy: EventLoopProxy<ToGui>,
41    ph: PhantomData<X>,
42}
43
44impl<X: GXExt> Gui<X> {
45    async fn start(
46        gx: &GXHandle<X>,
47        _env: Env,
48        root: CompExp<X>,
49        stop: oneshot::Sender<()>,
50        run_on_main: graphix_package::MainThreadHandle,
51    ) -> Self {
52        let gx = gx.clone();
53        let (proxy_tx, proxy_rx) = oneshot::channel();
54        let rt_handle = tokio::runtime::Handle::current();
55        run_on_main
56            .run(Box::new(move || {
57                event_loop::run(gx, root, proxy_tx, stop, rt_handle);
58            }))
59            .expect("main thread receiver dropped");
60        let proxy = proxy_rx.await.expect("event loop failed to send proxy");
61        Self { proxy, ph: PhantomData }
62    }
63
64    fn update(&self, id: ExprId, v: Value) {
65        if self.proxy.send_event(ToGui::Update(id, v)).is_err() {
66            error!("could not send update because gui event loop closed")
67        }
68    }
69}
70
71static GUITYP: LazyLock<Type> = LazyLock::new(|| {
72    Type::Array(Arc::new(Type::ByRef(Arc::new(Type::Ref {
73        scope: ModPath::root(),
74        name: ModPath::from(["gui", "Window"]),
75        params: Arc::from_iter([]),
76    }))))
77});
78
79#[async_trait]
80impl<X: GXExt> CustomDisplay<X> for Gui<X> {
81    async fn clear(&mut self) {
82        let (tx, rx) = oneshot::channel::<()>();
83        let _ = self.proxy.send_event(ToGui::Stop(tx));
84        let _ = rx.await;
85    }
86
87    async fn process_update(&mut self, _env: &Env, id: ExprId, v: Value) {
88        self.update(id, v);
89    }
90}
91
92graphix_derive::defpackage! {
93    builtins => [
94        clipboard::ReadText,
95        clipboard::WriteText,
96        clipboard::ReadImage,
97        clipboard::WriteImage,
98        clipboard::ReadHtml,
99        clipboard::WriteHtml,
100        clipboard::ReadFiles,
101        clipboard::WriteFiles,
102        clipboard::Clear,
103    ],
104    is_custom => |gx, env, e| {
105        if let Some(typ) = e.typ.with_deref(|t| t.cloned())
106            && typ != Type::Bottom
107            && typ != Type::Any
108        {
109            GUITYP.contains(env, &typ).unwrap_or(false)
110        } else {
111            false
112        }
113    },
114    init_custom => |gx, env, stop, e, run_on_main| {
115        Ok(Box::new(Gui::<X>::start(gx, env.clone(), e, stop, run_on_main).await))
116    },
117}