1use crate::layout::{layout_tree, LayoutNode, Rect};
4use crate::renderer::RendererState;
5use crate::value::EvalContext;
6use crate::{compile, get_screen, Program};
7use std::path::PathBuf;
8use std::sync::mpsc;
9use std::time::Duration;
10use winit::application::ApplicationHandler;
11use winit::event::WindowEvent;
12use winit::event_loop::ActiveEventLoop;
13use winit::window::{Window, WindowId};
14
15pub struct App {
16 window: Option<Window>,
17 renderer: Option<RendererState>,
18 program: Option<Program>,
19 root_layout: Option<LayoutNode>,
20 source_path: Option<PathBuf>,
21 source_code: String,
22 screen_name: Option<String>,
23 reload_rx: Option<mpsc::Receiver<()>>,
24}
25
26impl App {
27 pub fn new(source_path: Option<PathBuf>, source_code: String, screen_name: Option<String>) -> Self {
28 Self {
29 window: None,
30 renderer: None,
31 program: None,
32 root_layout: None,
33 source_path,
34 source_code,
35 screen_name,
36 reload_rx: None,
37 }
38 }
39
40 fn load_and_layout(&mut self) {
41 let source = self.source_code.trim();
42 if source.is_empty() {
43 log::error!("No source code to parse");
44 return;
45 }
46 let path = self.source_path.as_ref().map(|p| p.as_path());
47 match compile(source, path, self.screen_name.as_deref()) {
48 Ok((program, _layout)) => {
49 self.program = Some(program);
50 self.rebuild_layout();
51 }
52 Err(e) => {
53 log::error!("Compile error: {}", e);
54 }
55 }
56 }
57
58 fn rebuild_layout(&mut self) {
59 let program = match &self.program {
60 Some(p) => p,
61 None => return,
62 };
63 let ctx = EvalContext::from_program(program);
64 let screen = get_screen(program, self.screen_name.as_deref());
65 let root_layout = match (screen, &self.renderer) {
66 (Some(screen), Some(renderer)) => {
67 let viewport_w = renderer.config.width as f32;
68 let viewport_h = renderer.config.height as f32;
69 let rect = Rect::new(0.0, 0.0, viewport_w, viewport_h);
70 match layout_tree(&ctx, &screen.body, rect) {
71 Ok(layout) => Some(layout),
72 Err(e) => {
73 log::error!("Layout error: {}", e);
74 None
75 }
76 }
77 }
78 _ => None,
79 };
80 self.root_layout = root_layout;
81 }
82
83 fn start_file_watcher(&mut self, path: &PathBuf) {
84 use notify::{RecommendedWatcher, RecursiveMode, Watcher, EventKind};
85 let (tx, rx) = mpsc::channel();
86 self.reload_rx = Some(rx);
87 let path = path.clone();
88 std::thread::spawn(move || {
89 let (notify_tx, notify_rx) = mpsc::channel::<notify::Result<notify::Event>>();
90 let mut watcher = match RecommendedWatcher::new(
91 move |res| { let _ = notify_tx.send(res); },
92 notify::Config::default(),
93 ) {
94 Ok(w) => w,
95 Err(e) => {
96 log::error!("File watcher: {}", e);
97 return;
98 }
99 };
100 if let Err(e) = watcher.watch(path.as_path(), RecursiveMode::NonRecursive) {
101 log::error!("Watch {}: {}", path.display(), e);
102 return;
103 }
104 while let Ok(event) = notify_rx.recv() {
106 if let Ok(event) = event {
107 if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) {
108 std::thread::sleep(Duration::from_millis(50));
109 let _ = tx.send(());
110 }
111 }
112 }
113 });
114 }
115}
116
117impl ApplicationHandler for App {
118 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
119 if self.window.is_some() {
120 return;
121 }
122 let attrs = winit::window::Window::default_attributes()
123 .with_title("Newt — Design Canvas")
124 .with_inner_size(winit::dpi::LogicalSize::new(960.0, 640.0));
125 let window = match event_loop.create_window(attrs) {
126 Ok(w) => w,
127 Err(e) => {
128 log::error!("Failed to create window: {}", e);
129 return;
130 }
131 };
132 let renderer = match pollster::block_on(RendererState::new(&window)) {
133 Ok(r) => r,
134 Err(e) => {
135 log::error!("Failed to create renderer: {}", e);
136 return;
137 }
138 };
139 self.window = Some(window);
140 self.renderer = Some(renderer);
141 self.load_and_layout();
142 if let Some(path) = self.source_path.clone() {
143 self.start_file_watcher(&path);
144 }
145 }
146
147 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
148 if let Some(ref rx) = self.reload_rx {
149 if rx.try_recv().is_ok() {
150 if let Some(ref path) = self.source_path {
151 if let Ok(code) = std::fs::read_to_string(path) {
152 self.source_code = code;
153 self.load_and_layout();
154 }
155 }
156 if let Some(ref window) = self.window {
157 window.request_redraw();
158 }
159 }
160 }
161 }
162
163 fn window_event(
164 &mut self,
165 event_loop: &ActiveEventLoop,
166 _id: WindowId,
167 event: WindowEvent,
168 ) {
169 match event {
170 WindowEvent::CloseRequested => {
171 event_loop.exit();
172 }
173 WindowEvent::Resized(size) => {
174 if let Some(ref mut r) = self.renderer {
175 r.resize(size.width, size.height);
176 self.rebuild_layout();
177 }
178 }
179 WindowEvent::RedrawRequested => {
180 if let (Some(renderer), Some(ref root)) = (&mut self.renderer, &self.root_layout) {
181 renderer.draw_layout(root);
182 if let Err(e) = renderer.render() {
183 log::error!("Render error: {:?}", e);
184 }
185 }
186 }
187 _ => {}
188 }
189 }
190}
191