Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Add flux widget for shortcuts
Erik Kundt committed 2 years ago
commit 98132c68e0f5344687b40e7eb6377b6749720e9a
parent ce675b03b5e7395525582dd244866c1767fb3ec7
3 files changed +228 -23
modified src/flux/ui.rs
@@ -1,44 +1,28 @@
pub mod layout;
+
pub mod widget;
+
pub mod theme;

use std::io::{self};
use std::thread;
use std::time::Duration;

-
use termion::event::{Event, Key};
+
use termion::event::Event;
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};

use ratatui::prelude::*;

use tokio::sync::broadcast;
-
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
-

-
use super::termination::Interrupted;
+
use tokio::sync::mpsc::{self, UnboundedReceiver};

use super::store::State;
+
use super::termination::Interrupted;
+
use super::ui::widget::{Render, Widget};

type Backend = TermionBackend<RawTerminal<io::Stdout>>;

const RENDERING_TICK_RATE: Duration = Duration::from_millis(250);

-
pub trait Widget<S, A> {
-
    fn new(state: &S, action_tx: UnboundedSender<A>) -> Self
-
    where
-
        Self: Sized;
-

-
    fn move_with_state(self, state: &S) -> Self
-
    where
-
        Self: Sized;
-

-
    fn name(&self) -> &str;
-

-
    fn handle_key_event(&mut self, key: Key);
-
}
-

-
pub trait Render<P> {
-
    fn render<B: ratatui::backend::Backend>(&self, frame: &mut Frame, props: P);
-
}
-

pub struct Frontend<A> {
    action_tx: mpsc::UnboundedSender<A>,
}
@@ -85,7 +69,7 @@ impl<A> Frontend<A> {
                }
            }

-
            terminal.draw(|frame| root.render::<Backend>(frame, ()))?;
+
            terminal.draw(|frame| root.render::<Backend>(frame, frame.size(), ()))?;
        };

        restore_terminal(&mut terminal)?;
added src/flux/ui/theme.rs
@@ -0,0 +1,101 @@
+
pub mod style {
+
    use ratatui::style::{Color, Modifier, Style};
+

+
    pub fn reset() -> Style {
+
        Style::default().fg(Color::Reset)
+
    }
+

+
    pub fn reset_dim() -> Style {
+
        Style::default()
+
            .fg(Color::Reset)
+
            .add_modifier(Modifier::DIM)
+
    }
+

+
    pub fn red() -> Style {
+
        Style::default().fg(Color::Red)
+
    }
+

+
    pub fn green() -> Style {
+
        Style::default().fg(Color::Green)
+
    }
+

+
    pub fn yellow() -> Style {
+
        Style::default().fg(Color::Yellow)
+
    }
+

+
    pub fn yellow_dim() -> Style {
+
        yellow().add_modifier(Modifier::DIM)
+
    }
+

+
    pub fn yellow_dim_reversed() -> Style {
+
        yellow_dim().add_modifier(Modifier::REVERSED)
+
    }
+

+
    pub fn blue() -> Style {
+
        Style::default().fg(Color::Blue)
+
    }
+

+
    pub fn magenta() -> Style {
+
        Style::default().fg(Color::Magenta)
+
    }
+

+
    pub fn magenta_dim() -> Style {
+
        Style::default()
+
            .fg(Color::Magenta)
+
            .add_modifier(Modifier::DIM)
+
    }
+

+
    pub fn cyan() -> Style {
+
        Style::default().fg(Color::Cyan)
+
    }
+

+
    pub fn lightblue() -> Style {
+
        Style::default().fg(Color::LightBlue)
+
    }
+

+
    pub fn gray() -> Style {
+
        Style::default().fg(Color::Gray)
+
    }
+

+
    pub fn gray_dim() -> Style {
+
        Style::default().fg(Color::Gray).add_modifier(Modifier::DIM)
+
    }
+

+
    pub fn darkgray() -> Style {
+
        Style::default().fg(Color::DarkGray)
+
    }
+

+
    pub fn reversed() -> Style {
+
        Style::default().add_modifier(Modifier::REVERSED)
+
    }
+

+
    pub fn default_reversed() -> Style {
+
        Style::default()
+
            .fg(Color::DarkGray)
+
            .add_modifier(Modifier::REVERSED)
+
    }
+

+
    pub fn magenta_reversed() -> Style {
+
        Style::default()
+
            .fg(Color::Magenta)
+
            .add_modifier(Modifier::REVERSED)
+
    }
+

+
    pub fn yellow_reversed() -> Style {
+
        Style::default().fg(Color::DarkGray).bg(Color::Yellow)
+
    }
+

+
    pub fn border(focus: bool) -> Style {
+
        if focus {
+
            gray_dim()
+
        } else {
+
            darkgray()
+
        }
+
    }
+

+
    pub fn highlight() -> Style {
+
        Style::default()
+
            .fg(Color::Cyan)
+
            .add_modifier(Modifier::REVERSED)
+
    }
+
}
added src/flux/ui/widget.rs
@@ -0,0 +1,120 @@
+
use tokio::sync::mpsc::UnboundedSender;
+

