Skip to main content

newter_compiler/
app.rs

1//! Application loop: winit window, file watch, and render.
2
3use 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            // Block on notify events — watcher stays alive as long as this thread runs
105            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