Documentation

Added documentation to many public functions

Added Codex documentaiton for the project as a whole

Added build pipline for generating codex site
This commit is contained in:
Joe Bellus 2024-04-19 01:43:03 -04:00
parent 6abfc6aa54
commit c3a14d6c33
15 changed files with 520 additions and 24 deletions

40
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: Publish Documentation
on:
workflow_dispatch:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Build
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/5Sigma/codex/releases/latest/download/Codex-installer.sh | sh
codex -r docs build
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/dist
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

15
docs/codex.yml Normal file
View File

@ -0,0 +1,15 @@
# Project name
name: Arkham CLI Library
# The author displayed on the title page during PDF generation
author: ~
# The path relative to the project root where the compiled static site files
# will be placed
build_path: dist
# The URL to the code repository for the project. If specified a link will appear
# in the site header.
repo_url: https://github.com/5sigma/arkham
# The URL to the main project page. If specified a home link will be displayed
# in the header.
project_url: ~
# Base URL can be set if the site is hosted in a sub path.
base_url: /arkham

2
docs/index.md Normal file
View File

@ -0,0 +1,2 @@
# Document Title

102
docs/overview/components.md Normal file
View File

@ -0,0 +1,102 @@
---
title: Designing Components
subtitle: Overview
menu_position: 1
---
# Component overview
A component in Arkham is a piece of rendering logic that could be reusable, or could be used for organization.
They are simple functions that accept a `ViewContext` reference, alongside any injected objects. See [Dependency Injection](resources)
A simple component might look like this:
```Rust
fn seperator(ctx: &mut ViewContext) {
let width = ctx.size().width;
let sep_chars = "-".repeat(ctx.size().width);
ctx.insert(sep_chars);
}
```
## Components with parameters
Reusable components will need the ability to pass parameters into them. This is done by returning the component function from within another function.
```Rust
fn name_field(name: &str) -> impl Fn(&mut ViewContext) {
move |ctx:&mut ViewContext| {
ctx.insert((0,0), "Name:");
ctx.insert((10,0), name);
}
}
```
## Understanding component sizing and positioning
When a component is used it is allocated a specific `Rect` that it can render to.
The total dimensions for the component are available in `ViewContext::size()`.
Components are also rendered at a specific position (the upper left corner).
Inside a component its coordinates are relative to itself, its upper left corner is (0,0).
## Using components
A component can render other components inside them.
In this example we can also see the component positioning and sizing.
The first parameter to `ViewContext::component` is a Rect for the component.
This is the position in the parent component it will render its top left corner
to and the size the component is allowed to render to.
The first parameter to `Rect::new` is the `Pos` for the rect, the coordinates
of its top left corner. From the perspective of the `field` component,
the content is inserted at y=0. However, when the component is placed in
the `container` component, its upper left coordinate is specified and the
coordinates of the `field` component become relative to the specified position
in the `container` component.
The second parameter to `Rect::new` is the `Size` of the `Rect`. Its width and height.
```Rust
fn field(key: &str, value: &str) -> impl Fn(&mut ViewContext) {
move |ctx: &mut ViewContext| {
ctx.insert((0,0), format!("{}:", key));
ctx.insert((10,0), value);
}
}
fn container(ctx: &mut ViewContext) {
ctx.component(Rect::new((0,0), (10,1)), field("Name", "Alice")
ctx.component(Rect::new((0,1), (10,1)), field("Age", "22")
}
```
The `container` component will render to the following:
```
Name: Alice
Age: 22
```
## Components as closures
When you need something more complex than `ViewContext::insert`, but don't want to
build a whole separate component. Components can be defined inline using a closure.
```Rust
fn container(ctx: &mut ViewContext) {
let size = ctx.size();
ctx.component(
Rect::new((0,0), (size.width, size.height /2))
|ctx: &mut ViewContext| {
ctx.fill_all(Color::Blue);
}
);
}
```

View File

