1use crate::{
2 types::{ImageSourceV, SizeV, ThemeV},
3 widgets::{compile, EmptyW, GuiW},
4};
5use anyhow::{Context, Result};
6use arcstr::ArcStr;
7use graphix_compiler::expr::ExprId;
8use graphix_rt::{GXExt, GXHandle, Ref, TRef};
9use iced_core::mouse;
10use netidx::publisher::Value;
11use std::sync::Arc;
12use std::time::Instant;
13
14use tokio::try_join;
15use winit::window::{Window, WindowAttributes, WindowId};
16
17pub struct ResolvedWindow<X: GXExt> {
19 pub gx: GXHandle<X>,
20 pub title: TRef<X, String>,
21 pub size: TRef<X, SizeV>,
22 pub theme: TRef<X, ThemeV>,
23 pub icon: TRef<X, ImageSourceV>,
24 pub decoded_icon: Option<winit::window::Icon>,
25 pub content_ref: Ref<X>,
26 pub content: GuiW<X>,
27}
28
29impl<X: GXExt> ResolvedWindow<X> {
30 pub async fn compile(gx: GXHandle<X>, source: Value) -> Result<Self> {
32 let [(_, content), (_, icon), (_, size), (_, theme), (_, title)] =
33 source.cast_to::<[(ArcStr, u64); 5]>().context("window flds")?;
34 let (content_ref, icon, size, theme, title) = try_join! {
35 gx.compile_ref(content),
36 gx.compile_ref(icon),
37 gx.compile_ref(size),
38 gx.compile_ref(theme),
39 gx.compile_ref(title),
40 }?;
41 let compiled_content: GuiW<X> = match content_ref.last.as_ref() {
42 None => Box::new(EmptyW),
43 Some(v) => compile(gx.clone(), v.clone()).await.context("window content")?,
44 };
45 let icon = TRef::new(icon).context("window tref icon")?;
46 let decoded_icon =
47 icon.t.as_ref().and_then(|s: &ImageSourceV| match s.decode_icon() {
48 Ok(i) => i,
49 Err(e) => {
50 log::warn!("failed to decode window icon: {e}");
51 None
52 }
53 });
54 Ok(Self {
55 gx,
56 title: TRef::new(title).context("window tref title")?,
57 size: TRef::new(size).context("window tref size")?,
58 theme: TRef::new(theme).context("window tref theme")?,
59 icon,
60 decoded_icon,
61 content_ref,
62 content: compiled_content,
63 })
64 }
65
66 pub fn window_attrs(&self) -> WindowAttributes {
68 let title = self.title.t.as_ref().map(|t| t.as_str()).unwrap_or("Graphix");
69 let (w, h) = self
70 .size
71 .t
72 .as_ref()
73 .map(|sz| (sz.0.width, sz.0.height))
74 .unwrap_or((800.0, 600.0));
75 WindowAttributes::default()
76 .with_title(title)
77 .with_inner_size(winit::dpi::LogicalSize::new(w, h))
78 .with_window_icon(self.decoded_icon.clone())
79 }
80
81 pub fn into_tracked(
83 self,
84 window_ref: Ref<X>,
85 window: Arc<Window>,
86 ) -> TrackedWindow<X> {
87 TrackedWindow {
88 window_ref,
89 gx: self.gx,
90 window,
91 title: self.title,
92 size: self.size,
93 theme: self.theme,
94 icon: self.icon,
95 decoded_icon: self.decoded_icon,
96 content_ref: self.content_ref,
97 content: self.content,
98 cursor_position: iced_core::Point::ORIGIN,
99 last_mouse_interaction: mouse::Interaction::default(),
100 pending_events: Vec::new(),
101 needs_redraw: true,
102 last_set_size: None,
103 pending_resize: None,
104 resize_timer_armed: false,
105 last_render: Instant::now(),
106 }
107 }
108}
109
110pub struct TrackedWindow<X: GXExt> {
112 pub window_ref: Ref<X>,
113 pub gx: GXHandle<X>,
114 pub window: Arc<Window>,
115 pub title: TRef<X, String>,
116 pub size: TRef<X, SizeV>,
117 pub theme: TRef<X, ThemeV>,
118 pub icon: TRef<X, ImageSourceV>,
119 pub decoded_icon: Option<winit::window::Icon>,
120 pub content_ref: Ref<X>,
121 pub content: GuiW<X>,
122 pub cursor_position: iced_core::Point,
123 pub last_mouse_interaction: mouse::Interaction,
124 pub pending_events: Vec<iced_core::Event>,
125 pub needs_redraw: bool,
126 pub last_set_size: Option<SizeV>,
127 pub pending_resize: Option<(u32, u32, f64)>,
128 pub resize_timer_armed: bool,
133 pub last_render: Instant,
134}
135
136impl<X: GXExt> TrackedWindow<X> {
137 pub fn handle_update(
138 &mut self,
139 rt: &tokio::runtime::Handle,
140 id: ExprId,
141 v: &Value,
142 ) -> Result<()> {
143 if id == self.window_ref.id {
144 self.window_ref.last = Some(v.clone());
145 let resolved = rt
146 .block_on(ResolvedWindow::compile(self.gx.clone(), v.clone()))
147 .context("window ref recompile")?;
148 self.title = resolved.title;
149 self.size = resolved.size;
150 self.theme = resolved.theme;
151 self.icon = resolved.icon;
152 self.decoded_icon = resolved.decoded_icon;
153 self.content_ref = resolved.content_ref;
154 self.content = resolved.content;
155 if let Some(t) = self.title.t.as_ref() {
156 self.window.set_title(t);
157 }
158 if let Some(sz) = self.size.t.as_ref() {
159 let _ = self.window.request_inner_size(winit::dpi::LogicalSize::new(
160 sz.0.width,
161 sz.0.height,
162 ));
163 }
164 self.window.set_window_icon(self.decoded_icon.clone());
165 self.needs_redraw = true;
166 return Ok(());
167 }
168 let mut changed = false;
169 if let Some(t) = self.title.update(id, v).context("window update title")? {
170 self.window.set_title(t);
171 changed = true;
172 }
173 if let Some(sz) = self.size.update(id, v).context("window update size")? {
174 if self.last_set_size.take() != Some(*sz) {
175 let _ = self.window.request_inner_size(winit::dpi::LogicalSize::new(
176 sz.0.width,
177 sz.0.height,
178 ));
179 }
180 changed = true;
181 }
182 if self.theme.update(id, v).context("window update theme")?.is_some() {
183 changed = true;
184 }
185 if self.icon.update(id, v).context("window update icon")?.is_some() {
186 self.decoded_icon =
187 self.icon.t.as_ref().and_then(|s: &ImageSourceV| match s.decode_icon() {
188 Ok(i) => i,
189 Err(e) => {
190 log::warn!("failed to decode window icon: {e}");
191 None
192 }
193 });
194 self.window.set_window_icon(self.decoded_icon.clone());
195 changed = true;
196 }
197 if id == self.content_ref.id {
198 self.content_ref.last = Some(v.clone());
199 self.content = rt
200 .block_on(compile(self.gx.clone(), v.clone()))
201 .context("window content recompile")?;
202 changed = true;
203 }
204 changed |= self.content.handle_update(rt, id, v)?;
205 self.needs_redraw |= changed;
206 Ok(())
207 }
208
209 pub fn window_id(&self) -> WindowId {
210 self.window.id()
211 }
212
213 pub fn iced_theme(&self) -> crate::theme::GraphixTheme {
214 self.theme.t.as_ref().map(|t| t.0.clone()).unwrap_or(crate::theme::GraphixTheme {
215 inner: iced_core::Theme::Dark,
216 overrides: None,
217 })
218 }
219
220 pub fn push_event(&mut self, event: iced_core::Event) {
221 self.pending_events.push(event);
222 if !self.resize_timer_armed {
230 self.needs_redraw = true;
231 }
232 }
233
234 pub fn cursor(&self) -> mouse::Cursor {
235 mouse::Cursor::Available(self.cursor_position)
236 }
237}