icmd 0.1.0

A command-line software framework
Documentation
//! Module for the ICmd Console.
//!
//! This module contains the implementation of the Console which manages rendering,
//! event loops, and interactions within the ICmd application.

use std::{
    any::{Any, TypeId},
    mem,
    sync::Arc,
    thread,
    time::Duration,
};

use crate::{
    canvas::Canvas,
    core::interactor::{Dispatcher, GeneralListener, Interactor},
    node::HasListener,
    style::GeneralStyle,
    utils::Lazy,
};
use crossterm::terminal;
use downcast_trait::{downcast_trait_impl_convert_to, DowncastTrait};
use parking_lot::{Mutex, RwLock};

use crate::{
    core::{
        looper::Looper,
        renderer::{FrameBuffer, Renderable, Renderer},
        ICmdWindow,
    },
    measure::Fixed,
    node::{LayoutAttribute, Node, StyleAttribute},
};

/// Represents a console in the ICmd application.
/// It handles rendering, event dispatching, interactions, and maintains its context.
#[derive(Debug)]
pub struct Console {
    pub name: String,
    pub children: RwLock<Vec<Arc<dyn Node>>>,
    renderer: Renderer,
    looper: Looper,
    interactor: Interactor,
    dispatcher: Dispatcher,
    context: ConsoleContext,
    listeners: RwLock<Vec<GeneralListener>>,
    layout_attributes: RwLock<LayoutAttribute>,
    visibility: RwLock<bool>,
}

impl Console {
    /// Creates a new instance of Console with the given name.
    /// An atomic reference-counted instance of Console.
    pub fn new(name: &'static str) -> Arc<Self> {
        let window = Arc::new(RwLock::new(ICmdWindow::new_with_name(name)));
        let window_read = window.read();
        let (console_width, console_height) = window_read.get_size();
        let background = window_read.bg_color.clone();
        let fps = window_read.fps;
        drop(window_read);
        Arc::new(Console {
            name: name.to_string(),
            children: RwLock::new(vec![]),
            renderer: Renderer::new(window.clone()),
            looper: Looper::new(window.clone(), fps),
            interactor: Interactor::new(window.clone()),
            dispatcher: Dispatcher::new(window.clone()),
            context: ConsoleContext {
                console_width,
                console_height,
                background,
                fps,
            },
            listeners: RwLock::new(vec![]),
            layout_attributes: RwLock::new(LayoutAttribute {
                l: Fixed(0).into(),
                t: Fixed(0).into(),
                r: Lazy::new(|| {
                    let (width, _) = terminal::size().unwrap();
                    width as i32
                }),
                b: Lazy::new(|| {
                    let (_, height) = terminal::size().unwrap();
                    height as i32
                }),
            }),
            visibility: RwLock::new(true),
        })
    }

    /// Returns a copy of the console context.
    pub fn context(&self) -> ConsoleContext {
        self.context.clone()
    }

    /// Renders the console using its renderer.
    pub fn print(self: &Arc<Self>) {
        self.renderer.render(self.clone());
    }

    /// Starts the console by launching renderer, looper, dispatcher and interactor.
    /// It spawns separate threads for rendering, looping, and dispatching events.
    pub fn start(self: Arc<Self>) {
        // Wait for the console to be ready
        thread::sleep(Duration::from_millis(200));

        let arc = self.clone();
        thread::spawn(move || {
            arc.renderer.launch(arc.clone());
        });

        let arc = self.clone();
        thread::spawn(move || {
            arc.looper.launch();
        });

        let arc = self.clone();
        thread::spawn(move || {
            arc.dispatcher.launch(arc.clone());
        });

        self.interactor.launch();
    }
}

/// A structure containing contextual information for the Console.
#[derive(Debug, Clone)]
pub struct ConsoleContext {
    /// Console width.
    pub console_width: u16,
    /// Console height.
    pub console_height: u16,
    /// Background color.
    pub background: String,
    /// Frames per second.
    pub fps: u16,
}

impl Renderable for Console {
    fn render(&self) -> FrameBuffer {
        let style = &self.context.background;
        Canvas::new(self)
            .fill(' ', GeneralStyle::new(style.clone()))
            .finish()
    }

    fn get_style_attributes(&self) -> Option<&Mutex<StyleAttribute>> {
        None
    }

    fn get_layout_attributes(&self) -> &RwLock<LayoutAttribute> {
        &self.layout_attributes
    }

    fn children(&self) -> &RwLock<Vec<Arc<dyn Node>>> {
        &self.children
    }
    fn parent(&self) -> Option<&Arc<dyn Node>> {
        None
    }

    fn get_visibility(&self) -> &RwLock<bool> {
        &self.visibility
    }
}

impl Node for Console {}

impl HasListener for Console {
    fn get_listeners(&self) -> &RwLock<Vec<GeneralListener>> {
        &self.listeners
    }
}

impl DowncastTrait for Console {
    downcast_trait_impl_convert_to!(dyn HasListener);
}