@ -0,0 +1,65 @@
---
title: Getting Started
subtitle: Overview
menu_position: 0
---
# Starting a new project
To setup a new project use `cargo new` and `cargo add arkham` to add the dependency.
```Shell
cargo new my_project
cd my_project
cago add arkham
```
# Import the arkham prelude
Add the following line to the top of _my_project/src/main.rs_ to import all the Arkham members.
```rust
use arkham::prelude::*;
```
# Setup the root view
Arkham requires a _Root View_ component that will act as a container view for the application. Views in Arkham are simple functions.
We can add a root view function to _my_project/src/main.rs_
A simple root view may look like this:
```Rust
fn root_view(ctx &mut ViewContext) {
ctx.insert((5,5), "Hello World");
}
```
# Setup the application
In our `main` function we can setup the application and run it, beginning the run loop. Replace the main function in _my_project/src/main.rs_ with the following:
```Rust
fn main() {
App::new(root_view).run().unwrap();
}
```
# The full main.rs
The full main.rs now looks like this:
```Rust
use arkham::prelude::*;
fn main() {
App::new(root_view).run().unwrap();
}
fn root_view(ctx &mut ViewContext) {
ctx.insert((5,5), "Hello World");
}
```
This can now be run with `cargo run` and a hello world app, complete with a default 'q' hotkey to quit, will run.

72
docs/overview/keyboard.md Normal file
View File

@ -0,0 +1,72 @@
---
title: Keyboard Input
subtitle: Overview
menu_position: 3
---
# Reading keyboard input
Keyboard input is provided by a `Keyboard` resource. This is
automatically available. To read input from keyboard events
accept the resource as a parameter for a component function
and check the current state.
```Rust
fn show_keypress(ctx &mut ViewContext, kb: Res<Keyboard>) {
if let Some(c) = kb.char() {
ctx.insert(0, format!("Key press: {}", c));
}
}
```
## Reading modifier keys
Modifier key state is provided within the keyboard resource.
```Rust
fn check_key(ctx &mut ViewContext, kb: Res<Keyboard>) {
if kb.char() == Some('d') && kb.control() {
ctx.insert(0, "Key Pressed")
} else {
ctx.insert(0, "Key NOT Pressed")
}
}
```
# Full Keyboard example
```Rust
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(((10, 10), (30, 1)), hello_world);
ctx.component(((10, 11), (20, 1)), show_key_press);
ctx.component((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),
);
}
```

View File

@ -0,0 +1,98 @@
---
title: Resources & State
subtitle: Overview
menu_position: 2
---
# Dependency Injection
Being able to access your own objects and state is
important for any application. Arkham focuses heavily
on making this ergonomic and easy.
There are two types of injectable objects a _State_
object and _Resource_ object. The main difference is
Resource objects are provided immutable and State
objects are provided with the ability to borrow both
as mutable and immutable references.
## Defining an injectable object
_Resources_ and _state_ are added during application startup
and are global to the application. A single state object can
be used to maintain the full state of the application and
individual components can read and write to the sections they
need.
Resources and state must have unique _Type_. Only one instance
any type can be inserted.
```Rust
pub struct Person {
pub name: String,
pub age: u16
}
#[derive(Default)]
pub struct AppState {
pub counter: usize,
}
let people: Vec<Person> = load_people();
App::new(root_view)
.insert_resource(people)
.insert_state(AppState::default())
.run();
```
# Using resources and state
Injectables are provided automatically to any component that accepts them.
Accepting them requires the use of a wrapper component depending on which
it is.
- Resources use `Res&lt;T&gt;`
- State objects use `State&lt;T&gt;`
## Using a resource
Including the resource in the function arguments automatically provides
the object inside a `Res` wrapper, which derefs to the underlying object.
```Rust
fn my_component(ctx: &mut ViewContext, people: Res<People>) {
for (idx, person) in people.iter().enumerate() {
ctx.insert((0, idx), person.name);
}
}
```
## Using state
Including the resource in the function arguments automatically provides
the object inside a `State` wrapper. The state wrapper has two primary
functions `State::get` which returns immutable access to the state
and `State::get_mut` which returns a mutable reference.
```Rust
fn my_component(ctx: &mut ViewContext, state: State<AppState>) {
ctx.insert(
(0, 0),
format!("Counter: {}", state.get().counter));
}
```
<Alert style="warning" title="Don't borrow state mutably more than once">
Under the hood state is provided inside `Rc&lt;RefCell&lt;T&gt;&gt;`.
Take care not to call `State::get_mut`, which is effectively calling `RefCell::borrow_mut` more than
once at a time.
This includes holding it and then calling a sub component that attempts to access state again.
Scope calls to `State::get_mut` so they live as short as possible and clone out objects if needed.
</Alert>

67
docs/overview/stacks.md Normal file
View File

