1use std::{
2 collections::{BTreeMap, BTreeSet, HashMap},
3 future::Future,
4};
5
6use ansiq_core::{
7 ComponentRenderer, Element, ElementKind, HistoryBlock, HistoryEntry, HookStore, Node, Rect,
8 RuntimeRequest, ScopeId, ViewCtx, dispose_component_scope, flush_reactivity,
9 render_in_component_scope, take_dirty_component_scopes,
10};
11use ansiq_layout::{layout_tree_with_ids, measure_node_height, relayout_tree_along_paths};
12use ansiq_surface::Key;
13use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender, error::SendError};
14
15use crate::{FocusState, routing};
16
17pub trait App {
18 type Message: Send + 'static;
19
20 fn mount(&mut self, _handle: &RuntimeHandle<Self::Message>) {}
21
22 fn render(&mut self, cx: &mut ViewCtx<'_, Self::Message>) -> Element<Self::Message>;
23
24 fn update(&mut self, message: Self::Message, handle: &RuntimeHandle<Self::Message>);
25
26 fn on_unhandled_key(&mut self, _key: Key, _handle: &RuntimeHandle<Self::Message>) -> bool {
27 false
28 }
29}
30
31pub struct RuntimeHandle<Message> {
32 sender: UnboundedSender<RuntimeRequest<Message>>,
33}
34
35impl<Message> Clone for RuntimeHandle<Message> {
36 fn clone(&self) -> Self {
37 Self {
38 sender: self.sender.clone(),
39 }
40 }
41}
42
43impl<Message: Send + 'static> RuntimeHandle<Message> {
44 pub fn emit(&self, message: Message) -> Result<(), SendError<RuntimeRequest<Message>>> {
45 self.sender.send(RuntimeRequest::EmitMessage(message))
46 }
47
48 pub fn quit(&self) -> Result<(), SendError<RuntimeRequest<Message>>> {
49 self.sender.send(RuntimeRequest::Quit)
50 }
51
52 pub fn trap_focus_in(
53 &self,
54 scope_key: impl Into<String>,
55 ) -> Result<(), SendError<RuntimeRequest<Message>>> {
56 self.sender
57 .send(RuntimeRequest::SetFocusScope(Some(scope_key.into())))
58 }
59
60 pub fn clear_focus_scope(&self) -> Result<(), SendError<RuntimeRequest<Message>>> {
61 self.sender.send(RuntimeRequest::SetFocusScope(None))
62 }
63
64 pub fn commit_history(
65 &self,
66 content: String,
67 ) -> Result<(), SendError<RuntimeRequest<Message>>> {
68 self.sender.send(RuntimeRequest::commit_text(content))
69 }
70
71 pub fn commit_history_block(
72 &self,
73 block: HistoryBlock,
74 ) -> Result<(), SendError<RuntimeRequest<Message>>> {
75 self.sender.send(RuntimeRequest::commit_block(block))
76 }
77
78 pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<()>
79 where
80 F: Future<Output = ()> + Send + 'static,
81 {
82 tokio::spawn(future)
83 }
84}
85
86pub struct Engine<A: App> {
87 app: A,
88 hooks: HookStore,
89 component_hooks: HashMap<ScopeId, HookStore>,
90 sender: UnboundedSender<RuntimeRequest<A::Message>>,
91 receiver: UnboundedReceiver<RuntimeRequest<A::Message>>,
92 focus: FocusState,
93 pending_history: Vec<HistoryEntry>,
94 tree: Option<Node<A::Message>>,
95 redraw_regions: Option<Vec<Rect>>,
96 root_scope: Option<ScopeId>,
97 component_scopes: BTreeSet<ScopeId>,
98 pending_component_scopes: Vec<ScopeId>,
99 bounds: Rect,
100 required_height: u16,
101 next_node_id: usize,
102 needs_rerender: bool,
103 dirty: bool,
104 mounted: bool,
105}
106
107impl<A: App> Engine<A> {
108 pub fn new(app: A) -> Self {
109 let (sender, receiver) = mpsc::unbounded_channel();
110
111 Self {
112 app,
113 hooks: HookStore::default(),
114 component_hooks: HashMap::new(),
115 sender,
116 receiver,
117 focus: FocusState::default(),
118 pending_history: Vec::new(),
119 tree: None,
120 redraw_regions: None,
121 root_scope: None,
122 component_scopes: BTreeSet::new(),
123 pending_component_scopes: Vec::new(),
124 bounds: Rect::new(0, 0, 80, 24),
125 required_height: 1,
126 next_node_id: 0,
127 needs_rerender: true,
128 dirty: true,
129 mounted: false,
130 }
131 }
132
133 pub fn app(&self) -> &A {
134 &self.app
135 }
136
137 pub fn app_mut(&mut self) -> &mut A {
138 &mut self.app
139 }
140
141 pub fn handle(&self) -> RuntimeHandle<A::Message> {
142 RuntimeHandle {
143 sender: self.sender.clone(),
144 }
145 }
146
147 pub fn mount(&mut self) {
148 if self.mounted {
149 return;
150 }
151
152 let handle = self.handle();
153 self.app.mount(&handle);
154 self.mounted = true;
155 self.needs_rerender = true;
156 self.dirty = true;
157 }
158
159 pub fn tree(&self) -> Option<&Node<A::Message>> {
160 self.tree.as_ref()
161 }
162
163 pub fn redraw_regions(&self) -> Option<&[Rect]> {
164 self.redraw_regions.as_deref()
165 }
166
167 pub fn is_dirty(&self) -> bool {
168 self.dirty || self.needs_rerender
169 }
170
171 pub fn focused(&self) -> Option<usize> {
172 self.focus.current()
173 }
174
175 pub fn take_pending_history(&mut self) -> Vec<HistoryEntry> {
176 std::mem::take(&mut self.pending_history)
177 }
178
179 pub fn required_height(&self) -> u16 {
180 self.required_height
181 }
182
183 pub fn set_bounds(&mut self, bounds: Rect) {
184 self.bounds = bounds;
185 self.needs_rerender = true;
186 self.dirty = true;
187 }
188
189 pub fn render_tree(&mut self) {
190 self.sync_reactivity();
191
192 if !self.needs_rerender && self.pending_component_scopes.is_empty() && self.tree.is_some() {
193 if self.dirty {
194 self.redraw_regions = None;
195 self.dirty = false;
196 }
197 return;
198 }
199
200 let root_scope_dirty = self
201 .root_scope
202 .is_some_and(|scope| self.pending_component_scopes.contains(&scope));
203
204 if self.tree.is_none()
205 || self.root_scope.is_none()
206 || self.needs_rerender
207 || root_scope_dirty
208 {
209 self.render_root_tree();
210 return;
211 }
212
213 let dirty_scopes: BTreeSet<_> = std::mem::take(&mut self.pending_component_scopes)
214 .into_iter()
215 .collect();
216 if dirty_scopes.is_empty() {
217 return;
218 }
219
220 if let Some(mut tree) = self.tree.take() {
221 let focused_before = self.focus.current();
222 let focus_rect_before =
223 focused_before.and_then(|focused| find_node_rect(&tree, focused));
224 let dirty_paths =
225 self.rerender_dirty_component_nodes(&mut tree, &dirty_scopes, &mut Vec::new());
226 let mut relayout = relayout_tree_along_paths(&mut tree, self.bounds, &dirty_paths);
227 self.required_height = tree.measured_height;
228 self.focus.sync_from_tree(&tree);
229 let focused_after = self.focus.current();
230 let focus_rect_after = focused_after.and_then(|focused| find_node_rect(&tree, focused));
231 append_focus_transition_regions(
232 &mut relayout.invalidated_regions,
233 focus_rect_before,
234 focus_rect_after,
235 );
236 self.redraw_regions = Some(relayout.invalidated_regions);
237 self.refresh_component_scope_registry(&tree);
238 self.tree = Some(tree);
239 }
240
241 self.dirty = false;
242 self.needs_rerender = false;
243 }
244
245 pub fn handle_input(&mut self, key: Key) -> bool {
246 let Some(tree) = self.tree.as_mut() else {
247 return false;
248 };
249
250 let focused_before = self.focus.current();
251 let effect = routing::handle_key(tree, &mut self.focus, key);
252 if effect.dirty {
253 self.dirty = true;
254 }
255 if self.focus.current() != focused_before {
256 self.dirty = true;
257 }
258 if let Some(message) = effect.message {
259 let handle = self.handle();
260 self.app.update(message, &handle);
261 self.needs_rerender = true;
262 self.dirty = true;
263 }
264 if !effect.handled && !effect.quit {
265 let handle = self.handle();
266 if self.app.on_unhandled_key(key, &handle) {
267 self.needs_rerender = true;
268 self.dirty = true;
269 }
270 }
271
272 effect.quit
273 }
274
275 pub fn drain_requests(&mut self) -> bool {
276 let mut should_quit = false;
277
278 while let Ok(request) = self.receiver.try_recv() {
279 match request {
280 RuntimeRequest::EmitMessage(message) => {
281 let handle = self.handle();
282 self.app.update(message, &handle);
283 self.needs_rerender = true;
284 self.dirty = true;
285 }
286 RuntimeRequest::CommitHistory(entry) => {
287 self.pending_history.push(entry);
288 self.dirty = true;
289 }
290 RuntimeRequest::SetFocusScope(scope_key) => {
291 self.focus.set_scope_key(scope_key);
292 if let Some(tree) = self.tree.as_ref() {
293 self.focus.sync_from_tree(tree);
294 }
295 self.dirty = true;
296 }
297 RuntimeRequest::Quit => should_quit = true,
298 }
299 }
300
301 should_quit
302 }
303
304 fn sync_reactivity(&mut self) {
305 flush_reactivity();
306 let dirty = take_dirty_component_scopes();
307 if !dirty.is_empty() {
308 self.pending_component_scopes.extend(dirty);
309 }
310 }
311
312 fn render_root_tree(&mut self) {
313 let continuity_restore = self
314 .tree
315 .as_ref()
316 .map(capture_widget_runtime_state_in_subtree)
317 .unwrap_or_default();
318 let focus_restore = self.focus.current().and_then(|current| {
319 self.tree
320 .as_ref()
321 .map(|tree| capture_focus_continuity_in_subtree(tree, current))
322 });
323
324 self.hooks.begin_render();
325 let (scope, element) = render_in_component_scope(self.root_scope, |_| {
326 let element = {
327 let mut cx = ViewCtx::new(&mut self.hooks);
328 self.app.render(&mut cx)
329 };
330 self.hooks.finish_render();
331 element
332 });
333 self.root_scope = Some(scope);
334
335 let element = self.resolve_component_elements(element);
336 self.next_node_id = 0;
337 let mut tree = layout_tree_with_ids(element, self.bounds, &mut self.next_node_id);
338 self.required_height = tree.measured_height;
339 self.redraw_regions = None;
340 restore_widget_runtime_state_in_subtree(&mut tree, &continuity_restore);
341 if let Some(focus_target) = focus_restore
342 && let Some(focus_id) = restore_focus_continuity_in_subtree(&tree, &focus_target)
343 {
344 self.focus.set_current(Some(focus_id));
345 }
346 self.focus.sync_from_tree(&tree);
347 self.refresh_component_scope_registry(&tree);
348 self.tree = Some(tree);
349 self.pending_component_scopes.clear();
350 self.dirty = false;
351 self.needs_rerender = false;
352 }
353
354 fn resolve_component_elements(&mut self, element: Element<A::Message>) -> Element<A::Message> {
355 let Element {
356 kind,
357 layout,
358 style,
359 focusable,
360 continuity_key,
361 children,
362 } = element;
363
364 match kind {
365 ElementKind::Component(mut props) => {
366 let sender = self.sender.clone();
367 let renderer = props.renderer.clone();
368 let (scope, child) = render_in_component_scope(props.scope, |scope| {
369 self.render_component(renderer, scope, sender)
370 });
371 props.scope = Some(scope);
372 let child = self.resolve_component_elements(child);
373
374 let mut element = Element::new(ElementKind::Component(props))
375 .with_layout(layout)
376 .with_style(style)
377 .with_focusable(focusable)
378 .with_children(vec![child]);
379 if let Some(key) = continuity_key {
380 element = element.with_continuity_key(key);
381 }
382 element
383 }
384 kind => {
385 let children = children
386 .into_iter()
387 .map(|child| self.resolve_component_elements(child))
388 .collect();
389 let mut element = Element::new(kind)
390 .with_layout(layout)
391 .with_style(style)
392 .with_focusable(focusable)
393 .with_children(children);
394 if let Some(key) = continuity_key {
395 element = element.with_continuity_key(key);
396 }
397 element
398 }
399 }
400 }
401
402 fn rerender_dirty_component_nodes(
403 &mut self,
404 node: &mut Node<A::Message>,
405 dirty_scopes: &BTreeSet<ScopeId>,
406 path: &mut Vec<usize>,
407 ) -> Vec<Vec<usize>> {
408 if let ElementKind::Component(props) = &mut node.element.kind
409 && let Some(scope) = props.scope
410 && dirty_scopes.contains(&scope)
411 {
412 let focus_restore = self.focus.current().and_then(|current| {
413 node.children
414 .first()
415 .map(|child| capture_focus_continuity_in_subtree(child, current))
416 });
417 let widget_state_restore = node
418 .children
419 .first()
420 .map(capture_widget_runtime_state_in_subtree)
421 .unwrap_or_default();
422 let sender = self.sender.clone();
423 let renderer = props.renderer.clone();
424 let (_, child) = render_in_component_scope(Some(scope), |scope| {
425 self.render_component(renderer, scope, sender)
426 });
427 let child = self.resolve_component_elements(child);
428 let mut child = layout_tree_with_ids(child, node.rect, &mut self.next_node_id);
429 restore_widget_runtime_state_in_subtree(&mut child, &widget_state_restore);
430 if let Some(focus_target) = focus_restore
431 && let Some(focus_id) = restore_focus_continuity_in_subtree(&child, &focus_target)
432 {
433 self.focus.set_current(Some(focus_id));
434 }
435 node.children = vec![child];
436 node.measured_height = 0;
437 node.measured_height = measure_node_height(node, node.rect.width);
438 return vec![path.clone()];
439 }
440
441 let mut dirty_paths = Vec::new();
442 for (index, child) in node.children.iter_mut().enumerate() {
443 path.push(index);
444 dirty_paths.extend(self.rerender_dirty_component_nodes(child, dirty_scopes, path));
445 path.pop();
446 }
447 dirty_paths
448 }
449
450 fn render_component(
451 &mut self,
452 renderer: ComponentRenderer<A::Message>,
453 scope: ScopeId,
454 _sender: UnboundedSender<RuntimeRequest<A::Message>>,
455 ) -> Element<A::Message> {
456 match renderer {
457 ComponentRenderer::Static(renderer) => renderer(),
458 ComponentRenderer::WithCx(renderer) => {
459 let hooks = self.component_hooks.entry(scope).or_default();
460 hooks.begin_render();
461 let element = {
462 let mut cx = ViewCtx::new(hooks);
463 renderer(&mut cx)
464 };
465 hooks.finish_render();
466 element
467 }
468 }
469 }
470
471 fn refresh_component_scope_registry(&mut self, tree: &Node<A::Message>) {
472 let mut live = BTreeSet::new();
473 collect_component_scopes(tree, &mut live);
474
475 let stale: Vec<_> = self.component_scopes.difference(&live).copied().collect();
479 for scope in stale {
480 self.component_hooks.remove(&scope);
481 self.pending_component_scopes
482 .retain(|pending| *pending != scope);
483 dispose_component_scope(scope);
484 }
485
486 self.component_scopes = live;
487 }
488}
489
490fn collect_component_scopes<Message>(node: &Node<Message>, scopes: &mut BTreeSet<ScopeId>) {
491 if let ElementKind::Component(props) = &node.element.kind
492 && let Some(scope) = props.scope
493 {
494 scopes.insert(scope);
495 }
496
497 for child in &node.children {
498 collect_component_scopes(child, scopes);
499 }
500}
501
502fn find_node_rect<Message>(node: &Node<Message>, target_id: usize) -> Option<Rect> {
503 if node.id == target_id {
504 return Some(node.rect);
505 }
506
507 for child in &node.children {
508 if let Some(rect) = find_node_rect(child, target_id) {
509 return Some(rect);
510 }
511 }
512
513 None
514}
515
516fn append_focus_transition_regions(
517 regions: &mut Vec<Rect>,
518 before: Option<Rect>,
519 after: Option<Rect>,
520) {
521 if before == after {
522 return;
523 }
524
525 if let Some(rect) = before {
526 push_region_if_missing(regions, rect);
527 }
528 if let Some(rect) = after {
529 push_region_if_missing(regions, rect);
530 }
531}
532
533fn push_region_if_missing(regions: &mut Vec<Rect>, rect: Rect) {
534 if regions.iter().any(|existing| *existing == rect) {
535 return;
536 }
537 regions.push(rect);
538}
539
540#[derive(Debug, Clone, Default)]
541struct FocusContinuityTarget {
542 continuity_key: Option<String>,
543 fallback_index: usize,
544}
545
546#[derive(Debug, Clone, Default)]
547struct WidgetRuntimeStateSnapshot {
548 keyed: BTreeMap<String, ansiq_core::RuntimeWidgetState>,
549 ordered: Vec<ansiq_core::RuntimeWidgetState>,
550}
551
552fn focusable_index_in_subtree<Message>(node: &Node<Message>, target_id: usize) -> Option<usize> {
553 let mut index = 0usize;
554 focusable_index_in_subtree_inner(node, target_id, &mut index)
555}
556
557fn focusable_index_in_subtree_inner<Message>(
558 node: &Node<Message>,
559 target_id: usize,
560 index: &mut usize,
561) -> Option<usize> {
562 if node.element.focusable {
563 if node.id == target_id {
564 return Some(*index);
565 }
566 *index = index.saturating_add(1);
567 }
568
569 for child in &node.children {
570 if let Some(found) = focusable_index_in_subtree_inner(child, target_id, index) {
571 return Some(found);
572 }
573 }
574
575 None
576}
577
578fn focusable_id_at_index<Message>(node: &Node<Message>, target_index: usize) -> Option<usize> {
579 let mut index = 0usize;
580 focusable_id_at_index_inner(node, target_index, &mut index)
581}
582
583fn focusable_id_at_index_inner<Message>(
584 node: &Node<Message>,
585 target_index: usize,
586 index: &mut usize,
587) -> Option<usize> {
588 if node.element.focusable {
589 if *index == target_index {
590 return Some(node.id);
591 }
592 *index = index.saturating_add(1);
593 }
594
595 for child in &node.children {
596 if let Some(found) = focusable_id_at_index_inner(child, target_index, index) {
597 return Some(found);
598 }
599 }
600
601 None
602}
603
604fn capture_focus_continuity_in_subtree<Message>(
605 node: &Node<Message>,
606 target_id: usize,
607) -> FocusContinuityTarget {
608 FocusContinuityTarget {
609 continuity_key: continuity_key_for_node_id(node, target_id),
610 fallback_index: focusable_index_in_subtree(node, target_id).unwrap_or(0),
611 }
612}
613
614fn continuity_key_for_node_id<Message>(node: &Node<Message>, target_id: usize) -> Option<String> {
615 if node.id == target_id {
616 return node.element.continuity_key.clone();
617 }
618
619 for child in &node.children {
620 if let Some(key) = continuity_key_for_node_id(child, target_id) {
621 return Some(key);
622 }
623 }
624
625 None
626}
627
628fn focusable_id_for_continuity_key<Message>(
629 node: &Node<Message>,
630 target_key: &str,
631) -> Option<usize> {
632 if node.element.focusable && node.element.continuity_key() == Some(target_key) {
633 return Some(node.id);
634 }
635
636 for child in &node.children {
637 if let Some(id) = focusable_id_for_continuity_key(child, target_key) {
638 return Some(id);
639 }
640 }
641
642 None
643}
644
645fn restore_focus_continuity_in_subtree<Message>(
646 node: &Node<Message>,
647 target: &FocusContinuityTarget,
648) -> Option<usize> {
649 if let Some(key) = target.continuity_key.as_deref()
650 && let Some(id) = focusable_id_for_continuity_key(node, key)
651 {
652 return Some(id);
653 }
654
655 focusable_id_at_index(node, target.fallback_index)
656}
657
658fn capture_widget_runtime_state_in_subtree<Message>(
659 node: &Node<Message>,
660) -> WidgetRuntimeStateSnapshot {
661 let mut snapshot = WidgetRuntimeStateSnapshot::default();
662 capture_widget_runtime_state_in_subtree_inner(node, &mut snapshot);
663 snapshot
664}
665
666fn capture_widget_runtime_state_in_subtree_inner<Message>(
667 node: &Node<Message>,
668 output: &mut WidgetRuntimeStateSnapshot,
669) {
670 if let Some(state) = node.element.kind.capture_runtime_state() {
671 if let Some(key) = node.element.continuity_key() {
672 output.keyed.insert(key.to_string(), state);
673 } else {
674 output.ordered.push(state);
675 }
676 }
677
678 for child in &node.children {
679 capture_widget_runtime_state_in_subtree_inner(child, output);
680 }
681}
682
683fn restore_widget_runtime_state_in_subtree<Message>(
684 node: &mut Node<Message>,
685 snapshot: &WidgetRuntimeStateSnapshot,
686) {
687 let mut index = 0usize;
688 restore_widget_runtime_state_in_subtree_inner(node, snapshot, &mut index);
689}
690
691fn restore_widget_runtime_state_in_subtree_inner<Message>(
692 node: &mut Node<Message>,
693 snapshot: &WidgetRuntimeStateSnapshot,
694 index: &mut usize,
695) {
696 let expects_runtime_state = node.element.kind.capture_runtime_state().is_some();
697 if expects_runtime_state {
698 let keyed_state = node
699 .element
700 .continuity_key()
701 .and_then(|key| snapshot.keyed.get(key));
702 let positional_state = snapshot.ordered.get(*index);
703 if let Some(state) = keyed_state.or(positional_state) {
704 node.element.kind.restore_runtime_state(state);
705 } else {
706 node.element.kind.initialize_runtime_state();
707 }
708 if keyed_state.is_none() {
709 *index = index.saturating_add(1);
710 }
711 }
712
713 for child in &mut node.children {
714 restore_widget_runtime_state_in_subtree_inner(child, snapshot, index);
715 }
716}