Skip to main content

foundry_runtime/
lib.rs

1pub mod codegen;
2pub mod css;
3pub mod events;
4pub mod html;
5pub mod js;
6pub mod layout;
7pub mod render;
8pub mod scene;
9pub mod text;
10
11#[cfg(test)]
12mod test_gpu;
13
14use std::sync::Arc;
15use winit::application::ApplicationHandler;
16use winit::event::{ElementState, MouseButton, WindowEvent};
17use winit::event_loop::{ActiveEventLoop, EventLoop};
18use winit::window::{Window, WindowAttributes, WindowId};
19
20/// Entry point for compiled Foundry binaries -- runs embedded HTML as a native GPU window.
21pub fn run_embedded(html: &str, title: &str) {
22    env_logger::init();
23
24    let (scene_graph, scripts) = parse_embedded(html);
25
26    println!("foundry: {} nodes", scene_graph.node_count());
27
28    let event_loop = EventLoop::new().expect("failed to create event loop");
29    let mut app = EmbeddedApp {
30        title: title.to_string(),
31        window: None,
32        renderer: None,
33        text_engine: None,
34        scene: scene_graph,
35        scripts,
36        layout_engine: layout::LayoutEngine::new(),
37        event_system: events::EventSystem::new(),
38        js_engine: None,
39        needs_relayout: true,
40        scripts_executed: false,
41        cursor_pos: (0.0, 0.0),
42    };
43
44    event_loop.run_app(&mut app).expect("event loop failed");
45}
46
47fn parse_embedded(html_source: &str) -> (scene::SceneGraph, Vec<String>) {
48    let (mut scene_graph, inline_styles, inline_scripts, _, _) = html::parse_html_full(html_source);
49
50    for style_text in &inline_styles {
51        let (rules, kfs) = css::parse_stylesheet_with_keyframes(style_text);
52        scene_graph.keyframes.extend(kfs);
53        css::apply_styles(&mut scene_graph, &rules);
54    }
55
56    (scene_graph, inline_scripts)
57}
58
59struct EmbeddedApp {
60    title: String,
61    window: Option<Arc<Window>>,
62    renderer: Option<render::Renderer>,
63    text_engine: Option<text::TextEngine>,
64    scene: scene::SceneGraph,
65    scripts: Vec<String>,
66    layout_engine: layout::LayoutEngine,
67    event_system: events::EventSystem,
68    js_engine: Option<js::JsEngine>,
69    needs_relayout: bool,
70    scripts_executed: bool,
71    cursor_pos: (f32, f32),
72}
73
74impl ApplicationHandler for EmbeddedApp {
75    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
76        if self.window.is_some() {
77            return;
78        }
79
80        let window = Arc::new(
81            event_loop
82                .create_window(
83                    WindowAttributes::default()
84                        .with_title(&self.title)
85                        .with_inner_size(winit::dpi::LogicalSize::new(1024.0, 768.0)),
86                )
87                .expect("failed to create window"),
88        );
89
90        let renderer = pollster::block_on(render::Renderer::new(window.clone()));
91        let te = text::TextEngine::new(&renderer.device, &renderer.queue, renderer.format);
92
93        self.window = Some(window.clone());
94        self.text_engine = Some(te);
95        self.renderer = Some(renderer);
96        self.js_engine = Some(js::JsEngine::new());
97        self.needs_relayout = true;
98        window.request_redraw();
99    }
100
101    fn window_event(
102        &mut self,
103        event_loop: &ActiveEventLoop,
104        _window_id: WindowId,
105        event: WindowEvent,
106    ) {
107        match event {
108            WindowEvent::CloseRequested => {
109                event_loop.exit();
110            }
111            WindowEvent::Resized(size) => {
112                if let Some(renderer) = &mut self.renderer {
113                    renderer.resize(size.width, size.height);
114                    self.needs_relayout = true;
115                    if let Some(window) = &self.window {
116                        window.request_redraw();
117                    }
118                }
119            }
120            WindowEvent::MouseInput {
121                state: ElementState::Pressed,
122                button: MouseButton::Left,
123                ..
124            } => {
125                let (x, y) = self.cursor_pos;
126                let app_event = events::AppEvent::Click(x, y);
127                let results = self.event_system.handle(&app_event, &mut self.scene);
128                if !results.is_empty() {
129                    self.execute_handlers(&results);
130                    if let Some(window) = &self.window {
131                        window.request_redraw();
132                    }
133                }
134            }
135            WindowEvent::CursorMoved { position, .. } => {
136                self.cursor_pos = (position.x as f32, position.y as f32);
137                let old_hover = self.event_system.hover_node;
138                let app_event = events::AppEvent::MouseMove(position.x as f32, position.y as f32);
139                let results = self.event_system.handle(&app_event, &mut self.scene);
140                if !results.is_empty() {
141                    self.execute_handlers(&results);
142                }
143                if self.event_system.hover_node != old_hover {
144                    self.needs_relayout = true;
145                    if let Some(window) = &self.window {
146                        window.request_redraw();
147                    }
148                }
149            }
150            WindowEvent::MouseWheel { delta, .. } => {
151                let (dx, dy) = match delta {
152                    winit::event::MouseScrollDelta::LineDelta(x, y) => (x, y),
153                    winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
154                };
155                let app_event =
156                    events::AppEvent::Scroll(self.cursor_pos.0, self.cursor_pos.1, dx, -dy);
157                self.event_system.handle(&app_event, &mut self.scene);
158                if let Some(window) = &self.window {
159                    window.request_redraw();
160                }
161            }
162            WindowEvent::RedrawRequested => {
163                if self.needs_relayout {
164                    if let Some(renderer) = &self.renderer {
165                        let (vw, vh) = renderer.viewport_size();
166                        self.layout_engine.compute(&mut self.scene, vw, vh);
167                        self.needs_relayout = false;
168                    }
169
170                    if !self.scripts_executed {
171                        self.scripts_executed = true;
172                        let scripts = self.scripts.clone();
173                        for script in &scripts {
174                            if let Some(js) = &mut self.js_engine {
175                                if let Err(e) = js.execute(script) {
176                                    eprintln!("foundry: {}", e);
177                                }
178                                let mutations = js.take_mutations();
179                                if !mutations.is_empty() {
180                                    js.apply_mutations(&mut self.scene, &mutations);
181                                    if let Some(renderer) = &self.renderer {
182                                        let (vw, vh) = renderer.viewport_size();
183                                        self.layout_engine.compute(&mut self.scene, vw, vh);
184                                    }
185                                }
186                            }
187                        }
188                    }
189                }
190
191                if let Some(renderer) = &mut self.renderer {
192                    match renderer.render(&self.scene, self.text_engine.as_mut()) {
193                        Ok(_) => {}
194                        Err(e) => {
195                            eprintln!("foundry: render error: {}", e);
196                        }
197                    }
198                }
199            }
200            _ => {}
201        }
202    }
203
204    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
205        if self.scene.tick_animations() {
206            self.needs_relayout = true;
207            if let Some(window) = &self.window {
208                window.request_redraw();
209            }
210            event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(
211                std::time::Instant::now() + std::time::Duration::from_millis(16),
212            ));
213        } else {
214            event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait);
215        }
216    }
217}
218
219impl EmbeddedApp {
220    fn execute_handlers(&mut self, results: &[events::EventResult]) {
221        for result in results {
222            if let Some(js) = &mut self.js_engine {
223                if let Err(e) = js.execute(&result.handler_code) {
224                    eprintln!("foundry: event handler error: {}", e);
225                }
226                let mutations = js.take_mutations();
227                if !mutations.is_empty() {
228                    js.apply_mutations(&mut self.scene, &mutations);
229                    self.needs_relayout = true;
230                }
231            }
232        }
233    }
234}