@ -0,0 +1,67 @@
---
title: Stacks
subtitle: Overview
menu_position: 4
---
# Stack Components
Stacks are a convenient layout component available through the `ViewContext`.
They allow a number of components to be easily positioned next to each other.
Stacks can be either _horizontal_ or _vertical_.
To use a stack initialize it from the context and use the `Stack::insert`
and `Stack:component` functions. The `Stack::component` function requires only a
`Size` and not a `Rect` like the `ViewContext`. This is because the stack will
automatically handle its positioning.
```Rust
let size = ctx.size();
let mut stack = ctx.vertical_stack(Size::new(100, 100));
stack.component((size.width, 2), my_component);
// We can pass size here and it will be automtically
// converted to a Rect with position (0,0).
ctx.component(size, stack);
```
## Alignment
Stacks can also have a specified alignment. This will modify the
positioning of the sub components so they align in the given direction.
Vertical stacks can have Left, Center, Right alignments
Horizontal stacks can have Top, Center, Bottom alignments
```Rust
let size = ctx.size();
let mut stack = ctx.vertical_stack(Size::new(100, 100));
stack.alignment(StackAlignment::Center);
```
# Full stack example
```Rust
use arkham::prelude::*;
fn main() {
let _ = App::new(root).run();
}
fn root(ctx: &mut ViewContext) {
let mut stack = ctx.vertical_stack((100, 100));
for _ in 0..10 {
stack.component((ctx.size().width, 1), list_item);
}
ctx.component((0, (100, 100)), stack);
}
fn list_item(ctx: &mut ViewContext) {
let size = ctx.size();
let mut hstack = ctx.horizontal_stack((ctx.size().width, 1));
hstack.insert("> ");
hstack.insert("line 1");
ctx.component(size, hstack);
}
```

View File

@ -7,9 +7,9 @@ fn main() {
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);
ctx.component(((10, 10), (30, 1)), hello_world);
ctx.component(((10, 11), (20, 1)), show_key_press);
ctx.component((0, (size.width, 1)), quit_nag);
}
fn hello_world(ctx: &mut ViewContext) {

View File

@ -5,13 +5,17 @@ fn main() {
}
fn root(ctx: &mut ViewContext) {
let mut stack = ctx.vertical_stack(Size::new(100, 100));
let mut stack = ctx.vertical_stack((100, 100));
for _ in 0..10 {
stack.component(Size::new(ctx.size().width, 2), list_item);
stack.component((ctx.size().width, 1), list_item);
}
ctx.component(Rect::new(0, (100, 100)), stack);
ctx.component((0, (100, 100)), stack);
}
fn list_item(ctx: &mut ViewContext) {
ctx.insert(0, "line 1");
let size = ctx.size();
let mut hstack = ctx.horizontal_stack((ctx.size().width, 1));
hstack.insert("> ");
hstack.insert("line 1");
ctx.component(size, hstack);
}

View File

@ -38,7 +38,7 @@ impl Renderer {
///
/// Setting up a basic application:
///
/// ```
/// ```no_run
/// use arkham::prelude::*;
///
/// fn main() {

View File

@ -55,21 +55,27 @@ impl ViewContext {
self.rerender = true;
}
pub fn vertical_stack(&self, size: Size) -> Stack {
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),
view: View::new(size.into()),
position: Pos::from(0),
alignment: crate::stack::StackAlignment::Top,
}
}
pub fn horizontal_stack(&self, size: Size) -> Stack {
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),
view: View::new(size.into()),
position: Pos::from(0),
alignment: crate::stack::StackAlignment::Left,
}

View File

@ -243,3 +243,13 @@ impl From<Size> for Rect {
Rect::with_size(value)
}
}
impl<P, S> From<(P, S)> for Rect
where
P: Into<Pos>,
S: Into<Size>,
{
fn from(value: (P, S)) -> Self {
Rect::new(value.0.into(), value.1.into())
}
}

View File

