2 releases
0.1.1 | May 13, 2022 |
---|---|
0.1.0 | May 13, 2022 |
#1315 in GUI
58KB
415 lines
Douglas is a tiny terminal UI framework built on the Elm architecture. It's heavily inspired by bubbletea and iced.
Usage
Building a UI with Douglas is a cinch!
use douglas::{Config, Program};
struct App;
impl Program for App {
type Message = ();
fn view(&self) -> String {
"Hello, world!\n".into()
}
}
fn main() {
App.run(&mut Config::default()).unwrap();
}
Granted, this isn't very exciting. Douglas apps just need 3 ingredients:
init
initialize your state model, and perform any setupupdate
handle messages and user inputview
render your UI (you've seen this already!)
use douglas::{Command, Config, Mailbox, Program};
// declare your state model
struct App {
counter: usize,
}
// setup a constructor
impl App {
fn new() -> Self {
Self {
counter: 0,
}
}
}
// declare message type
enum Message {
Increment,
Decrement,
}
impl Program for App {
type Message = Message;
fn init(&mut self, _: Mailbox<Self::Message>) -> Command<Self::Message> {
// send an Increment message right away
Command::send(Message::Increment)
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
// you can pattern match on incoming messages
match message {
Message::Increment => self.counter += 1,
Message::Decrement => self.counter -= 1,
}
Command::none()
}
fn view(&self) -> String {
format!("count: {}\n", self.counter)
}
}
fn main() {
let app = App::new();
let config = Config::default();
app.run(&mut config).unwrap();
}
Finally, if you need to do any cleanup, you may implement exit
:
use douglas::Program;
impl Program for App {
// (...snip...)
fn exit(self) {
println!("Goodbye, cruel world!");
}
}
Handling Events
In addition to update
, apps may implement on_event
to send messages in
response to terminal events such as keypresses:
use douglas::{Command, Program};
use crossterm::event::{Event, KeyCode, KeyEvent};
impl Program for App {
// (...snip...)
fn on_event(event: Event) -> Command<Self::Message> {
match event {
Event::Key(KeyEvent { code: KeyCode::Up, .. }) =>
Command::send(Message::Increment),
_ => Command::none()
}
}
}
External Interaction
Sometimes it's useful to respond to events that occur outside of your program's lifecycle, like responding when data becomes available on the network or sending a message on a recurring interval. You can use your program's mailbox:
use douglas::{Command, Mailbox, Program, Timer};
use crossterm::event::{Event, KeyCode, KeyEvent};
use std::time::Duration;
struct App {
timer: Timer,
}
#[derive(Clone)]
enum Message {
Tick,
}
impl App {
fn new() -> Self {
Self {
timer: Timer::new(Duration::from_millis(1_000), Message::Tick),
}
}
}
impl Program for App {
// (...snip...)
fn init(&mut self, mailbox: Mailbox<Self::Message>) -> Command<Self::Message> {
self.timer.start(mailbox);
Command::none()
}
// make sure to clean up!
fn exit(mut self) {
self.timer.stop();
}
}
Examples
You can check out the examples
directory to see some projects in
action. You can also run an example directly:
cargo run --package hello_world
Contributing
This project is a work in progress. All contributions are welcome!
This project adheres to the Contributor Covenant code of conduct. Please be nice to each other 🙂
License
This project is made available under the MIT License. Copyright 2022 Aaron Ross, all rights reserved.
Dependencies
~2.5MB
~42K SLoC