arkham 0.2.1

TUI made simple
Documentation
use std::{cell::RefCell, rc::Rc};

use crate::{
    container::{Callable, FromContainer},
    stack::Stack,
};

use super::{
    container::Container,
    geometry::{Pos, Rect, Size},
    runes::Rune,
    view::View,
};

/// ViewContext represents the display context for a given area.
/// it maintains the drawing state for the region internally and is used
/// to generate a final view that is eventually rendered.
pub struct ViewContext {
    pub view: View,
    pub container: Rc<RefCell<Container>>,
    pub(crate) rerender: bool,
}

impl std::ops::DerefMut for ViewContext {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.view
    }
}

impl std::ops::Deref for ViewContext {
    type Target = View;

    fn deref(&self) -> &Self::Target {
        &self.view
    }
}

impl ViewContext {
    /// Constructs a new ViewConext for a given area.  A container reference
    /// must also be passedo, so that component functions called
    /// from the context are injectable.
    pub fn new(container: Rc<RefCell<Container>>, size: Size) -> Self {
        let view = View::new(size);

        Self {
            view,
            container,
            rerender: false,
        }
    }

    /// Notify the application to rerender the view. This is useful after a
    /// state change that might affect other views.  
    pub fn render(&mut self) {
        self.rerender = true;
    }

    pub fn vertical_stack<S>(&self, size: S) -> Stack
    where
        S: Into<Size>,
    {
        Stack {
            direction: crate::stack::StackDirection::Vertical,
            container: self.container.clone(),
            view: View::new(size.into()),
            position: Pos::from(0),
            alignment: crate::stack::StackAlignment::Top,
        }
    }

    pub fn horizontal_stack<S>(&self, size: S) -> Stack
    where
        S: Into<Size>,
    {
        Stack {
            direction: crate::stack::StackDirection::Horizontal,
            container: self.container.clone(),
            view: View::new(size.into()),
            position: Pos::from(0),
            alignment: crate::stack::StackAlignment::Left,
        }
    }

    /// Execute a component function. The passed function will receive a new
    /// ViewContext for its size and can be injected with arguments.
    /// The context given to the component function will then be applied to
    /// the parent ViewContext at a given position.
    pub fn component<F, Args, R>(&mut self, rect: R, f: F)
    where
        F: Callable<Args>,
        Args: FromContainer,
        R: Into<Rect>,
    {
        let rect = rect.into();
        let mut context = ViewContext::new(self.container.clone(), rect.size);
        let args = Args::from_container(&self.container.borrow());
        f.call(&mut context, args);
        self.view.apply(rect.pos, &context.view);
        self.rerender = context.rerender;
    }

    /// Set a specific rune to a specific position. This function can be used
    /// to set a signle character. To set multiple runes at a time see the
    /// View::insert function.
    pub fn set_rune<P>(&mut self, pos: P, rune: Rune)
    where
        P: Into<Pos>,
    {
        let Pos { x, y } = pos.into();
        if let Some(r) = self.view.get_mut(y).and_then(|row| row.get_mut(x)) {
            *r = rune;
        }
    }
}

#[cfg(test)]
pub mod tests {
    use std::{cell::RefCell, rc::Rc};

    use crate::container::Container;

    use super::ViewContext;

    pub fn context_fixture() -> ViewContext {
        ViewContext::new(Rc::new(RefCell::new(Container::default())), (20, 20).into())
    }
}