refactor
This commit is contained in:
commit
aaa51179ad
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "arkham"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
crossterm = "0.26.1"
|
||||
ctrlc = "3.3.1"
|
|
@ -0,0 +1,138 @@
|
|||
use std::{any::Any, cell::RefCell, io::Write, marker::PhantomData, rc::Rc};
|
||||
|
||||
use crossterm::{
|
||||
cursor,
|
||||
event::{Event, KeyCode, KeyEventKind, KeyModifiers},
|
||||
execute, queue, terminal,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
container::{Callable, Container, FromContainer, Res, State},
|
||||
context::ViewContext,
|
||||
view::View,
|
||||
};
|
||||
|
||||
use super::input::Keyboard;
|
||||
|
||||
pub struct App<F, Args>
|
||||
where
|
||||
F: Callable<Args>,
|
||||
Args: FromContainer,
|
||||
{
|
||||
container: Rc<RefCell<Container>>,
|
||||
main_view: View,
|
||||
root: F,
|
||||
args: PhantomData<Args>,
|
||||
}
|
||||
|
||||
impl<F, Args> App<F, Args>
|
||||
where
|
||||
F: Callable<Args>,
|
||||
Args: FromContainer,
|
||||
{
|
||||
pub fn new(root: F) -> App<F, Args> {
|
||||
let container = Rc::new(RefCell::new(Container::default()));
|
||||
let size = terminal::size().unwrap();
|
||||
let main_view = View::new(size);
|
||||
App {
|
||||
container,
|
||||
root,
|
||||
main_view,
|
||||
args: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_root(&mut self, root: F) {
|
||||
self.root = root;
|
||||
}
|
||||
|
||||
pub fn insert_resource<T: Any>(self, v: T) -> Self {
|
||||
self.container.borrow_mut().bind(Res::new(v));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn insert_state<T: Any>(self, v: T) -> Self {
|
||||
self.container.borrow_mut().bind(State::new(v));
|
||||
self
|
||||
}
|
||||
|
||||
fn teardown(&self) {
|
||||
let mut out = std::io::stdout();
|
||||
let _ = terminal::disable_raw_mode();
|
||||
let _ = execute!(out, terminal::LeaveAlternateScreen, cursor::Show);
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> anyhow::Result<()> {
|
||||
self.container.borrow_mut().bind(Res::new(Terminal));
|
||||
self.container.borrow_mut().bind(Res::new(Keyboard::new()));
|
||||
let _ = ctrlc::set_handler(|| {
|
||||
let mut out = std::io::stdout();
|
||||
let _ = terminal::disable_raw_mode();
|
||||
let _ = execute!(out, terminal::LeaveAlternateScreen, cursor::Show);
|
||||
std::process::exit(0);
|
||||
});
|
||||
let mut out = std::io::stdout();
|
||||
execute!(out, terminal::EnterAlternateScreen, cursor::Hide)?;
|
||||
terminal::enable_raw_mode()?;
|
||||
|
||||
loop {
|
||||
let mut context =
|
||||
ViewContext::new(self.container.clone(), terminal::size().unwrap().into());
|
||||
|
||||
self.container.borrow().call(&mut context, &self.root);
|
||||
self.main_view.apply((0, 0), context.view);
|
||||
self.render()?;
|
||||
|
||||
self.container
|
||||
.borrow()
|
||||
.get::<Res<Keyboard>>()
|
||||
.unwrap()
|
||||
.reset();
|
||||
|
||||
if let Ok(event) = crossterm::event::read() {
|
||||
match event {
|
||||
Event::FocusGained => todo!(),
|
||||
Event::FocusLost => todo!(),
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('q') => {
|
||||
break;
|
||||
}
|
||||
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
|
||||
let container = self.container.borrow();
|
||||
let kb = container.get::<Res<Keyboard>>().unwrap();
|
||||
kb.set_key(key_event.code);
|
||||
}
|
||||
Event::Mouse(_) => todo!(),
|
||||
Event::Paste(_) => todo!(),
|
||||
Event::Resize(_, _) => todo!(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.teardown();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&mut self) -> anyhow::Result<()> {
|
||||
let mut out = std::io::stdout();
|
||||
for (row, line) in self.main_view.iter().enumerate() {
|
||||
for (col, rune) in line.iter().enumerate() {
|
||||
queue!(out, cursor::MoveTo(col as u16, row as u16))?;
|
||||
rune.render(&mut out)?;
|
||||
}
|
||||
}
|
||||
out.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Terminal;
|
||||
|
||||
impl Terminal {
|
||||
pub fn set_title(&self, name: &str) {
|
||||
let _ = execute!(std::io::stdout(), terminal::SetTitle(name));
|
||||
}
|
||||
pub fn size(&self) -> (u16, u16) {
|
||||
crossterm::terminal::size().unwrap_or_default()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::context::ViewContext;
|
||||
type ArkhamResult = anyhow::Result<ArkhamState>;
|
||||
pub enum ArkhamState {
|
||||
Noop,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Container {
|
||||
bindings: HashMap<TypeId, Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn bind<T: Any>(&mut self, val: T) {
|
||||
self.bindings.insert(val.type_id(), Box::new(val));
|
||||
}
|
||||
|
||||
pub fn get<T: Any>(&self) -> Option<&T> {
|
||||
self.bindings
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|boxed| boxed.downcast_ref())
|
||||
}
|
||||
|
||||
pub fn call<F, Args>(&self, view: &mut ViewContext, callable: &F)
|
||||
where
|
||||
F: Callable<Args>,
|
||||
Args: FromContainer,
|
||||
{
|
||||
callable.call(view, Args::from_container(self));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Callable<Args> {
|
||||
fn call(&self, view: &mut ViewContext, args: Args) -> ArkhamResult;
|
||||
}
|
||||
|
||||
pub trait FromContainer {
|
||||
fn from_container(container: &Container) -> Self;
|
||||
}
|
||||
|
||||
pub struct State<T: ?Sized>(Rc<RefCell<T>>);
|
||||
|
||||
impl<T> State<T> {
|
||||
pub fn new(val: T) -> Self {
|
||||
State(Rc::new(RefCell::new(val)))
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> std::cell::RefMut<T> {
|
||||
RefCell::borrow_mut(&self.0)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> std::cell::Ref<T> {
|
||||
RefCell::borrow(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for State<T> {
|
||||
fn clone(&self) -> State<T> {
|
||||
State(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static> FromContainer for State<T> {
|
||||
fn from_container(container: &Container) -> Self {
|
||||
container.get::<Self>().expect("type not found").clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Res<T: ?Sized>(Rc<T>);
|
||||
|
||||
impl<T> Res<T> {
|
||||
pub fn new(val: T) -> Self {
|
||||
Res(Rc::new(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Res<T> {
|
||||
pub fn get(&self) -> &T {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for Res<T> {
|
||||
fn clone(&self) -> Res<T> {
|
||||
Res(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for Res<T> {
|
||||
type Target = Rc<T>;
|
||||
|
||||
fn deref(&self) -> &Rc<T> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static> FromContainer for Res<T> {
|
||||
fn from_container(container: &Container) -> Self {
|
||||
container.get::<Self>().expect("type not found").clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Func> Callable<()> for Func
|
||||
where
|
||||
Func: Fn(&mut ViewContext),
|
||||
{
|
||||
#[inline]
|
||||
fn call(&self, view: &mut ViewContext, _args: ()) -> ArkhamResult {
|
||||
(self)(view);
|
||||
Ok(ArkhamState::Noop)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromContainer for () {
|
||||
#[inline]
|
||||
fn from_container(_container: &Container) -> Self {}
|
||||
}
|
||||
|
||||
macro_rules! callable_tuple ({ $($param:ident)* } => {
|
||||
impl<Func, $($param,)*> Callable<($($param,)*)> for Func
|
||||
where
|
||||
Func: Fn(&mut ViewContext, $($param),*),
|
||||
{
|
||||
#[inline]
|
||||
#[allow(non_snake_case)]
|
||||
fn call(&self, view: &mut ViewContext , ($($param,)*): ($($param,)*)) -> ArkhamResult{
|
||||
(self)(view, $($param,)*);
|
||||
Ok(ArkhamState::Noop)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// callable_tuple! {}
|
||||
callable_tuple! { A }
|
||||
callable_tuple! { A B }
|
||||
callable_tuple! { A B C }
|
||||
callable_tuple! { A B C D }
|
||||
callable_tuple! { A B C D E }
|
||||
callable_tuple! { A B C D E F }
|
||||
callable_tuple! { A B C D E F G }
|
||||
callable_tuple! { A B C D E F G H }
|
||||
callable_tuple! { A B C D E F G H I }
|
||||
callable_tuple! { A B C D E F G H I J }
|
||||
callable_tuple! { A B C D E F G H I J K }
|
||||
callable_tuple! { A B C D E F G H I J K L }
|
||||
|
||||
macro_rules! tuple_from_tm {
|
||||
( $($T: ident )+ ) => {
|
||||
impl<$($T: FromContainer),+> FromContainer for ($($T,)+)
|
||||
{
|
||||
#[inline]
|
||||
fn from_container(container: &Container) -> Self {
|
||||
($($T::from_container(container),)+)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
tuple_from_tm! { A }
|
||||
tuple_from_tm! { A B }
|
||||
tuple_from_tm! { A B C }
|
||||
tuple_from_tm! { A B C D }
|
||||
tuple_from_tm! { A B C D E }
|
||||
tuple_from_tm! { A B C D E F }
|
||||
tuple_from_tm! { A B C D E F G }
|
||||
tuple_from_tm! { A B C D E F G H }
|
||||
tuple_from_tm! { A B C D E F G H I }
|
||||
tuple_from_tm! { A B C D E F G H I J }
|
||||
tuple_from_tm! { A B C D E F G H I J K }
|
||||
tuple_from_tm! { A B C D E F G H I J K L }
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{container::Container, prelude::ViewContext};
|
||||
|
||||
#[test]
|
||||
fn test_no_params() {
|
||||
fn test_f(_ctx: &mut ViewContext) {}
|
||||
let container = Rc::new(RefCell::new(Container::default()));
|
||||
let mut ctx = ViewContext::new(container.clone(), (1, 1).into());
|
||||
container.borrow().call(&mut ctx, &test_f);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
container::{Callable, FromContainer},
|
||||
widget::Widget,
|
||||
};
|
||||
|
||||
use super::{
|
||||
container::Container,
|
||||
geometry::{Pos, Rect, Size},
|
||||
runes::Rune,
|
||||
view::View,
|
||||
};
|
||||
|
||||
pub struct ViewContext {
|
||||
pub view: View,
|
||||
pub container: Rc<RefCell<Container>>,
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn new(container: Rc<RefCell<Container>>, size: Size) -> Self {
|
||||
let view = View::new(size);
|
||||
|
||||
Self { view, container }
|
||||
}
|
||||
|
||||
pub fn component<F, Args>(&mut self, rect: Rect, f: F)
|
||||
where
|
||||
F: Callable<Args>,
|
||||
Args: FromContainer,
|
||||
{
|
||||
let mut context = ViewContext::new(self.container.clone(), rect.size);
|
||||
self.container.borrow().call(&mut context, &f);
|
||||
self.view.apply(rect.pos, context.view);
|
||||
}
|
||||
|
||||
pub fn widget(&mut self, rect: Rect, mut widget: impl Widget) {
|
||||
let mut context = ViewContext::new(self.container.clone(), rect.size);
|
||||
widget.ui(&mut context);
|
||||
self.view.apply(rect.pos, context.view);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
use std::ops::{Add, Sub};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Pos {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
}
|
||||
|
||||
impl From<(usize, usize)> for Pos {
|
||||
fn from(value: (usize, usize)) -> Self {
|
||||
Self {
|
||||
x: value.0,
|
||||
y: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Pos {
|
||||
fn from(value: usize) -> Self {
|
||||
Self { x: value, y: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Size {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl Add<Size> for Size {
|
||||
type Output = Size;
|
||||
|
||||
fn add(self, rhs: Size) -> Self::Output {
|
||||
Self {
|
||||
width: self.width + rhs.width,
|
||||
height: self.height + rhs.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Size> for Size {
|
||||
type Output = Size;
|
||||
|
||||
fn sub(self, rhs: Size) -> Self::Output {
|
||||
Self {
|
||||
width: self.width - rhs.width,
|
||||
height: self.height - rhs.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<i32> for Size {
|
||||
type Output = Size;
|
||||
|
||||
fn add(self, rhs: i32) -> Self::Output {
|
||||
Self {
|
||||
width: self.width + rhs as usize,
|
||||
height: self.height + rhs as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<i32> for Size {
|
||||
type Output = Size;
|
||||
|
||||
fn sub(self, rhs: i32) -> Self::Output {
|
||||
Self {
|
||||
width: self.width - rhs as usize,
|
||||
height: self.height - rhs as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(usize, usize)> for Size {
|
||||
fn from(value: (usize, usize)) -> Self {
|
||||
Self {
|
||||
width: value.0,
|
||||
height: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u16, u16)> for Size {
|
||||
fn from(value: (u16, u16)) -> Self {
|
||||
Self {
|
||||
width: value.0 as usize,
|
||||
height: value.1 as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(i32, i32)> for Size {
|
||||
fn from(value: (i32, i32)) -> Self {
|
||||
Self {
|
||||
width: value.0 as usize,
|
||||
height: value.1 as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Rect {
|
||||
pub pos: Pos,
|
||||
pub size: Size,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn new<P, S>(pos: P, size: S) -> Self
|
||||
where
|
||||
P: Into<Pos>,
|
||||
S: Into<Size>,
|
||||
{
|
||||
Self {
|
||||
pos: pos.into(),
|
||||
size: size.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_size<S>(size: S) -> Self
|
||||
where
|
||||
S: Into<Size>,
|
||||
{
|
||||
Rect {
|
||||
pos: (0, 0).into(),
|
||||
size: size.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for Rect {
|
||||
fn from(value: Size) -> Self {
|
||||
Rect::with_size(value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crossterm::event::KeyCode;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Keyboard {
|
||||
key: Rc<RefCell<Option<KeyCode>>>,
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn set_key(&self, k: KeyCode) {
|
||||
*self.key.borrow_mut() = Some(k);
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
*self.key.borrow_mut() = None;
|
||||
}
|
||||
|
||||
pub fn code(&self) -> Option<KeyCode> {
|
||||
*self.key.borrow()
|
||||
}
|
||||
|
||||
pub fn char(&self) -> Option<char> {
|
||||
if let Some(KeyCode::Char(c)) = *self.key.borrow() {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
mod app;
|
||||
mod container;
|
||||
mod context;
|
||||
mod geometry;
|
||||
mod input;
|
||||
mod runes;
|
||||
pub mod symbols;
|
||||
mod theme;
|
||||
mod view;
|
||||
mod widget;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{
|
||||
app::{App, Terminal},
|
||||
container::{Callable, FromContainer, Res, State},
|
||||
context::ViewContext,
|
||||
geometry::{Pos, Rect, Size},
|
||||
input::Keyboard,
|
||||
runes::{Rune, Runes, ToRuneExt},
|
||||
theme::Theme,
|
||||
widget::Widget,
|
||||
};
|
||||
pub use crossterm::event::KeyCode;
|
||||
pub use crossterm::style::Color;
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
use crossterm::{
|
||||
queue,
|
||||
style::{Attribute, Color, Print, SetAttribute, SetBackgroundColor, SetForegroundColor},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Default, Eq, PartialEq)]
|
||||
pub struct Rune {
|
||||
pub content: Option<char>,
|
||||
pub fg: Option<Color>,
|
||||
pub bg: Option<Color>,
|
||||
pub bold: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Rune {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct(&format!("Rn({})", self.content.unwrap_or_default()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rune {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn content(mut self, content: char) -> Self {
|
||||
self.content = Some(content);
|
||||
self
|
||||
}
|
||||
pub fn bg(mut self, bg: Color) -> Self {
|
||||
self.bg = Some(bg);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fg(mut self, fg: Color) -> Self {
|
||||
self.fg = Some(fg);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bold(mut self) -> Self {
|
||||
self.bold = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render<W>(self, out: &mut W) -> anyhow::Result<()>
|
||||
where
|
||||
W: std::io::Write,
|
||||
{
|
||||
if let Some(content) = self.content {
|
||||
if let Some(c) = self.fg {
|
||||
queue!(out, SetForegroundColor(c))?;
|
||||
}
|
||||
if let Some(c) = self.bg {
|
||||
queue!(out, SetBackgroundColor(c))?;
|
||||
}
|
||||
if self.bold {
|
||||
queue!(out, SetAttribute(Attribute::Bold))?;
|
||||
}
|
||||
queue!(out, Print(content))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Runes(Vec<Rune>);
|
||||
|
||||
impl std::ops::Deref for Runes {
|
||||
type Target = Vec<Rune>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToString> From<T> for Runes {
|
||||
fn from(value: T) -> Self {
|
||||
Runes(
|
||||
value
|
||||
.to_string()
|
||||
.chars()
|
||||
.map(|c| Rune::new().content(c))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Runes {
|
||||
pub fn fg(mut self, color: Color) -> Self {
|
||||
for r in self.0.iter_mut() {
|
||||
r.fg = Some(color);
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn bg(mut self, color: Color) -> Self {
|
||||
for r in self.0.iter_mut() {
|
||||
r.bg = Some(color);
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn bold(mut self) -> Self {
|
||||
for r in self.0.iter_mut() {
|
||||
r.bold = true;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToRuneExt {
|
||||
fn to_runes(&self) -> Runes;
|
||||
}
|
||||
|
||||
impl ToRuneExt for String {
|
||||
fn to_runes(&self) -> Runes {
|
||||
Runes::from(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuneExt for &str {
|
||||
fn to_runes(&self) -> Runes {
|
||||
Runes::from(self.to_string())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
#[cfg(any(not(windows), all(windows)))]
|
||||
mod universal {
|
||||
pub const TICK: char = '✔';
|
||||
pub const CROSS: char = '✖';
|
||||
pub const STAR: char = '★';
|
||||
pub const SQUARE: char = '▇';
|
||||
pub const SQUARE_SMALL: char = '◻';
|
||||
pub const SQUARE_SMALL_FILLED: char = '◼';
|
||||
pub const PLAY: char = '▶';
|
||||
pub const CIRCLE: char = '◯';
|
||||
pub const CIRCLE_FILLED: char = '◉';
|
||||
pub const CIRCLE_DOTTED: char = '◌';
|
||||
pub const CIRCLE_DOUBLE: char = '◎';
|
||||
pub const CIRCLE_CIRCLE: char = 'ⓞ';
|
||||
pub const CIRCLE_CROSS: char = 'ⓧ';
|
||||
pub const CIRCLE_PIPE: char = 'Ⓘ';
|
||||
pub const CIRCLE_QUESTION_MARK: char = '?';
|
||||
pub const BULLET: char = '●';
|
||||
pub const DOT: char = '․';
|
||||
pub const LINE: char = '─';
|
||||
pub const ELLIPSIS: char = '…';
|
||||
pub const POINTER: char = '❯';
|
||||
pub const POINTER_SMALL: char = '›';
|
||||
pub const INFO: char = 'ℹ';
|
||||
pub const WARNING: char = '⚠';
|
||||
pub const HAMBURGER: char = '☰';
|
||||
pub const SMILEY: char = '㋡';
|
||||
pub const MUSTACHE: char = '෴';
|
||||
pub const HEART: char = '♥';
|
||||
pub const NODEJS: char = '⬢';
|
||||
pub const ARROW_UP: char = '↑';
|
||||
pub const ARROW_DOWN: char = '↓';
|
||||
pub const ARROW_LEFT: char = '←';
|
||||
pub const ARROW_RIGHT: char = '→';
|
||||
pub const RADIO_ON: char = '◉';
|
||||
pub const RADIO_OFF: char = '◯';
|
||||
pub const CHECKBOX_ON: char = '☒';
|
||||
pub const CHECKBOX_OFF: char = '☐';
|
||||
pub const CHECKBOX_CIRCLE_ON: char = 'ⓧ';
|
||||
pub const CHECKBOX_CIRCLE_OFF: char = 'Ⓘ';
|
||||
pub const QUESTION_MARK_PREFIX: char = '?';
|
||||
pub const ONE_HALF: char = '½';
|
||||
pub const ONE_THIRD: char = '⅓';
|
||||
pub const ONE_QUARTER: char = '¼';
|
||||
pub const ONE_FIFTH: char = '⅕';
|
||||
pub const ONE_SIXTH: char = '⅙';
|
||||
pub const ONE_SEVENTH: char = '⅐';
|
||||
pub const ONE_EIGHTH: char = '⅛';
|
||||
pub const ONE_NINTH: char = '⅑';
|
||||
pub const ONE_TENTH: char = '⅒';
|
||||
pub const TWO_THIRDS: char = '⅔';
|
||||
pub const TWO_FIFTHS: char = '⅖';
|
||||
pub const THREE_QUARTERS: char = '¾';
|
||||
pub const THREE_FIFTHS: char = '⅗';
|
||||
pub const THREE_EIGHTHS: char = '⅜';
|
||||
pub const FOUR_FIFTHS: char = '⅘';
|
||||
pub const FIVE_SIXTHS: char = '⅚';
|
||||
pub const FIVE_EIGHTHS: char = '⅝';
|
||||
pub const SEVEN_EIGHTHS: char = '⅞';
|
||||
}
|
||||
|
||||
#[cfg(any(not(windows), all(windows)))]
|
||||
pub use universal::*;
|
||||
|
||||
#[cfg(all(windows))]
|
||||
mod win {
|
||||
pub const TICK: char = '√';
|
||||
pub const CROSS: char = '×';
|
||||
pub const STAR: char = '*';
|
||||
pub const SQUARE: char = '█';
|
||||
pub const PLAY: char = '►';
|
||||
pub const BULLET: char = '*';
|
||||
pub const DOT: char = '.';
|
||||
pub const LINE: char = '─';
|
||||
pub const POINTER: char = '>';
|
||||
pub const POINTER_SMALL: char = '»';
|
||||
pub const INFO: char = 'i';
|
||||
pub const WARNING: char = '‼';
|
||||
pub const HAMBURGER: char = '≡';
|
||||
pub const SMILEY: char = '☺';
|
||||
pub const HEART: char = '♥';
|
||||
pub const NODEJS: char = '♦';
|
||||
pub const ARROW_UP: char = '↑';
|
||||
pub const ARROW_DOWN: char = '↓';
|
||||
pub const ARROW_LEFT: char = '←';
|
||||
pub const ARROW_RIGHT: char = '→';
|
||||
pub const QUESTION_MARK_PREFIX: char = '?';
|
||||
pub const ONE_HALF: char = ' ';
|
||||
}
|
||||
|
||||
#[cfg(all(windows))]
|
||||
pub use win::*;
|
|
@ -0,0 +1,45 @@
|
|||
use crossterm::style::Color;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Theme {
|
||||
pub bg_primary: Color,
|
||||
pub bg_secondary: Color,
|
||||
pub bg_tertiary: Color,
|
||||
pub bg_selection: Color,
|
||||
pub fg_selection: Color,
|
||||
pub fg: Color,
|
||||
pub accent: Color,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bg_primary: Color::Rgb {
|
||||
r: 36,
|
||||
g: 39,
|
||||
b: 58,
|
||||
},
|
||||
|
||||
bg_secondary: Color::Rgb {
|
||||
r: 20,
|
||||
g: 22,
|
||||
b: 30,
|
||||
},
|
||||
|
||||
bg_tertiary: Color::Rgb {
|
||||
r: 76,
|
||||
g: 79,
|
||||
b: 98,
|
||||
},
|
||||
|
||||
bg_selection: Color::Rgb { r: 60, g: 0, b: 60 },
|
||||
fg_selection: Color::White,
|
||||
fg: Color::White,
|
||||
accent: Color::Rgb {
|
||||
r: 150,
|
||||
g: 0,
|
||||
b: 150,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
use crate::{
|
||||
geometry::{Pos, Rect, Size},
|
||||
runes::{Rune, Runes},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct View(pub Vec<Vec<Rune>>);
|
||||
|
||||
impl std::ops::DerefMut for View {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for View {
|
||||
type Target = Vec<Vec<Rune>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new<T>(size: T) -> Self
|
||||
where
|
||||
T: Into<Size>,
|
||||
{
|
||||
let size: Size = size.into();
|
||||
Self(vec![vec![Rune::default(); size.width]; size.height])
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Vec<Rune>> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn apply<P: Into<Pos>>(&mut self, pos: P, view: View) {
|
||||
let pos = pos.into();
|
||||
for (y, line) in view.0.iter().enumerate() {
|
||||
if self.0.len() > y + pos.y {
|
||||
for (x, rune) in line.iter().enumerate() {
|
||||
if rune.content.is_some() && self.0[y].len() > x + pos.x {
|
||||
let _ = std::mem::replace(&mut self.0[y + pos.y][x + pos.x], *rune);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.first().unwrap().len()
|
||||
}
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
pub fn size(&self) -> Size {
|
||||
(self.width(), self.height()).into()
|
||||
}
|
||||
pub fn fill(&mut self, rect: Rect, rune: Rune) {
|
||||
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()) {
|
||||
let _ = std::mem::replace(&mut self.0[y][x], rune);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert<P: Into<Pos>, S: Into<Runes>>(&mut self, pos: P, value: S) {
|
||||
let Pos { x, y } = pos.into();
|
||||
let runes: Runes = value.into();
|
||||
if let Some(line) = self.0.get_mut(y) {
|
||||
let line_len = line.len() as i32;
|
||||
for (i, c) in runes
|
||||
.iter()
|
||||
.take((line_len - x as i32).max(0) as usize)
|
||||
.enumerate()
|
||||
{
|
||||
let _ = std::mem::replace(&mut line[x + i], *c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{geometry::Rect, runes::Rune};
|
||||
|
||||
use super::View;
|
||||
|
||||
#[test]
|
||||
pub fn test_insert_pos() {
|
||||
let mut view = View::new((5, 3));
|
||||
view.insert((1, 2), "test");
|
||||
dbg!(&view.0);
|
||||
assert_eq!(view.0[2][1].content, Some('t'));
|
||||
assert_eq!(view.0[2][2].content, Some('e'));
|
||||
assert_eq!(view.0[2][3].content, Some('s'));
|
||||
assert_eq!(view.0[2][4].content, Some('t'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_fill() {
|
||||
let mut view = View::new((3, 3));
|
||||
view.fill(Rect::new((1, 1), (2, 2)), Rune::new().content('X'));
|
||||
dbg!(&view.0);
|
||||
assert_eq!(view.0[0][0].content, None);
|
||||
assert_eq!(view.0[0][1].content, None);
|
||||
assert_eq!(view.0[0][2].content, None);
|
||||
|
||||
assert_eq!(view.0[1][0].content, None);
|
||||
assert_eq!(view.0[1][1].content, Some('X'));
|
||||
assert_eq!(view.0[1][2].content, Some('X'));
|
||||
|
||||
assert_eq!(view.0[2][0].content, None);
|
||||
assert_eq!(view.0[2][1].content, Some('X'));
|
||||
assert_eq!(view.0[2][2].content, Some('X'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_fill_overflow() {
|
||||
let mut view = View::new((3, 3));
|
||||
view.fill(Rect::new((1, 1), (4, 4)), Rune::new().content('X'));
|
||||
dbg!(&view.0);
|
||||
assert_eq!(view.0[0][0].content, None);
|
||||
assert_eq!(view.0[0][1].content, None);
|
||||
assert_eq!(view.0[0][2].content, None);
|
||||
|
||||
assert_eq!(view.0[1][0].content, None);
|
||||
assert_eq!(view.0[1][1].content, Some('X'));
|
||||
assert_eq!(view.0[1][2].content, Some('X'));
|
||||
|
||||
assert_eq!(view.0[2][0].content, None);
|
||||
assert_eq!(view.0[2][1].content, Some('X'));
|
||||
assert_eq!(view.0[2][2].content, Some('X'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_apply() {
|
||||
let mut view1 = View::new((3, 4));
|
||||
view1.fill(Rect::new((1, 1), (2, 2)), Rune::new().content('X'));
|
||||
let mut view2 = View::new((3, 4));
|
||||
view2.apply((0, 1), view1);
|
||||
dbg!(&view2.0);
|
||||
assert_eq!(view2.0[0][0].content, None);
|
||||
assert_eq!(view2.0[0][1].content, None);
|
||||
assert_eq!(view2.0[0][2].content, None);
|
||||
|
||||
assert_eq!(view2.0[1][0].content, None);
|
||||
assert_eq!(view2.0[1][1].content, None);
|
||||
assert_eq!(view2.0[1][2].content, None);
|
||||
|
||||
assert_eq!(view2.0[2][0].content, None);
|
||||
assert_eq!(view2.0[2][1].content, Some('X'));
|
||||
assert_eq!(view2.0[2][2].content, Some('X'));
|
||||
|
||||
assert_eq!(view2.0[3][0].content, None);
|
||||
assert_eq!(view2.0[3][1].content, Some('X'));
|
||||
assert_eq!(view2.0[3][2].content, Some('X'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_apply_overflow() {
|
||||
let mut view0 = View::new((5, 5));
|
||||
view0.fill(Rect::new((1, 1), (4, 4)), Rune::new().content('X'));
|
||||
let mut view = View::new((3, 3));
|
||||
view.apply((0, 0), view0);
|
||||
dbg!(&view.0);
|
||||
assert_eq!(view.0[0][0].content, None);
|
||||
assert_eq!(view.0[0][1].content, None);
|
||||
assert_eq!(view.0[0][2].content, None);
|
||||
|
||||
assert_eq!(view.0[1][0].content, None);
|
||||
assert_eq!(view.0[1][1].content, Some('X'));
|
||||
assert_eq!(view.0[1][2].content, Some('X'));
|
||||
|
||||
assert_eq!(view.0[2][0].content, None);
|
||||
assert_eq!(view.0[2][1].content, Some('X'));
|
||||
assert_eq!(view.0[2][2].content, Some('X'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
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