@ -1,6 +1,6 @@
use std::{cell::RefCell, rc::Rc};
use crossterm::event::{KeyCode, KeyModifiers, ModifierKeyCode};
use crossterm::event::{KeyCode, KeyModifiers};
/// Keyboard can be used as an injectable resource that provides information
/// about the current keyboard state. This is the primary mechanism by which
@ -24,22 +24,31 @@ impl Keyboard {
Self::default()
}
pub fn set_key(&self, k: KeyCode) {
/// Set the keyboard state to indicate a specific keycode is pressed
pub(crate) fn set_key(&self, k: KeyCode) {
*self.key.borrow_mut() = Some(k);
}
pub fn set_modifiers(&self, modifiers: KeyModifiers) {
/// Set the keyboard state to indicate specific modifier keys are pressed
pub(crate) fn set_modifiers(&self, modifiers: KeyModifiers) {
*self.modifiers.borrow_mut() = modifiers;
}
/// Resets the keyboard state. This can be used after accepting
/// a keypress within a component to prevent further components from
/// registering the keypress event
pub fn reset(&self) {
*self.key.borrow_mut() = None;
}
/// Retruns the keycode that is current pressed, or None if there are
/// no currently pressed keys
pub fn code(&self) -> Option<KeyCode> {
*self.key.borrow()
}
/// Returns the char value of the pressed key. Returns None if no key
/// is currently pressed, or if the key does not have a char value.
pub fn char(&self) -> Option<char> {
if let Some(KeyCode::Char(c)) = *self.key.borrow() {
Some(c)
@ -48,26 +57,32 @@ impl Keyboard {
}
}
/// Returns true if the shift key is current pressed
pub fn shift(&self) -> bool {
self.modifiers.borrow().contains(KeyModifiers::SHIFT)
}
/// Returns true if the control key is current pressed
pub fn control(&self) -> bool {
self.modifiers.borrow().contains(KeyModifiers::CONTROL)
}
/// Returns true if the alt key is current pressed
pub fn alt(&self) -> bool {
self.modifiers.borrow().contains(KeyModifiers::ALT)
}
/// Returns true if the super key is current pressed
pub fn super_key(&self) -> bool {
self.modifiers.borrow().contains(KeyModifiers::SUPER)
}
/// Returns true if the hyper key is current pressed
pub fn hyper(&self) -> bool {
self.modifiers.borrow().contains(KeyModifiers::HYPER)
}
/// Returns true if the meta key is current pressed
pub fn meta(&self) -> bool {
self.modifiers.borrow().contains(KeyModifiers::META)
}

View File

@ -178,7 +178,7 @@ mod tests {
#[test]
fn test_vertical_insert() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.vertical_stack((10, 2).into());
let mut stack = ctx.vertical_stack((10, 2));
stack.insert("one");
stack.insert("two");
assert_eq!(
@ -190,7 +190,7 @@ mod tests {
#[test]
fn test_horizontal_insert() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.horizontal_stack((10, 1).into());
let mut stack = ctx.horizontal_stack((10, 1));
stack.insert("one");
stack.insert("two");
assert_eq!(stack.view.render_text(), "onetwo\0\0\0\0\n".to_string());
@ -199,7 +199,7 @@ mod tests {
#[test]
fn test_component() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.horizontal_stack((10, 2).into());
let mut stack = ctx.horizontal_stack((10, 2));
stack.component((10, 2), |ctx: &mut ViewContext| {
ctx.insert((3, 1), "one");
});
@ -212,7 +212,7 @@ mod tests {
#[test]
fn test_align_left() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.vertical_stack((10, 2).into());
let mut stack = ctx.vertical_stack((10, 2));
stack.component((5, 1), |ctx: &mut ViewContext| {
ctx.insert((0, 0), "one");
});
@ -226,7 +226,7 @@ mod tests {
#[test]
fn test_align_right() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.vertical_stack((10, 3).into());
let mut stack = ctx.vertical_stack((10, 3));
stack.alignment = StackAlignment::Right;
stack.insert("one");
stack.component((5, 1), |ctx: &mut ViewContext| {
@ -248,7 +248,7 @@ mod tests {
#[test]
fn test_align_center_v() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.vertical_stack((10, 3).into());
let mut stack = ctx.vertical_stack((10, 3));
stack.alignment = StackAlignment::Center;
stack.insert("one");
stack.component((5, 1), |ctx: &mut ViewContext| {
@ -270,7 +270,7 @@ mod tests {
#[test]
fn test_align_top() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.horizontal_stack((9, 6).into());
let mut stack = ctx.horizontal_stack((9, 6));
stack.component((3, 1), |ctx: &mut ViewContext| {
ctx.insert((0, 0), "one");
});
@ -292,7 +292,7 @@ mod tests {
#[test]
fn test_align_bottom() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.horizontal_stack((9, 6).into());
let mut stack = ctx.horizontal_stack((9, 6));
stack.alignment(StackAlignment::Bottom);
stack.component((3, 1), |ctx: &mut ViewContext| {
ctx.insert((0, 0), "one");
@ -315,7 +315,7 @@ mod tests {
#[test]
fn test_align_center_h() {
let ctx = crate::context::tests::context_fixture();
let mut stack = ctx.horizontal_stack((9, 6).into());
let mut stack = ctx.horizontal_stack((9, 6));
stack.alignment(StackAlignment::Center);
stack.component((3, 1), |ctx: &mut ViewContext| {
ctx.insert((0, 0), "one");