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 last_render: Instant::now(),
105 }
106 }
107}
108
109pub struct TrackedWindow<X: GXExt> {
111 pub window_ref: Ref<X>,
112 pub gx: GXHandle<X>,
113 pub window: Arc<Window>,
114 pub title: TRef<X, String>,
115 pub size: TRef<X, SizeV>,
116 pub theme: TRef<X, ThemeV>,
117 pub icon: TRef<X, ImageSourceV>,
118 pub decoded_icon: Option<winit::window::Icon>,
119 pub content_ref: Ref<X>,
120 pub content: GuiW<X>,
121 pub cursor_position: iced_core::Point,
122 pub last_mouse_interaction: mouse::Interaction,
123 pub pending_events: Vec<iced_core::Event>,
124 pub needs_redraw: bool,
125 pub last_set_size: Option<SizeV>,
126 pub pending_resize: Option<(u32, u32, f64)>,
127 pub last_render: Instant,
128}
129
130impl<X: GXExt> TrackedWindow<X> {
131 pub fn handle_update(
132 &mut self,
133 rt: &tokio::runtime::Handle,
134 id: ExprId,
135 v: &Value,
136 ) -> Result<()> {
137 if id == self.window_ref.id {
138 self.window_ref.last = Some(v.clone());
139 let resolved = rt
140 .block_on(ResolvedWindow::compile(self.gx.clone(), v.clone()))
141 .context("window ref recompile")?;
142 self.title = resolved.title;
143 self.size = resolved.size;
144 self.theme = resolved.theme;
145 self.icon = resolved.icon;
146 self.decoded_icon = resolved.decoded_icon;
147 self.content_ref = resolved.content_ref;
148 self.content = resolved.content;
149 if let Some(t) = self.title.t.as_ref() {
150 self.window.set_title(t);
151 }
152 if let Some(sz) = self.size.t.as_ref() {
153 let _ = self.window.request_inner_size(winit::dpi::LogicalSize::new(
154 sz.0.width,
155 sz.0.height,
156 ));
157 }
158 self.window.set_window_icon(self.decoded_icon.clone());
159 self.needs_redraw = true;
160 return Ok(());
161 }
162 let mut changed = false;
163 if let Some(t) = self.title.update(id, v).context("window update title")? {
164 self.window.set_title(t);
165 changed = true;
166 }
167 if let Some(sz) = self.size.update(id, v).context("window update size")? {
168 if self.last_set_size.take() != Some(*sz) {
169 let _ = self.window.request_inner_size(winit::dpi::LogicalSize::new(
170 sz.0.width,
171 sz.0.height,
172 ));
173 }
174 changed = true;
175 }
176 if self.theme.update(id, v).context("window update theme")?.is_some() {
177 changed = true;
178 }
179 if self.icon.update(id, v).context("window update icon")?.is_some() {
180 self.decoded_icon =
181 self.icon.t.as_ref().and_then(|s: &ImageSourceV| match s.decode_icon() {
182 Ok(i) => i,
183 Err(e) => {
184 log::warn!("failed to decode window icon: {e}");
185 None
186 }
187 });
188 self.window.set_window_icon(self.decoded_icon.clone());
189 changed = true;
190 }
191 if id == self.content_ref.id {
192 self.content_ref.last = Some(v.clone());
193 self.content = rt
194 .block_on(compile(self.gx.clone(), v.clone()))
195 .context("window content recompile")?;
196 changed = true;
197 }
198 changed |= self.content.handle_update(rt, id, v)?;
199 self.needs_redraw |= changed;
200 Ok(())
201 }
202
203 pub fn window_id(&self) -> WindowId {
204 self.window.id()
205 }
206
207 pub fn iced_theme(&self) -> crate::theme::GraphixTheme {
208 self.theme.t.as_ref().map(|t| t.0.clone()).unwrap_or(crate::theme::GraphixTheme {
209 inner: iced_core::Theme::Dark,
210 overrides: None,
211 })
212 }
213
214 pub fn push_event(&mut self, event: iced_core::Event) {
215 self.pending_events.push(event);
216 self.needs_redraw = true;
217 }
218
219 pub fn editor_action(
220 &mut self,
221 id: ExprId,
222 action: &iced_widget::text_editor::Action,
223 ) -> Option<(graphix_rt::CallableId, Value)> {
224 self.content.editor_action(id, action)
225 }
226
227 pub fn cursor(&self) -> mouse::Cursor {
228 mouse::Cursor::Available(self.cursor_position)
229 }
230}