Skip to content
This repository has been archived by the owner on May 11, 2023. It is now read-only.

terminal-tui: library w/ tui-realm #171

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,252 changes: 780 additions & 472 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"anchor",
"account",
"terminal",
"terminal-tui",
"common",
"checkout",
"cli",
Expand Down
10 changes: 10 additions & 0 deletions terminal-tui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "radicle-terminal-tui"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"

[dependencies]
anyhow = { version = "1.0" }
tuirealm = { version = "1.7.0" }
tui-realm-stdlib = { version = "1.1.6" }
155 changes: 155 additions & 0 deletions terminal-tui/examples/tui-demo/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use anyhow::Result;

use tui_realm_stdlib::Textarea;

use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{AttrValue, Attribute, BorderSides, Borders, Color, TextSpan};
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
use tuirealm::{Frame, Sub, SubClause, SubEventClause};

use radicle_terminal_tui as tui;
use tui::components::{ApplicationTitle, Shortcut, ShortcutBar, TabContainer};
use tui::{App, Tui};

use super::components::GlobalListener;

/// Messages handled by this tui-application.
#[derive(Debug, Eq, PartialEq)]
pub enum Message {
Quit,
}

/// All components known to the application.
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum Id {
Global,
Title,
Content,
Shortcuts,
}

/// App-window used by this application.
pub struct Demo {
quit: bool,
}

/// Creates a new application using a tui-realm-application, mounts all
/// components and sets focus to a default one.
impl Demo {
pub fn welcome_content() -> Vec<TextSpan> {
vec![
TextSpan::new("# Welcome").fg(Color::Cyan),
TextSpan::new(String::new()),
TextSpan::from("This is a basic Radicle TUI application."),
]
}

pub fn help_content() -> Vec<TextSpan> {
vec![
TextSpan::new("# Help").fg(Color::Cyan),
TextSpan::new(String::new()),
TextSpan::from("Please see https://radicle.xyz for further information."),
]
}

fn layout(app: &mut App<Id, Message>, frame: &mut Frame) -> Vec<Rect> {
let area = frame.size();
let title_h = app
.query(Id::Title, Attribute::Height)
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let shortcuts_h = app
.query(Id::Shortcuts, Attribute::Height)
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let container_h = area.height.saturating_sub(title_h + shortcuts_h);

Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints(
[
Constraint::Length(title_h),
Constraint::Length(container_h - 2),
Constraint::Length(shortcuts_h),
]
.as_ref(),
)
.split(area)
}
}

impl Default for Demo {
fn default() -> Self {
Self { quit: false }
}
}

impl Tui<Id, Message> for Demo {
fn init(&mut self, app: &mut App<Id, Message>) -> Result<()> {
// Add app components
app.mount(Id::Title, ApplicationTitle::new("my-project"), vec![])?;
app.mount(
Id::Content,
TabContainer::default()
.child(
String::from("Welcome"),
Textarea::default()
.borders(Borders::default().sides(BorderSides::NONE))
.text_rows(&Self::welcome_content()),
)
.child(
String::from("Help"),
Textarea::default()
.borders(Borders::default().sides(BorderSides::NONE))
.text_rows(&Self::help_content()),
),
vec![],
)?;
app.mount(
Id::Shortcuts,
ShortcutBar::default()
.child(Shortcut::new("q", "quit"))
.child(Shortcut::new("?", "help")),
vec![],
)?;

// Add global key listener and subscribe to key events
app.mount(
Id::Global,
GlobalListener::default(),
vec![Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('q'),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
)],
)?;

// We need to give focus to a component then
app.activate(Id::Content)?;

Ok(())
}

fn view(&mut self, app: &mut App<Id, Message>, frame: &mut Frame) {
let layout = Self::layout(app, frame);

app.view(Id::Title, frame, layout[0]);
app.view(Id::Content, frame, layout[1]);
app.view(Id::Shortcuts, frame, layout[2]);
}

fn update(&mut self, app: &mut App<Id, Message>) {
for message in app.poll() {
match message {
Message::Quit => self.quit = true,
}
}
}

fn quit(&self) -> bool {
self.quit
}
}
60 changes: 60 additions & 0 deletions terminal-tui/examples/tui-demo/components.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use tui_realm_stdlib::{Phantom, Textarea};

use tuirealm::command::{Cmd, CmdResult, Direction};
use tuirealm::event::{Event, Key, KeyEvent};
use tuirealm::{Component, MockComponent, NoUserEvent};

use radicle_terminal_tui as tui;
use tui::components::{ApplicationTitle, ShortcutBar, TabContainer};

use super::app::Message;

#[derive(Default, MockComponent)]
pub struct GlobalListener {
component: Phantom,
}

impl Component<Message, NoUserEvent> for GlobalListener {
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
match event {
Event::Keyboard(KeyEvent {
code: Key::Char('q'),
..
}) => Some(Message::Quit),
_ => None,
}
}
}

/// Since `terminal-tui` does not know the type of messages that are being
/// passed around in the app, the following handlers need to be implemented for
/// each component used.
impl Component<Message, NoUserEvent> for ApplicationTitle {
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
None
}
}

impl Component<Message, NoUserEvent> for Textarea {
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
None
}
}

impl Component<Message, NoUserEvent> for ShortcutBar {
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
None
}
}

impl Component<Message, NoUserEvent> for TabContainer {
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
let _ = match event {
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
self.perform(Cmd::Move(Direction::Right))
}
_ => CmdResult::None,
};
None
}
}
20 changes: 20 additions & 0 deletions terminal-tui/examples/tui-demo/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use anyhow::Result;

use radicle_terminal_tui as tui;
use tui::Window;

/// App-specific window implementation.
mod app;

/// Event handler implementations for app-specific message types, since
/// these are not known to `terminal-tui` and need to exist for every component
/// used.
mod components;

/// Runs a basic tui-application with a title, some content and shortcuts.
fn main() -> Result<()> {
let mut window = Window::default();
window.run(&mut app::Demo::default())?;

Ok(())
}
Loading