doc strings and doc tests
This commit is contained in:
parent
aaa51179ad
commit
3388ae4096
20
Cargo.toml
20
Cargo.toml
|
@ -3,7 +3,25 @@ name = "arkham"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[[example]]
|
||||||
|
name = "simple"
|
||||||
|
path = "examples/simple.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "component_functions"
|
||||||
|
path = "examples/component_functions.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "theme"
|
||||||
|
path = "examples/theme.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "component_params"
|
||||||
|
path = "examples/component_params.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "keyboard"
|
||||||
|
path = "examples/keyboard.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
use arkham::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new(root).run().expect("couldnt launch app");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root(ctx: &mut ViewContext) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.fill(size, Rune::new().bg(Color::DarkGrey));
|
||||||
|
ctx.component(Rect::new((10, 10), (20, 1)), hello_world);
|
||||||
|
ctx.component(Rect::new(0, (size.width, 1)), quit_nag);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hello_world(ctx: &mut ViewContext) {
|
||||||
|
ctx.insert(0, "Hello World");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quit_nag(ctx: &mut ViewContext) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.insert(
|
||||||
|
((size.width / 2) - 7, 0),
|
||||||
|
"Press Q to Quit".to_runes().fg(Color::Red),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
use arkham::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new(root).run().expect("couldnt launch app");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root(ctx: &mut ViewContext) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.fill(size, Rune::new().bg(Color::DarkGrey));
|
||||||
|
ctx.component(Rect::new((10, 10), (20, 1)), say_hello("Alice"));
|
||||||
|
ctx.component(Rect::new(0, (size.width, 1)), quit_nag);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn say_hello(name: &'static str) -> impl Fn(&mut ViewContext) {
|
||||||
|
move |ctx: &mut ViewContext| {
|
||||||
|
ctx.insert((0, 0), format!("Hello, {}", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quit_nag(ctx: &mut ViewContext) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.insert(
|
||||||
|
((size.width / 2) - 7, 0),
|
||||||
|
"Press Q to Quit".to_runes().fg(Color::Red),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
use arkham::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new(root).run().expect("couldnt launch app");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root(ctx: &mut ViewContext) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.fill(size, Rune::new().bg(Color::DarkGrey));
|
||||||
|
ctx.component(Rect::new((10, 10), (30, 1)), hello_world);
|
||||||
|
ctx.component(Rect::new((10, 11), (20, 1)), show_key_press);
|
||||||
|
ctx.component(Rect::new(0, (size.width, 1)), quit_nag);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hello_world(ctx: &mut ViewContext) {
|
||||||
|
ctx.insert(0, "Hello World, Press a key");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_key_press(ctx: &mut ViewContext, kb: Res<Keyboard>) {
|
||||||
|
if let Some(c) = kb.char() {
|
||||||
|
ctx.insert(0, format!("Key press: {}", c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quit_nag(ctx: &mut ViewContext) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.insert(
|
||||||
|
((size.width / 2) - 7, 0),
|
||||||
|
"Press Q to Quit".to_runes().fg(Color::Red),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
use arkham::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new(root).run().expect("couldnt launch app");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root(ctx: &mut ViewContext) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.fill(size, Rune::new().bg(Color::DarkGrey));
|
||||||
|
ctx.insert((10, 10), "Hello World");
|
||||||
|
ctx.insert(
|
||||||
|
((size.width / 2) - 7, 0),
|
||||||
|
"Press Q to Quit".to_runes().fg(Color::Red),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
use arkham::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new(root)
|
||||||
|
.insert_resource(Theme::default())
|
||||||
|
.run()
|
||||||
|
.expect("couldnt launch app");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root(ctx: &mut ViewContext, theme: Res<Theme>) {
|
||||||
|
let size = ctx.size();
|
||||||
|
ctx.paint(size, theme.bg_primary);
|
||||||
|
ctx.paint(Rect::new((5, 5), size - 10), theme.bg_secondary);
|
||||||
|
ctx.insert((10, 10), "Hello World");
|
||||||
|
ctx.insert(
|
||||||
|
((size.width / 2) - 7, 0),
|
||||||
|
"Press Q to Quit".to_runes().fg(theme.fg),
|
||||||
|
);
|
||||||
|
}
|
57
src/app.rs
57
src/app.rs
|
@ -2,7 +2,7 @@ use std::{any::Any, cell::RefCell, io::Write, marker::PhantomData, rc::Rc};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor,
|
cursor,
|
||||||
event::{Event, KeyCode, KeyEventKind, KeyModifiers},
|
event::{Event, KeyCode, KeyEventKind},
|
||||||
execute, queue, terminal,
|
execute, queue, terminal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ use crate::{
|
||||||
|
|
||||||
use super::input::Keyboard;
|
use super::input::Keyboard;
|
||||||
|
|
||||||
|
/// The app is the core container for the application logic, resources,
|
||||||
|
/// state, and run loop.
|
||||||
pub struct App<F, Args>
|
pub struct App<F, Args>
|
||||||
where
|
where
|
||||||
F: Callable<Args>,
|
F: Callable<Args>,
|
||||||
|
@ -30,6 +32,9 @@ where
|
||||||
F: Callable<Args>,
|
F: Callable<Args>,
|
||||||
Args: FromContainer,
|
Args: FromContainer,
|
||||||
{
|
{
|
||||||
|
/// Constructs a new App objcet. This object uses a builder pattern and
|
||||||
|
/// should be finalized with App::run(). which will start a blocking run
|
||||||
|
/// loop and perform the initial screen setup and render.
|
||||||
pub fn new(root: F) -> App<F, Args> {
|
pub fn new(root: F) -> App<F, Args> {
|
||||||
let container = Rc::new(RefCell::new(Container::default()));
|
let container = Rc::new(RefCell::new(Container::default()));
|
||||||
let size = terminal::size().unwrap();
|
let size = terminal::size().unwrap();
|
||||||
|
@ -42,26 +47,76 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to change the root function that is used during each render cycle.
|
||||||
pub fn change_root(&mut self, root: F) {
|
pub fn change_root(&mut self, root: F) {
|
||||||
self.root = root;
|
self.root = root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a resource which can be injected into component functions.
|
||||||
|
///
|
||||||
|
/// This resource can only be accessed immutably by reference.
|
||||||
|
/// Interior mutability must be used for anything that requires an internal
|
||||||
|
/// state.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// use arkham::prelude::*;
|
||||||
|
/// struct MyResource {
|
||||||
|
/// value: i32
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// App::new(root).insert_resource(MyResource { value: 12 });
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn root(ctx: &mut ViewContext, thing: Res<MyResource>) {
|
||||||
|
/// ctx.insert(0,format!("Value is {}", thing.value));
|
||||||
|
/// }
|
||||||
|
/// ````
|
||||||
|
/// Alternatively, App::insert_state can be used to insert a state object,
|
||||||
|
/// that can be borrowed mutable.
|
||||||
pub fn insert_resource<T: Any>(self, v: T) -> Self {
|
pub fn insert_resource<T: Any>(self, v: T) -> Self {
|
||||||
self.container.borrow_mut().bind(Res::new(v));
|
self.container.borrow_mut().bind(Res::new(v));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a stateful object that can be injected into component functions
|
||||||
|
/// unlike App::insert_resource, this value can be borrowed mutably and
|
||||||
|
/// is meant to store application state.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// use arkham::prelude::*;
|
||||||
|
/// struct MyState {
|
||||||
|
/// value: i32
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// App::new(root).insert_state(MyState { value: 12 });
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn root(ctx: &mut ViewContext, thing: State<MyState>) {
|
||||||
|
/// thing.get_mut().value += 1;
|
||||||
|
/// ctx.insert(0,format!("Value is {}", thing.get().value));
|
||||||
|
/// }
|
||||||
|
/// ````
|
||||||
pub fn insert_state<T: Any>(self, v: T) -> Self {
|
pub fn insert_state<T: Any>(self, v: T) -> Self {
|
||||||
self.container.borrow_mut().bind(State::new(v));
|
self.container.borrow_mut().bind(State::new(v));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Repairs the terminal state so it operates properly.
|
||||||
fn teardown(&self) {
|
fn teardown(&self) {
|
||||||
let mut out = std::io::stdout();
|
let mut out = std::io::stdout();
|
||||||
let _ = terminal::disable_raw_mode();
|
let _ = terminal::disable_raw_mode();
|
||||||
let _ = execute!(out, terminal::LeaveAlternateScreen, cursor::Show);
|
let _ = execute!(out, terminal::LeaveAlternateScreen, cursor::Show);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the main run loop. This should be called to start the
|
||||||
|
/// application logic.
|
||||||
|
///
|
||||||
|
/// This function will block while it reads events and performs render
|
||||||
|
/// cycles.
|
||||||
pub fn run(&mut self) -> anyhow::Result<()> {
|
pub fn run(&mut self) -> anyhow::Result<()> {
|
||||||
self.container.borrow_mut().bind(Res::new(Terminal));
|
self.container.borrow_mut().bind(Res::new(Terminal));
|
||||||
self.container.borrow_mut().bind(Res::new(Keyboard::new()));
|
self.container.borrow_mut().bind(Res::new(Keyboard::new()));
|
||||||
|
|
|
@ -12,50 +12,75 @@ pub enum ArkhamState {
|
||||||
Noop,
|
Noop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The container stores typed resource and state objects and provides
|
||||||
|
/// them to component functions.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
bindings: HashMap<TypeId, Box<dyn Any>>,
|
bindings: HashMap<TypeId, Box<dyn Any>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
pub fn bind<T: Any>(&mut self, val: T) {
|
/// insert a type binding into the container. This is used to provide an
|
||||||
|
/// object to functions executed by Container::call.
|
||||||
|
///
|
||||||
|
/// App::insert_ressource and App::isnert_state proxies to this function.
|
||||||
|
pub(crate) fn bind<T: Any>(&mut self, val: T) {
|
||||||
self.bindings.insert(val.type_id(), Box::new(val));
|
self.bindings.insert(val.type_id(), Box::new(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an object from the store by its type. This is a utility function
|
||||||
|
/// to extract an object directly, instead of using the container to
|
||||||
|
/// inject objects into a function's arguments.
|
||||||
pub fn get<T: Any>(&self) -> Option<&T> {
|
pub fn get<T: Any>(&self) -> Option<&T> {
|
||||||
self.bindings
|
self.bindings
|
||||||
.get(&TypeId::of::<T>())
|
.get(&TypeId::of::<T>())
|
||||||
.and_then(|boxed| boxed.downcast_ref())
|
.and_then(|boxed| boxed.downcast_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call a function while providing dependency injcetion.
|
||||||
pub fn call<F, Args>(&self, view: &mut ViewContext, callable: &F)
|
pub fn call<F, Args>(&self, view: &mut ViewContext, callable: &F)
|
||||||
where
|
where
|
||||||
F: Callable<Args>,
|
F: Callable<Args>,
|
||||||
Args: FromContainer,
|
Args: FromContainer,
|
||||||
{
|
{
|
||||||
callable.call(view, Args::from_container(self));
|
let _ = callable.call(view, Args::from_container(self));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Callable<Args> {
|
/// A wrapper for state objcets. This internally holds a reference counted
|
||||||
fn call(&self, view: &mut ViewContext, args: Args) -> ArkhamResult;
|
/// poitner to the object and is used when injecting itno functions.
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FromContainer {
|
|
||||||
fn from_container(container: &Container) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct State<T: ?Sized>(Rc<RefCell<T>>);
|
pub struct State<T: ?Sized>(Rc<RefCell<T>>);
|
||||||
|
|
||||||
impl<T> State<T> {
|
impl<T> State<T> {
|
||||||
|
/// Create a new state wrapper.
|
||||||
pub fn new(val: T) -> Self {
|
pub fn new(val: T) -> Self {
|
||||||
State(Rc::new(RefCell::new(val)))
|
State(Rc::new(RefCell::new(val)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the underlying state object.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// use arkham::prelude::*;
|
||||||
|
/// struct MyState(i32);
|
||||||
|
///
|
||||||
|
/// let state = State::new(MyState(4));
|
||||||
|
/// state.get_mut().0 = 6;
|
||||||
|
/// assert_eq!(state.get().0, 6);
|
||||||
|
/// ```
|
||||||
pub fn get_mut(&self) -> std::cell::RefMut<T> {
|
pub fn get_mut(&self) -> std::cell::RefMut<T> {
|
||||||
RefCell::borrow_mut(&self.0)
|
RefCell::borrow_mut(&self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns an immutable reference to the underlying state object.
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// use arkham::prelude::*;
|
||||||
|
/// struct MyState(i32);
|
||||||
|
///
|
||||||
|
/// let state = State::new(MyState(4));
|
||||||
|
/// assert_eq!(state.get().0, 4);
|
||||||
|
/// ```
|
||||||
pub fn get(&self) -> std::cell::Ref<T> {
|
pub fn get(&self) -> std::cell::Ref<T> {
|
||||||
RefCell::borrow(&self.0)
|
RefCell::borrow(&self.0)
|
||||||
}
|
}
|
||||||
|
@ -73,6 +98,9 @@ impl<T: ?Sized + 'static> FromContainer for State<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper for resources stored within the app. This wrapper is returned
|
||||||
|
/// when objects are injected into component functions and provide immutable
|
||||||
|
/// access
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Res<T: ?Sized>(Rc<T>);
|
pub struct Res<T: ?Sized>(Rc<T>);
|
||||||
|
|
||||||
|
@ -119,6 +147,19 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Callable must be implemented for functions that can be used as component
|
||||||
|
/// functions. They are given a ViewContext for the component function and
|
||||||
|
/// injectable arguments.
|
||||||
|
pub trait Callable<Args> {
|
||||||
|
fn call(&self, view: &mut ViewContext, args: Args) -> ArkhamResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FromContainer must be implmented for objects that can be injected into
|
||||||
|
/// component functions. This includes the Res and State structs.
|
||||||
|
pub trait FromContainer {
|
||||||
|
fn from_container(container: &Container) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
impl FromContainer for () {
|
impl FromContainer for () {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_container(_container: &Container) -> Self {}
|
fn from_container(_container: &Container) -> Self {}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crate::{
|
use crate::container::{Callable, FromContainer};
|
||||||
container::{Callable, FromContainer},
|
|
||||||
widget::Widget,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
container::Container,
|
container::Container,
|
||||||
|
@ -12,6 +9,9 @@ use super::{
|
||||||
view::View,
|
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 struct ViewContext {
|
||||||
pub view: View,
|
pub view: View,
|
||||||
pub container: Rc<RefCell<Container>>,
|
pub container: Rc<RefCell<Container>>,
|
||||||
|
@ -32,28 +32,34 @@ impl std::ops::Deref for ViewContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewContext {
|
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 {
|
pub fn new(container: Rc<RefCell<Container>>, size: Size) -> Self {
|
||||||
let view = View::new(size);
|
let view = View::new(size);
|
||||||
|
|
||||||
Self { view, container }
|
Self { view, container }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn component<F, Args>(&mut self, rect: Rect, f: F)
|
/// 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
|
where
|
||||||
F: Callable<Args>,
|
F: Callable<Args>,
|
||||||
Args: FromContainer,
|
Args: FromContainer,
|
||||||
|
R: Into<Rect>,
|
||||||
{
|
{
|
||||||
|
let rect = rect.into();
|
||||||
let mut context = ViewContext::new(self.container.clone(), rect.size);
|
let mut context = ViewContext::new(self.container.clone(), rect.size);
|
||||||
self.container.borrow().call(&mut context, &f);
|
self.container.borrow().call(&mut context, &f);
|
||||||
self.view.apply(rect.pos, context.view);
|
self.view.apply(rect.pos, context.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(&mut self, rect: Rect, mut widget: impl Widget) {
|
/// Set a specific rune to a specific position. This function can be used
|
||||||
let mut context = ViewContext::new(self.container.clone(), rect.size);
|
/// to set a signle character. To set multiple runes at a time see the
|
||||||
widget.ui(&mut context);
|
/// View::insert function.
|
||||||
self.view.apply(rect.pos, context.view);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_rune<P>(&mut self, pos: P, rune: Rune)
|
pub fn set_rune<P>(&mut self, pos: P, rune: Rune)
|
||||||
where
|
where
|
||||||
P: Into<Pos>,
|
P: Into<Pos>,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
|
/// Pos represents a coordinate position within the termianl screen.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Pos {
|
pub struct Pos {
|
||||||
pub x: usize,
|
pub x: usize,
|
||||||
|
@ -21,6 +22,7 @@ impl From<usize> for Pos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An area that can be operated on.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
|
@ -98,6 +100,8 @@ impl From<(i32, i32)> for Size {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An area of the screen with a given size and postiion. The position
|
||||||
|
/// represents the top-left corner of the rectangle.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub pos: Pos,
|
pub pos: Pos,
|
||||||
|
|
|
@ -2,6 +2,9 @@ use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
|
|
||||||
|
/// Keyboard can be used as an injectable resource that provides information
|
||||||
|
/// about the current keyboard state. This is the primary mechanism by which
|
||||||
|
/// applications can respond to keyboard input from users.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Keyboard {
|
pub struct Keyboard {
|
||||||
key: Rc<RefCell<Option<KeyCode>>>,
|
key: Rc<RefCell<Option<KeyCode>>>,
|
||||||
|
|
|
@ -7,7 +7,6 @@ mod runes;
|
||||||
pub mod symbols;
|
pub mod symbols;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod view;
|
mod view;
|
||||||
mod widget;
|
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{
|
pub use super::{
|
||||||
|
@ -18,7 +17,6 @@ pub mod prelude {
|
||||||
input::Keyboard,
|
input::Keyboard,
|
||||||
runes::{Rune, Runes, ToRuneExt},
|
runes::{Rune, Runes, ToRuneExt},
|
||||||
theme::Theme,
|
theme::Theme,
|
||||||
widget::Widget,
|
|
||||||
};
|
};
|
||||||
pub use crossterm::event::KeyCode;
|
pub use crossterm::event::KeyCode;
|
||||||
pub use crossterm::style::Color;
|
pub use crossterm::style::Color;
|
||||||
|
|
|
@ -3,6 +3,8 @@ use crossterm::{
|
||||||
style::{Attribute, Color, Print, SetAttribute, SetBackgroundColor, SetForegroundColor},
|
style::{Attribute, Color, Print, SetAttribute, SetBackgroundColor, SetForegroundColor},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Rune repesents the state of the screen at a specific position. It stores
|
||||||
|
/// the character content and styling information that will be rendered.
|
||||||
#[derive(Clone, Copy, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Default, Eq, PartialEq)]
|
||||||
pub struct Rune {
|
pub struct Rune {
|
||||||
pub content: Option<char>,
|
pub content: Option<char>,
|
||||||
|
@ -61,6 +63,8 @@ impl Rune {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runes represetns a series of runes. This is generally used to convert
|
||||||
|
/// strings into Runes and apply styling information to them.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Runes(Vec<Rune>);
|
pub struct Runes(Vec<Rune>);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
use crossterm::style::Color;
|
use crossterm::style::Color;
|
||||||
|
|
||||||
|
/// Theme is a simple theme provider. This structure is nothing special. It
|
||||||
|
/// simply holds some general styling information and can be inserted as a
|
||||||
|
/// resource into the application.
|
||||||
|
///
|
||||||
|
/// If you would like to use different style names just make your own structure
|
||||||
|
/// which meets your needs and add it as a resource with App::insert_resource.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
pub bg_primary: Color,
|
pub bg_primary: Color,
|
||||||
|
|
37
src/view.rs
37
src/view.rs
|
@ -1,8 +1,13 @@
|
||||||
|
use crossterm::style::Color;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{Pos, Rect, Size},
|
geometry::{Pos, Rect, Size},
|
||||||
runes::{Rune, Runes},
|
runes::{Rune, Runes},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A renderable region. View stores the renderable state of an area of the
|
||||||
|
/// screen. Views can be combined together to achieve a finalized view that
|
||||||
|
/// repsresents the entire screens next render.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct View(pub Vec<Vec<Rune>>);
|
pub struct View(pub Vec<Vec<Rune>>);
|
||||||
|
|
||||||
|
@ -21,6 +26,7 @@ impl std::ops::Deref for View {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
|
/// Construct a new view for a given region size.
|
||||||
pub fn new<T>(size: T) -> Self
|
pub fn new<T>(size: T) -> Self
|
||||||
where
|
where
|
||||||
T: Into<Size>,
|
T: Into<Size>,
|
||||||
|
@ -29,10 +35,12 @@ impl View {
|
||||||
Self(vec![vec![Rune::default(); size.width]; size.height])
|
Self(vec![vec![Rune::default(); size.width]; size.height])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return an iterator for all runes in the view.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &Vec<Rune>> {
|
pub fn iter(&self) -> impl Iterator<Item = &Vec<Rune>> {
|
||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply another view onto this view at a given position.
|
||||||
pub fn apply<P: Into<Pos>>(&mut self, pos: P, view: View) {
|
pub fn apply<P: Into<Pos>>(&mut self, pos: P, view: View) {
|
||||||
let pos = pos.into();
|
let pos = pos.into();
|
||||||
for (y, line) in view.0.iter().enumerate() {
|
for (y, line) in view.0.iter().enumerate() {
|
||||||
|
@ -46,16 +54,38 @@ impl View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The width of the view.
|
||||||
pub fn width(&self) -> usize {
|
pub fn width(&self) -> usize {
|
||||||
self.0.first().unwrap().len()
|
self.0.first().unwrap().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The height of the view.
|
||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The region size of the view.
|
||||||
pub fn size(&self) -> Size {
|
pub fn size(&self) -> Size {
|
||||||
(self.width(), self.height()).into()
|
(self.width(), self.height()).into()
|
||||||
}
|
}
|
||||||
pub fn fill(&mut self, rect: Rect, rune: Rune) {
|
|
||||||
|
/// Paint is a conveinence method for filling a region ith a given color.
|
||||||
|
/// This is done by using the passed color as the background color and
|
||||||
|
/// filling the region with ' ' characters.
|
||||||
|
pub fn paint<R>(&mut self, rect: R, color: Color)
|
||||||
|
where
|
||||||
|
R: Into<Rect>,
|
||||||
|
{
|
||||||
|
self.fill(rect, Rune::new().content(' ').bg(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill a region of the view with a single rune, repeating it in every
|
||||||
|
/// position.
|
||||||
|
pub fn fill<R>(&mut self, rect: R, rune: Rune)
|
||||||
|
where
|
||||||
|
R: Into<Rect>,
|
||||||
|
{
|
||||||
|
let rect = rect.into();
|
||||||
for y in rect.pos.y..(rect.size.height + rect.pos.y).min(self.0.len()) {
|
for y in rect.pos.y..(rect.size.height + rect.pos.y).min(self.0.len()) {
|
||||||
for x in rect.pos.x..(rect.size.width + rect.pos.x).min(self.0[y].len()) {
|
for x in rect.pos.x..(rect.size.width + rect.pos.x).min(self.0[y].len()) {
|
||||||
let _ = std::mem::replace(&mut self.0[y][x], rune);
|
let _ = std::mem::replace(&mut self.0[y][x], rune);
|
||||||
|
@ -63,6 +93,11 @@ impl View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a string at the specific position in the view. Each chacter is
|
||||||
|
/// mapped to a rune and placed starting at the position given and
|
||||||
|
/// continueing to the right
|
||||||
|
///
|
||||||
|
/// This function performs no wrapping of any kind.
|
||||||
pub fn insert<P: Into<Pos>, S: Into<Runes>>(&mut self, pos: P, value: S) {
|
pub fn insert<P: Into<Pos>, S: Into<Runes>>(&mut self, pos: P, value: S) {
|
||||||
let Pos { x, y } = pos.into();
|
let Pos { x, y } = pos.into();
|
||||||
let runes: Runes = value.into();
|
let runes: Runes = value.into();
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
use crate::prelude::{Res, Runes, Theme, ViewContext};
|
|
||||||
|
|
||||||
pub trait Widget {
|
|
||||||
fn ui(&mut self, ctx: &mut ViewContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list(items: Vec<Runes>, selection_index: usize) -> impl FnOnce(&mut ViewContext, Res<Theme>) {
|
|
||||||
move |ctx: &mut ViewContext, theme: Res<Theme>| {
|
|
||||||
for (idx, item) in items.into_iter().enumerate() {
|
|
||||||
ctx.insert((0, idx), item.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue