156 lines
4.3 KiB
Rust
156 lines
4.3 KiB
Rust
//! # Console Engine Implementation
|
|
//!
|
|
//! Provides a Console implementation of the Engine trait for rendering
|
|
//! to the terminal using crossterm.
|
|
|
|
use std::{
|
|
io::{stdout, Write},
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
|
|
|
use crate::component::{context::Context, ComponentImpl};
|
|
use crate::{
|
|
engine::{commands, CommandExecutor},
|
|
render::Area,
|
|
DrawContext, View,
|
|
};
|
|
|
|
use super::Engine;
|
|
|
|
/// Executes commands for the console engine
|
|
pub struct ConsoleExecutor {
|
|
/// Flag indicating whether the application is running
|
|
running: Mutex<bool>,
|
|
}
|
|
|
|
/// Console-based rendering engine
|
|
///
|
|
/// Renders components to the terminal using crossterm for cross-platform support.
|
|
pub struct Console {
|
|
/// Thread functions to execute
|
|
threads: Mutex<Vec<Arc<dyn Fn(Arc<Context>) + Send + Sync>>>,
|
|
/// The executor for this console
|
|
executor: Arc<ConsoleExecutor>,
|
|
}
|
|
|
|
impl Console {
|
|
/// Creates a new console engine
|
|
pub fn new() -> Self {
|
|
Self {
|
|
threads: Mutex::new(Vec::new()),
|
|
executor: Arc::new(ConsoleExecutor {
|
|
running: Mutex::new(true),
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Registers a thread function to run alongside the engine
|
|
pub fn thread<F: Fn(Arc<Context>) + Send + Sync + 'static>(&self, run: F) {
|
|
self.threads.lock().unwrap().push(Arc::new(run));
|
|
}
|
|
}
|
|
|
|
impl Engine for Console {
|
|
fn render_view(&self, area: &Area, view: &View) -> DrawContext {
|
|
let mut context = DrawContext::new(area.clone());
|
|
view(&mut context);
|
|
context
|
|
}
|
|
|
|
fn draw_context(&self, ctx: &DrawContext) {
|
|
for inst in &ctx.drawing {
|
|
match inst {
|
|
crate::render::DrawInstruction::Text(point, text) => {
|
|
let (x, y) = (ctx.area.x + point.x, ctx.area.y + point.y);
|
|
for (i, line) in text.lines().enumerate() {
|
|
execute!(stdout(), MoveTo(x, y + (i as u16))).unwrap();
|
|
print!("{line}");
|
|
}
|
|
stdout().flush().unwrap();
|
|
}
|
|
crate::render::DrawInstruction::Child(_point, child) => self.draw_context(child),
|
|
crate::render::DrawInstruction::View(area, view) => {
|
|
self.draw_context(&self.render_view(area, view))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render(&self, cx: &Arc<Context>) {
|
|
let (width, height) = crossterm::terminal::size().unwrap();
|
|
|
|
execute!(stdout(), Clear(crossterm::terminal::ClearType::Purge)).unwrap();
|
|
execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap();
|
|
|
|
self.draw_context(&self.render_view(
|
|
&Area {
|
|
x: 0,
|
|
y: 0,
|
|
width,
|
|
height,
|
|
},
|
|
&cx.get_view(),
|
|
));
|
|
}
|
|
|
|
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context> {
|
|
crossterm::execute!(std::io::stdout(), crossterm::cursor::Hide).unwrap();
|
|
|
|
let cx = Context::new(component, self.executor.clone());
|
|
cx.refresh();
|
|
|
|
for thread in self.threads.lock().unwrap().iter() {
|
|
let thread = thread.clone();
|
|
|
|
std::thread::spawn({
|
|
let cx = cx.clone();
|
|
move || thread(cx)
|
|
});
|
|
}
|
|
|
|
cx
|
|
}
|
|
|
|
fn executor(&self) -> Arc<dyn super::CommandExecutor> {
|
|
self.executor.clone()
|
|
}
|
|
|
|
fn run<F: ComponentImpl + 'static>(&self, component: F) -> crate::Result<()> {
|
|
let cx = self.init(component);
|
|
|
|
while self.executor.is_running() {
|
|
self.render(&cx);
|
|
self.render_delay();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ConsoleExecutor {
|
|
/// Checks if the engine is still running
|
|
pub fn is_running(self: &Arc<ConsoleExecutor>) -> bool {
|
|
*self.running.lock().unwrap()
|
|
}
|
|
|
|
/// Stops the engine
|
|
pub fn stop(&self) -> crate::Result<()> {
|
|
*self.running.lock()? = false;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl CommandExecutor for ConsoleExecutor {
|
|
fn execute_command(&self, command: &Arc<dyn super::Command>) -> crate::Result<()> {
|
|
let command = command.as_any();
|
|
|
|
if let Some(commands::Stop) = command.downcast_ref() {
|
|
self.stop()?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|