1pub mod ir;
14pub mod platform;
15pub mod layout;
16pub mod events;
17pub mod tree;
18pub mod scheduler;
19pub mod navigation;
20pub mod accessibility;
21pub mod bridge;
22pub mod generated;
23pub mod platform_macos;
24pub mod platform_windows;
25pub mod platform_ios;
26pub mod platform_android;
27pub mod platform_web;
28pub mod modules;
29pub mod devtools;
30pub mod components;
31pub mod storage;
32pub mod ai;
33pub mod cloud;
34pub mod plugins;
35
36pub mod prelude {
38 pub use crate::ir::{IrCommand, IrBatch};
39 pub use crate::platform::{
40 PlatformBridge, PlatformId, PlatformCapability,
41 NativeHandle, ViewType, PropValue, PropsDiff,
42 };
43 pub use crate::layout::{LayoutEngine, LayoutStyle, ComputedLayout};
44 pub use crate::events::{InputEvent, PointerEvent, KeyboardEvent};
45 pub use crate::tree::{ShadowTree, NodeId};
46 pub use crate::scheduler::{Scheduler, Priority};
47 pub use crate::navigation::{Navigator, NavigationAction, NavigationEvent};
48 pub use crate::accessibility::{AccessibilityInfo, AccessibilityRole, FocusManager};
49 pub use crate::bridge::{SyncCall, SyncResult, AsyncCall, NativeCallback};
50 pub use crate::platform_ios::IosPlatform;
51 pub use crate::platform_android::AndroidPlatform;
52 pub use crate::platform_web::WebPlatform;
53 pub use crate::platform_macos::MacosPlatform;
54 pub use crate::platform_windows::WindowsPlatform;
55}
56
57use tree::ShadowTree;
58use layout::LayoutEngine;
59use events::{EventDispatcher, InputEvent};
60use scheduler::Scheduler;
61use navigation::Navigator;
62use accessibility::FocusManager;
63use bridge::{SyncCall, SyncResult, AsyncCall};
64use platform::PlatformBridge;
65use std::collections::HashSet;
66use std::sync::Arc;
67use std::time::Instant;
68
69pub struct Engine {
72 tree: ShadowTree,
73 layout: LayoutEngine,
74 events: EventDispatcher,
75 scheduler: Scheduler,
76 navigator: Navigator,
77 focus: FocusManager,
78 platform: Arc<dyn PlatformBridge>,
79 frame_count: u64,
80
81 dirty_nodes: HashSet<tree::NodeId>,
84}
85
86impl Engine {
87 pub fn new(platform: Arc<dyn PlatformBridge>) -> Self {
89 tracing::info!(
90 platform = ?platform.platform_id(),
91 "AppScale Engine initialized"
92 );
93
94 Self {
95 tree: ShadowTree::new(),
96 layout: LayoutEngine::new(),
97 events: EventDispatcher::new(),
98 scheduler: Scheduler::new(),
99 navigator: Navigator::new(),
100 focus: FocusManager::new(),
101 platform,
102 frame_count: 0,
103 dirty_nodes: HashSet::new(),
104 }
105 }
106
107 pub fn apply_commit(&mut self, batch: &ir::IrBatch) -> Result<(), EngineError> {
117 self.frame_count += 1;
118 let frame_start = Instant::now();
119 let _span = tracing::info_span!("commit", frame = self.frame_count).entered();
120
121 let dirty_nodes = self.apply_ir_to_tree(batch)?;
123 self.dirty_nodes.extend(&dirty_nodes);
124
125 let layout_start = Instant::now();
127 if !self.dirty_nodes.is_empty() {
128 let screen_size = self.platform.screen_size();
129 self.layout.compute(
130 &self.tree,
131 screen_size.width,
132 screen_size.height,
133 &*self.platform,
134 )?;
135 }
136 let layout_duration = layout_start.elapsed();
137
138 let mount_start = Instant::now();
140 let dirty_vec: Vec<_> = self.dirty_nodes.drain().collect();
141 self.mount_changes(&dirty_vec)?;
142 let mount_duration = mount_start.elapsed();
143
144 self.scheduler.record_frame(
146 layout_duration,
147 mount_duration,
148 1, );
150
151 Ok(())
152 }
153
154 pub fn enqueue_commit(
157 &self,
158 batch: ir::IrBatch,
159 priority: scheduler::Priority,
160 ) {
161 self.scheduler.enqueue(batch, priority);
162 }
163
164 pub fn process_frame(&mut self) -> Result<(), EngineError> {
167 let batches = self.scheduler.drain_frame();
168 for batch in &batches {
169 self.apply_commit(batch)?;
170 }
171 Ok(())
172 }
173
174 pub fn navigate(
176 &mut self,
177 action: navigation::NavigationAction,
178 ) -> Vec<navigation::NavigationEvent> {
179 self.navigator.dispatch(action)
180 }
181
182 pub fn navigator(&self) -> &Navigator {
184 &self.navigator
185 }
186
187 pub fn focus_manager(&mut self) -> &mut FocusManager {
189 &mut self.focus
190 }
191
192 pub fn scheduler_stats(&self) -> scheduler::FrameStats {
194 self.scheduler.stats()
195 }
196
197 fn apply_ir_to_tree(
199 &mut self,
200 batch: &ir::IrBatch,
201 ) -> Result<Vec<tree::NodeId>, EngineError> {
202 let mut dirty = Vec::new();
203
204 for cmd in &batch.commands {
205 match cmd {
206 ir::IrCommand::CreateNode { id, view_type, props, style } => {
207 self.tree.create_node(*id, view_type.clone(), props.clone());
208 self.layout.create_node(*id, style)?;
209 dirty.push(*id);
210 }
211 ir::IrCommand::UpdateProps { id, diff } => {
212 self.tree.update_props(*id, diff);
213 dirty.push(*id);
214 }
215 ir::IrCommand::UpdateStyle { id, style } => {
216 self.layout.update_style(*id, style)?;
217 dirty.push(*id);
218 }
219 ir::IrCommand::AppendChild { parent, child } => {
220 self.tree.append_child(*parent, *child);
221 self.layout.set_children_from_tree(*parent, &self.tree)?;
222 dirty.push(*parent);
223 }
224 ir::IrCommand::InsertBefore { parent, child, before } => {
225 self.tree.insert_before(*parent, *child, *before);
226 self.layout.set_children_from_tree(*parent, &self.tree)?;
227 dirty.push(*parent);
228 }
229 ir::IrCommand::RemoveChild { parent, child } => {
230 self.tree.remove_child(*parent, *child);
231 self.layout.remove_node(*child);
232 dirty.push(*parent);
233 dirty.push(*child);
234 }
235 ir::IrCommand::SetRootNode { id } => {
236 self.tree.set_root(*id);
237 self.layout.set_root(*id);
238 dirty.push(*id);
239 }
240 }
241 }
242
243 Ok(dirty)
244 }
245
246 fn mount_changes(
248 &mut self,
249 dirty_nodes: &[tree::NodeId],
250 ) -> Result<(), EngineError> {
251 for &node_id in dirty_nodes {
252 let (view_type, parent_info, native_handle) = match self.tree.get(node_id) {
253 Some(n) => (
254 n.view_type.clone(),
255 n.parent.and_then(|pid| {
256 self.tree.get(pid).and_then(|p| {
257 p.native_handle.map(|h| (h, p.children.iter().position(|&c| c == node_id).unwrap_or(0)))
258 })
259 }),
260 n.native_handle,
261 ),
262 None => continue, };
264
265 if native_handle.is_none() {
267 let handle = self.platform.create_view(view_type, node_id);
268 self.tree.set_native_handle(node_id, handle);
269
270 if let Some((parent_handle, index)) = parent_info {
272 self.platform.insert_child(parent_handle, handle, index);
273 }
274 }
275
276 if let Some(handle) = self.tree.get(node_id).and_then(|n| n.native_handle) {
278 let props_diff = self.tree.take_pending_props(node_id);
279 if !props_diff.is_empty() {
280 self.platform.update_view(handle, &props_diff)
281 .map_err(|e| EngineError::Platform(e.to_string()))?;
282 }
283
284 if let Some(layout) = self.layout.get_computed(node_id) {
286 let mut position_props = platform::PropsDiff::new();
287 position_props.set("frame", PropValue::Rect {
288 x: layout.x,
289 y: layout.y,
290 width: layout.width,
291 height: layout.height,
292 });
293 self.platform.update_view(handle, &position_props)
294 .map_err(|e| EngineError::Platform(e.to_string()))?;
295 }
296 }
297 }
298
299 Ok(())
300 }
301
302 pub fn handle_event(&mut self, event: InputEvent) -> events::EventResult {
305 self.events.dispatch(event, &self.layout, &self.tree)
306 }
307
308 pub fn handle_sync(&self, call: &SyncCall) -> SyncResult {
317 match call {
318 SyncCall::Measure { node_id } => {
319 match self.layout.get_computed(*node_id) {
320 Some(layout) => SyncResult::from_layout(layout),
321 None => SyncResult::NotFound,
322 }
323 }
324
325 SyncCall::IsFocused { node_id } => {
326 SyncResult::Bool {
327 value: self.focus.focused() == Some(*node_id),
328 }
329 }
330
331 SyncCall::GetScrollOffset { node_id } => {
332 if self.tree.get(*node_id).is_some() {
335 SyncResult::ScrollOffset { x: 0.0, y: 0.0 }
336 } else {
337 SyncResult::NotFound
338 }
339 }
340
341 SyncCall::SupportsCapability { capability } => {
342 let cap = match capability.as_str() {
343 "haptics" => Some(platform::PlatformCapability::Haptics),
344 "biometrics" => Some(platform::PlatformCapability::Biometrics),
345 "menuBar" => Some(platform::PlatformCapability::MenuBar),
346 "systemTray" => Some(platform::PlatformCapability::SystemTray),
347 "multiWindow" => Some(platform::PlatformCapability::MultiWindow),
348 "dragAndDrop" => Some(platform::PlatformCapability::DragAndDrop),
349 "contextMenu" => Some(platform::PlatformCapability::ContextMenu),
350 "nativeShare" => Some(platform::PlatformCapability::NativeShare),
351 "pushNotifications" => Some(platform::PlatformCapability::PushNotifications),
352 "backgroundFetch" => Some(platform::PlatformCapability::BackgroundFetch),
353 "nativeDatePicker" => Some(platform::PlatformCapability::NativeDatePicker),
354 "nativeFilePicker" => Some(platform::PlatformCapability::NativeFilePicker),
355 _ => None,
356 };
357 SyncResult::Bool {
358 value: cap.map_or(false, |c| self.platform.supports(c)),
359 }
360 }
361
362 SyncCall::GetScreenInfo => {
363 let size = self.platform.screen_size();
364 SyncResult::ScreenInfo {
365 width: size.width,
366 height: size.height,
367 scale: self.platform.scale_factor(),
368 }
369 }
370
371 SyncCall::IsProcessing => {
372 SyncResult::Bool {
373 value: self.scheduler.is_processing(),
374 }
375 }
376
377 SyncCall::GetAccessibilityRole { node_id } => {
378 match self.tree.get(*node_id) {
382 Some(node) => {
383 let role = match &node.view_type {
384 platform::ViewType::Button => "button",
385 platform::ViewType::Text => "text",
386 platform::ViewType::TextInput => "textField",
387 platform::ViewType::Image => "image",
388 platform::ViewType::Switch => "switch",
389 platform::ViewType::Slider => "adjustable",
390 _ => "none",
391 };
392 SyncResult::Role { role: role.to_string() }
393 }
394 None => SyncResult::NotFound,
395 }
396 }
397
398 SyncCall::GetFrameStats => {
399 let stats = self.scheduler.stats();
400 SyncResult::FrameStats {
401 frame_count: stats.frame_count,
402 frames_dropped: stats.frames_dropped,
403 last_frame_ms: stats.last_frame_duration.as_secs_f64() * 1000.0,
404 last_layout_ms: stats.last_layout_duration.as_secs_f64() * 1000.0,
405 last_mount_ms: stats.last_mount_duration.as_secs_f64() * 1000.0,
406 }
407 }
408
409 SyncCall::NodeExists { node_id } => {
410 SyncResult::Bool {
411 value: self.tree.get(*node_id).is_some(),
412 }
413 }
414
415 SyncCall::GetChildCount { node_id } => {
416 match self.tree.get(*node_id) {
417 Some(node) => SyncResult::Int { value: node.children.len() as u64 },
418 None => SyncResult::NotFound,
419 }
420 }
421
422 SyncCall::MeasureText { text, style, max_width } => {
423 let platform_style = style.to_platform_style();
424 let metrics = self.platform.measure_text(text, &platform_style, *max_width);
425 SyncResult::TextMetrics {
426 width: metrics.width,
427 height: metrics.height,
428 baseline: metrics.baseline,
429 line_count: metrics.line_count,
430 }
431 }
432
433 SyncCall::GetFocusedNode => {
434 SyncResult::NodeIdResult {
435 node_id: self.focus.focused().map(|id| id.0),
436 }
437 }
438
439 SyncCall::CanGoBack => {
440 SyncResult::Bool {
441 value: self.navigator.can_go_back(),
442 }
443 }
444
445 SyncCall::GetActiveRoute => {
446 match self.navigator.active_screen() {
447 Some(screen) => SyncResult::ActiveRoute {
448 route_name: Some(screen.route_name.clone()),
449 params: screen.params.clone(),
450 },
451 None => SyncResult::ActiveRoute {
452 route_name: None,
453 params: std::collections::HashMap::new(),
454 },
455 }
456 }
457 }
458 }
459
460 pub fn handle_async(&mut self, call: AsyncCall) {
467 match &call {
468 AsyncCall::Navigate { .. } => {
469 if let Some(action) = call.to_navigation_action() {
470 self.navigator.dispatch(action);
471 }
472 }
473 AsyncCall::SetFocus { node_id } => {
474 let _ = self.focus.focus(*node_id);
475 }
476 AsyncCall::MoveFocus { direction } => {
477 let _direction = direction; }
480 AsyncCall::Announce { message } => {
481 tracing::info!(message = %message, "a11y announcement");
482 }
485 }
486 }
487
488 pub fn tree(&self) -> &ShadowTree {
494 &self.tree
495 }
496
497 pub fn layout(&self) -> &LayoutEngine {
499 &self.layout
500 }
501}
502
503use platform::PropValue;
504
505#[derive(Debug, thiserror::Error)]
506pub enum EngineError {
507 #[error("Layout error: {0}")]
508 Layout(#[from] layout::LayoutError),
509
510 #[error("Platform error: {0}")]
511 Platform(String),
512
513 #[error("IR decode error: {0}")]
514 IrDecode(String),
515
516 #[error("Node not found: {0:?}")]
517 NodeNotFound(tree::NodeId),
518}