+
use termion::event::Key;
+

+
use ratatui::prelude::*;
+
use ratatui::widgets::{Row, Table};
+

+
use super::theme::style;
+

+
pub trait Widget<S, A> {
+
    fn new(state: &S, action_tx: UnboundedSender<A>) -> Self
+
    where
+
        Self: Sized;
+

+
    fn move_with_state(self, state: &S) -> Self
+
    where
+
        Self: Sized;
+

+
    fn name(&self) -> &str;
+

+
    fn handle_key_event(&mut self, key: Key);
+
}
+

+
pub trait Render<P> {
+
    fn render<B: ratatui::backend::Backend>(&self, frame: &mut Frame, area: Rect, props: P);
+
}
+

+
///
+
///
+
///
+
pub struct Shortcut {
+
    pub short: String,
+
    pub long: String,
+
}
+

+
impl Shortcut {
+
    pub fn new(short: &str, long: &str) -> Self {
+
        Self {
+
            short: short.to_string(),
+
            long: long.to_string(),
+
        }
+
    }
+
}
+

+
pub struct ShortcutWidgetProps {
+
    pub shortcuts: Vec<Shortcut>,
+
    pub divider: char,
+
}
+

+
pub struct ShortcutWidget<A> {
+
    pub action_tx: UnboundedSender<A>,
+
}
+

+
impl<S, A> Widget<S, A> for ShortcutWidget<A> {
+
    fn new(state: &S, action_tx: UnboundedSender<A>) -> Self
+
    where
+
        Self: Sized,
+
    {
+
        Self {
+
            action_tx: action_tx.clone(),
+
        }
+
        .move_with_state(state)
+
    }
+

+
    fn move_with_state(self, _state: &S) -> Self
+
    where
+
        Self: Sized,
+
    {
+
        Self { ..self }
+
    }
+

+
    fn name(&self) -> &str {
+
        "shortcuts"
+
    }
+

+
    fn handle_key_event(&mut self, _key: termion::event::Key) {}
+
}
+

+
impl<A> Render<ShortcutWidgetProps> for ShortcutWidget<A> {
+
    fn render<B: Backend>(
+
        &self,
+
        frame: &mut ratatui::Frame,
+
        area: Rect,
+
        props: ShortcutWidgetProps,
+
    ) {
+
        let mut shortcuts = props.shortcuts.iter().peekable();
+
        let mut row = vec![];
+

+
        while let Some(shortcut) = shortcuts.next() {
+
            let short = Text::from(shortcut.short.clone()).style(style::gray());
+
            let long = Text::from(shortcut.long.clone()).style(style::gray_dim());
+
            let spacer = Text::from(String::new());
+
            let divider =
+
                Text::from(String::from(format!(" {} ", props.divider))).style(style::gray_dim());
+

+
            row.push((1, short));
+
            row.push((1, spacer));
+
            row.push((shortcut.long.len(), long));
+

+
            if shortcuts.peek().is_some() {
+
                row.push((3, divider));
+
            }
+
        }
+

+
        let row_copy = row.clone();
+
        let row: Vec<Text<'_>> = row_copy
+
            .clone()
+
            .iter()
+
            .map(|(_, text)| text.clone())
+
            .collect();
+
        let widths: Vec<Constraint> = row_copy
+
            .clone()
+
            .iter()
+
            .map(|(width, _)| Constraint::Length(*width as u16))
+
            .collect();
+

+
        let table = Table::new([Row::new(row)], widths).column_spacing(0);
+
        frame.render_widget(table, area);
+
    }
+
}