1use super::messages::{InputEvent, RenderCommand};
8use super::{InputActor, RendererActor};
9use crate::buffer::{Buffer, Cell, Rgb};
10use crate::layout::Rect;
11use crossbeam_channel::{bounded, Receiver, Sender, TryRecvError};
12use crossterm::{
13 cursor,
14 event::EnableMouseCapture,
15 execute,
16 terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
17};
18use std::io::{self};
19use std::time::{Duration, Instant};
20
21#[derive(Debug, Clone)]
23pub struct EngineConfig {
24 pub target_fps: u32,
26 pub input_poll_timeout: Duration,
28 pub enable_mouse: bool,
30 pub alternate_screen: bool,
32}
33
34impl Default for EngineConfig {
35 fn default() -> Self {
36 Self {
37 target_fps: 60,
38 input_poll_timeout: Duration::from_millis(10),
39 enable_mouse: false,
40 alternate_screen: true,
41 }
42 }
43}
44
45pub struct Engine {
50 config: EngineConfig,
52 input_rx: Receiver<InputEvent>,
54 render_tx: Sender<RenderCommand>,
56 input_actor: Option<InputActor>,
58 #[allow(dead_code)]
60 renderer_actor: Option<RendererActor>,
61 buffer: Buffer,
63 width: u16,
65 height: u16,
67 frame_start: Instant,
69 frame_duration: Duration,
70 frame_count: u64,
71 running: bool,
73}
74
75impl Engine {
76 pub fn new() -> io::Result<Self> {
82 Self::with_config(EngineConfig::default())
83 }
84
85 pub fn with_config(config: EngineConfig) -> io::Result<Self> {
91 let (width, height) = terminal::size()?;
93
94 terminal::enable_raw_mode()?;
96
97 let mut stdout = io::stdout();
98 if config.alternate_screen {
99 execute!(stdout, EnterAlternateScreen)?;
100 }
101 if config.enable_mouse {
102 execute!(stdout, EnableMouseCapture)?;
103 }
104 execute!(stdout, cursor::Hide)?;
105
106 let (input_tx, input_rx) = bounded::<InputEvent>(64);
108 let (render_tx, render_rx) = bounded::<RenderCommand>(16);
109
110 let input_actor = InputActor::spawn(input_tx, config.input_poll_timeout);
112 let renderer_actor = RendererActor::spawn(render_rx, width, height);
113
114 let frame_duration = Duration::from_secs(1) / config.target_fps;
115
116 Ok(Self {
117 config,
118 input_rx,
119 render_tx,
120 input_actor: Some(input_actor),
121 renderer_actor: Some(renderer_actor),
122 buffer: Buffer::new(width, height),
123 width,
124 height,
125 frame_start: Instant::now(),
126 frame_duration,
127 frame_count: 0,
128 running: true,
129 })
130 }
131
132 pub const fn width(&self) -> u16 {
134 self.width
135 }
136
137 pub const fn height(&self) -> u16 {
139 self.height
140 }
141
142 pub const fn buffer(&self) -> &Buffer {
144 &self.buffer
145 }
146
147 pub const fn buffer_mut(&mut self) -> &mut Buffer {
149 &mut self.buffer
150 }
151
152 pub const fn input_receiver(&self) -> &Receiver<InputEvent> {
154 &self.input_rx
155 }
156
157 pub const fn is_running(&self) -> bool {
159 self.running
160 }
161
162 pub const fn stop(&mut self) {
164 self.running = false;
165 }
166
167 pub fn poll_input(&self) -> Option<InputEvent> {
171 match self.input_rx.try_recv() {
172 Ok(event) => Some(event),
173 Err(TryRecvError::Empty) => None,
174 Err(TryRecvError::Disconnected) => {
175 Some(InputEvent::Error("Input channel disconnected".to_string()))
176 }
177 }
178 }
179
180 pub fn wait_input(&self, timeout: Duration) -> Option<InputEvent> {
182 self.input_rx.recv_timeout(timeout).ok()
183 }
184
185 pub fn drain_input(&self) -> Vec<InputEvent> {
187 let mut events = Vec::new();
188 while let Ok(event) = self.input_rx.try_recv() {
189 events.push(event);
190 }
191 events
192 }
193
194 pub fn request_redraw(&self) {
196 let _ = self.render_tx.send(RenderCommand::FullRedraw(Box::new(self.buffer.clone())));
197 }
198
199 pub fn request_update(&self) {
201 let _ = self.render_tx.send(RenderCommand::Update(Box::new(self.buffer.clone())));
202 }
203
204 pub fn set_cursor(&self, x: Option<u16>, y: u16) {
206 let _ = self.render_tx.send(RenderCommand::SetCursor { x, y });
207 }
208
209 pub fn write_raw(&self, bytes: Vec<u8>) {
211 let _ = self.render_tx.send(RenderCommand::RawOutput { bytes });
212 }
213
214 pub fn handle_resize(&mut self, width: u16, height: u16) {
216 self.width = width;
217 self.height = height;
218 self.buffer.resize(width, height);
219 let _ = self.render_tx.send(RenderCommand::Resize { width, height });
220 }
221
222 pub fn begin_frame(&mut self) {
226 self.frame_start = Instant::now();
227 }
228
229 pub fn end_frame(&mut self) {
233 self.frame_count += 1;
234
235 self.request_update();
237
238 let elapsed = self.frame_start.elapsed();
240 if elapsed < self.frame_duration {
241 std::thread::sleep(self.frame_duration - elapsed);
242 }
243 }
244
245 pub const fn frame_count(&self) -> u64 {
247 self.frame_count
248 }
249
250 pub fn set_cell(&mut self, x: u16, y: u16, cell: Cell) {
252 self.buffer.set(x, y, cell);
253 }
254
255 pub fn set_grapheme(&mut self, x: u16, y: u16, grapheme: &str, fg: Rgb, bg: Rgb) -> u8 {
257 self.buffer.set_grapheme(x, y, grapheme, fg, bg)
258 }
259
260 pub fn clear(&mut self) {
262 self.buffer.clear();
263 }
264
265 pub fn fill_rect(&mut self, rect: Rect, cell: Cell) {
267 self.buffer.fill_rect(rect.x, rect.y, rect.width, rect.height, cell);
268 }
269
270 pub fn draw_text(&mut self, x: u16, y: u16, text: &str, fg: Rgb, bg: Rgb) -> u16 {
274 let mut col = x;
275 for grapheme in unicode_segmentation::UnicodeSegmentation::graphemes(text, true) {
276 if col >= self.width {
277 break;
278 }
279 let width = self.buffer.set_grapheme(col, y, grapheme, fg, bg);
280 col += u16::from(width);
281 }
282 col - x
283 }
284}
285
286impl Drop for Engine {
287 fn drop(&mut self) {
288 if let Some(actor) = self.input_actor.take() {
290 actor.join();
291 }
292
293 let _ = self.render_tx.send(RenderCommand::Shutdown);
294
295 let mut stdout = io::stdout();
297 let _ = execute!(stdout, cursor::Show);
298 if self.config.enable_mouse {
299 let _ = execute!(stdout, crossterm::event::DisableMouseCapture);
300 }
301 if self.config.alternate_screen {
302 let _ = execute!(stdout, LeaveAlternateScreen);
303 }
304 let _ = terminal::disable_raw_mode();
305 }
306}