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 = icon
47 .t
48 .as_ref()
49 .and_then(|s: &ImageSourceV| match s.decode_icon() {
50 Ok(i) => i,
51 Err(e) => {
52 log::warn!("failed to decode window icon: {e}");
53 None
54 }
55 });
56 Ok(Self {
57 gx,
58 title: TRef::new(title).context("window tref title")?,
59 size: TRef::new(size).context("window tref size")?,
60 theme: TRef::new(theme).context("window tref theme")?,
61 icon,
62 decoded_icon,
63 content_ref,
64 content: compiled_content,
65 })
66 }
67
68 pub fn window_attrs(&self) -> WindowAttributes {
70 let title = self.title.t.as_ref().map(|t| t.as_str()).unwrap_or("Graphix");
71 let (w, h) = self
72 .size
73 .t
74 .as_ref()
75 .map(|sz| (sz.0.width, sz.0.height))
76 .unwrap_or((800.0, 600.0));
77 WindowAttributes::default()
78 .with_title(title)
79 .with_inner_size(winit::dpi::LogicalSize::new(w, h))
80 .with_window_icon(self.decoded_icon.clone())
81 }
82
83 pub fn into_tracked(
85 self,
86 window_ref: Ref<X>,
87 window: Arc<Window>,
88 ) -> TrackedWindow<X> {
89 TrackedWindow {
90 window_ref,
91 gx: self.gx,
92 window,
93 title: self.title,
94 size: self.size,
95 theme: self.theme,
96 icon: self.icon,
97 decoded_icon: self.decoded_icon,
98 content_ref: self.content_ref,
99 content: self.content,
100 cursor_position: iced_core::Point::ORIGIN,
101 last_mouse_interaction: mouse::Interaction::default(),
102 pending_events: Vec::new(),
103 needs_redraw: true,
104 last_set_size: None,
105 pending_resize: None,
106 last_render: Instant::now(),
107 }
108 }
109}
110
111pub struct TrackedWindow<X: GXExt> {
113 pub window_ref: Ref<X>,
114 pub gx: GXHandle<X>,
115 pub window: Arc<Window>,
116 pub title: TRef<X, String>,
117 pub size: TRef<X, SizeV>,
118 pub theme: TRef<X, ThemeV>,
119 pub icon: TRef<X, ImageSourceV>,
120 pub decoded_icon: Option<winit::window::Icon>,
121 pub content_ref: Ref<X>,
122 pub content: GuiW<X>,
123 pub cursor_position: iced_core::Point,
124 pub last_mouse_interaction: mouse::Interaction,
125 pub pending_events: Vec<iced_core::Event>,
126 pub needs_redraw: bool,
127 pub last_set_size: Option<SizeV>,
128 pub pending_resize: Option<(u32, u32, f64)>,
129 pub last_render: Instant,
130}
131
132impl<X: GXExt> TrackedWindow<X> {
133 pub fn handle_update(
134 &mut self,
135 rt: &tokio::runtime::Handle,
136 id: ExprId,
137 v: &Value,
138 ) -> Result<()> {
139 if id == self.window_ref.id {
140 self.window_ref.last = Some(v.clone());
141 let resolved = rt
142 .block_on(ResolvedWindow::compile(self.gx.clone(), v.clone()))
143 .context("window ref recompile")?;
144 self.title = resolved.title;
145 self.size = resolved.size;
146 self.theme = resolved.theme;
147 self.icon = resolved.icon;
148 self.decoded_icon = resolved.decoded_icon;
149 self.content_ref = resolved.content_ref;
150 self.content = resolved.content;
151 if let Some(t) = self.title.t.as_ref() {
152 self.window.set_title(t);
153 }
154 if let Some(sz) = self.size.t.as_ref() {
155 let _ = self.window.request_inner_size(winit::dpi::LogicalSize::new(
156 sz.0.width,
157 sz.0.height,
158 ));
159 }
160 self.window.set_window_icon(self.decoded_icon.clone());
161 self.needs_redraw = true;
162 return Ok(());
163 }
164 let mut changed = false;
165 if let Some(t) = self.title.update(id, v).context("window update title")? {
166 self.window.set_title(t);
167 changed = true;
168 }
169 if let Some(sz) = self.size.update(id, v).context("window update size")? {
170 if self.last_set_size.take() != Some(*sz) {
171 let _ = self.window.request_inner_size(winit::dpi::LogicalSize::new(
172 sz.0.width,
173 sz.0.height,
174 ));
175 }
176 changed = true;
177 }
178 if self.theme.update(id, v).context("window update theme")?.is_some() {
179 changed = true;
180 }
181 if self.icon.update(id, v).context("window update icon")?.is_some() {
182 self.decoded_icon = self.icon.t.as_ref().and_then(
183 |s: &ImageSourceV| match s.decode_icon() {
184 Ok(i) => i,
185 Err(e) => {
186 log::warn!("failed to decode window icon: {e}");
187 None
188 }
189 },
190 );
191 self.window.set_window_icon(self.decoded_icon.clone());
192 changed = true;
193 }
194 if id == self.content_ref.id {
195 self.content_ref.last = Some(v.clone());
196 self.content = rt
197 .block_on(compile(self.gx.clone(), v.clone()))
198 .context("window content recompile")?;
199 changed = true;
200 }
201 changed |= self.content.handle_update(rt, id, v)?;
202 self.needs_redraw |= changed;
203 Ok(())
204 }
205
206 pub fn window_id(&self) -> WindowId {
207 self.window.id()
208 }
209
210 pub fn iced_theme(&self) -> crate::theme::GraphixTheme {
211 self.theme.t.as_ref().map(|t| t.0.clone()).unwrap_or(crate::theme::GraphixTheme {
212 inner: iced_core::Theme::Dark,
213 overrides: None,
214 })
215 }
216
217 pub fn push_event(&mut self, event: iced_core::Event) {
218 self.pending_events.push(event);
219 self.needs_redraw = true;
220 }
221
222 pub fn editor_action(
223 &mut self,
224 id: ExprId,
225 action: &iced_widget::text_editor::Action,
226 ) -> Option<(graphix_rt::CallableId, Value)> {
227 self.content.editor_action(id, action)
228 }
229
230 pub fn cursor(&self) -> mouse::Cursor {
231 mouse::Cursor::Available(self.cursor_position)
232 }
233}