Skip to main content

foundry_runtime/
lib.rs

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