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}