vtui_core/lib.rs
1use std::any::{Any, TypeId};
2
3use ratatui::{Frame, buffer::Buffer, layout::Rect};
4
5type DrawHandler = Box<dyn FnMut(DrawContext)>;
6
7/// A marker trait for runtime signals.
8///
9/// An [`Event`] represents something that has already occurred, such as user input, a timer tick,
10/// or the completion of an asynchronous task. Events are produced by the runtime and consumed
11/// synchronously during the update phase.
12///
13/// Events carry no control flow and must not fail. All state transitions in response to an event
14/// occur inside registered listeners during a runtime update.
15pub trait Event {}
16
17/// A runtime event where some producer wishes to continue the flow of time.
18///
19/// These events are emitted upon request by the runtime to drive time-based updates such as
20/// animations, polling, or scheduled work.
21///
22/// The exact frequency and batching behavior are runtime-defined and may vary depending on
23/// configuration.
24pub struct Tick {}
25
26impl Event for Tick {}
27
28/// A builder which declares the properties of a component.
29///
30/// Components are consumed into a [`Runtime`] object which performs the declared behavior at
31/// runtime.
32#[derive(Default)]
33pub struct Component {
34 draw: Option<DrawHandler>
35}
36
37impl Component {
38 /// Registers a listener for a specific [`Event`].
39 pub fn listen<E: Event + 'static>(&mut self, mut listener: impl FnMut(&E) + 'static) {
40 let type_id = TypeId::of::<E>();
41
42 let wrapped = Box::new(move |event: &mut dyn Any| {
43 if let Some(event) = event.downcast_mut::<E>() {
44 listener(event);
45 }
46 });
47 }
48
49 /// Registers a draw handler that specifies how this component is rendered.
50 pub fn draw(&mut self, listener: impl FnMut(DrawContext) + 'static) {
51 self.draw = Some(Box::new(listener));
52 }
53
54 /// Builds the [`Component`] into a [`Runtime`], which can be used at runtime to perform the
55 /// declared behavior of this [`Component`].
56 pub fn build(self) -> Runtime {
57 Runtime::new(self.draw)
58 }
59}
60
61/// A context container given to all component draw handlers.
62///
63/// This currently only contains the basic [`Rect`] and [`Buffer`] objects, but exists to support
64/// forward compatibility for new features.
65pub struct DrawContext<'a> {
66 pub rect: Rect,
67 pub buf: &'a mut Buffer,
68}
69
70/// The execution engine for a `vtui` application.
71///
72/// A [`Runtime`] owns all state required to execute a component tree, including registered draw
73/// handlers, event listeners, and internal queues. It is built from a fully-declared [`Component`]
74/// and is responsible for driving the draw–update lifecycle.
75///
76/// # Event loop model
77///
78/// The runtime operates in a strict, single-threaded loop with well-defined phases:
79///
80/// Draws occur first in order to calculate layout for potential hit-testing events such as mouse
81/// clicks. These occur synchronously from parent to children components.
82///
83/// A runtime update is performed immediately after, which blocks the event loop until it can
84/// consume some event. This can range from user IO, promise completions/cancellations, tick events,
85/// and more. It is also possible for the runtime to perform batching or coalescing of events in a
86/// manner that is invariant to the draw function.
87///
88/// During a runtime update, a listener may potentially politely request shutdown. Once the runtime
89/// is comfortable with a shutdown, the event loop exits.
90///
91/// # Concurrency
92///
93/// The runtime is single-threaded and not [`Send`] or [`Sync`]. Concurrent systems,
94/// such as async tasks or input streams, may enqueue events via channels, but
95/// the runtime itself processes all events deterministically on one thread.
96#[derive(Default)]
97pub struct Runtime {
98 draw: Option<DrawHandler>,
99}
100
101impl Runtime {
102 pub fn new(draw: Option<DrawHandler>) -> Self {
103 Self { draw }
104 }
105
106 pub fn update(&mut self) {
107 // TODO
108 }
109
110 pub fn draw(&mut self, frame: &mut Frame) {
111 let ctx = DrawContext {
112 rect: frame.area(),
113 buf: frame.buffer_mut(),
114 };
115
116 if let Some(draw) = &mut self.draw {
117 draw(ctx);
118 }
119 }
120
121 pub fn should_exit(&self) -> bool {
122 false
123 }
124}