Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Add message sending support from imUI
Erik Kundt committed 1 year ago
commit 79d722303b05bc9c1da6e65076a03fdc78e2fe35
parent 362693e45aba5130f6191f05693de4bcb163cdc2
3 files changed +138 -43
modified src/lib.rs
@@ -190,17 +190,18 @@ pub async fn im<S, M, P>(
) -> Result<Option<P>>
where
    S: State<P, Message = M> + Clone + Debug + Send + Sync + 'static,
-
    M: 'static,
+
    M: Clone + 'static,
    P: Clone + Debug + Send + Sync + 'static,
{
    let (terminator, mut interrupt_rx) = task::create_termination();

+
    let state_tx = channel.tx.clone();
    let (store, state_rx) = store::Store::<S, M, P>::new();
    let frontend = im::Frontend::default();

    tokio::try_join!(
        store.run(state, terminator, channel.rx, interrupt_rx.resubscribe()),
-
        frontend.run(app, state_rx, interrupt_rx.resubscribe()),
+
        frontend.run(app, state_tx, state_rx, interrupt_rx.resubscribe()),
    )?;

    if let Ok(reason) = interrupt_rx.recv().await {
modified src/ui/im.rs
@@ -9,7 +9,7 @@ use anyhow::Result;

use ratatui::text::Text;
use tokio::sync::broadcast;
-
use tokio::sync::mpsc::UnboundedReceiver;
+
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};

use termion::event::Key;

@@ -32,7 +32,12 @@ pub trait App {
    type State;
    type Message;

-
    fn update(&self, ui: &Context, frame: &mut Frame, state: &Self::State) -> Result<()>;
+
    fn update(
+
        &self,
+
        ctx: &Context<Self::Message>,
+
        frame: &mut Frame,
+
        state: &Self::State,
+
    ) -> Result<()>;
}

#[derive(Default)]
@@ -42,12 +47,13 @@ impl Frontend {
    pub async fn run<S, M, P>(
        self,
        app: impl App<State = S, Message = M>,
+
        state_tx: UnboundedSender<M>,
        mut state_rx: UnboundedReceiver<S>,
        mut interrupt_rx: broadcast::Receiver<Interrupted<P>>,
    ) -> anyhow::Result<Interrupted<P>>
    where
        S: State<P> + 'static,
-
        M: 'static,
+
        M: Clone + 'static,
        P: Clone + Send + Sync + Debug,
    {
        let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);
@@ -56,7 +62,7 @@ impl Frontend {
        let mut events_rx = terminal::events();

        let mut state = state_rx.recv().await.unwrap();
-
        let mut ctx = Context::default();
+
        let mut ctx = Context::default().with_sender(state_tx);

        let result: anyhow::Result<Interrupted<P>> = loop {
            tokio::select! {
@@ -115,17 +121,28 @@ impl<R> InnerResponse<R> {
    }
}

-
#[derive(Clone, Default, Debug)]
-
pub struct Context {
+
#[derive(Clone, Debug)]
+
pub struct Context<M> {
    pub(crate) inputs: VecDeque<Key>,
-
    frame_size: Rect,
+
    pub(crate) frame_size: Rect,
+
    pub(crate) sender: Option<UnboundedSender<M>>,
}

-
impl Context {
-
    pub fn new(frame_size: Rect) -> Self {
+
impl<M> Default for Context<M> {
+
    fn default() -> Self {
        Self {
            inputs: VecDeque::default(),
+
            frame_size: Rect::default(),
+
            sender: None,
+
        }
+
    }
+
}
+

+
impl<M> Context<M> {
+
    pub fn new(frame_size: Rect) -> Self {
+
        Self {
            frame_size,
+
            ..Default::default()
        }
    }

@@ -139,6 +156,11 @@ impl Context {
        self
    }

+
    pub fn with_sender(mut self, sender: UnboundedSender<M>) -> Self {
+
        self.sender = Some(sender);
+
        self
+
    }
+

    pub fn frame_size(&self) -> Rect {
        self.frame_size
    }
@@ -232,17 +254,17 @@ impl Layout {
    }
}

-
#[derive(Default, Clone, Debug)]
-
pub struct Ui {
+
#[derive(Clone, Debug)]
+
pub struct Ui<M> {
    pub theme: Theme,
    pub(crate) area: Rect,
    pub(crate) layout: Layout,
    focus: Option<usize>,
    count: usize,
-
    ctx: Context,
+
    ctx: Context<M>,
}

-
impl Ui {
+
impl<M> Ui<M> {
    pub fn input(&mut self, f: impl Fn(Key) -> bool) -> bool {
        self.has_focus() && self.ctx.inputs.iter().any(|key| f(*key))
    }
@@ -260,7 +282,20 @@ impl Ui {
    }
}

-
impl Ui {
+
impl<M> Default for Ui<M> {
+
    fn default() -> Self {
+
        Self {
+
            theme: Theme::default(),
+
            area: Rect::default(),
+
            layout: Layout::default(),
+
            focus: None,
+
            count: 0,
+
            ctx: Context::default(),
+
        }
+
    }
+
}
+

+
impl<M> Ui<M> {
    pub fn new(area: Rect) -> Self {
        Self {
            area,
@@ -278,11 +313,16 @@ impl Ui {
        self
    }

-
    pub fn with_ctx(mut self, ctx: Context) -> Self {
+
    pub fn with_ctx(mut self, ctx: Context<M>) -> Self {
        self.ctx = ctx;
        self
    }

+
    // pub fn with_sender(mut self, sender: UnboundedSender<M>) -> Self {
+
    //     self.sender = Some(sender);
+
    //     self
+
    // }
+

    pub fn area(&self) -> Rect {
        self.area
    }
@@ -325,9 +365,18 @@ impl Ui {
            self.focus = Some(self.focus.unwrap().saturating_add(1));
        }
    }
+

+
    pub fn send_message(&self, message: M) {
+
        if let Some(sender) = &self.ctx.sender {
+
            let _ = sender.send(message);
+
        }
+
    }
}

-
impl Ui {
+
impl<M> Ui<M>
+
where
+
    M: Clone,
+
{
    pub fn add(&mut self, frame: &mut Frame, widget: impl Widget) -> Response {
        widget.ui(self, frame)
    }
@@ -360,12 +409,15 @@ impl Ui {
    }
}

-
impl Ui {
+
impl<M> Ui<M>
+
where
+
    M: Clone,
+
{
    pub fn group<R>(
        &mut self,
        layout: impl Into<Layout>,
        focus: &mut Option<usize>,
-
        add_contents: impl FnOnce(&mut Ui) -> R,
+
        add_contents: impl FnOnce(&mut Ui<M>) -> R,
    ) -> InnerResponse<R> {
        let (area, _) = self.next_area().unwrap_or_default();

modified src/ui/im/widget.rs
@@ -16,7 +16,9 @@ use crate::ui::{Column, ToRow};
use super::{Borders, Context, InnerResponse, Response, Ui};

pub trait Widget {
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response;
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone;
}

#[derive(Default)]
@@ -24,19 +26,25 @@ pub struct Window {}

impl Window {
    #[inline]
-
    pub fn show<R>(
+
    pub fn show<M, R>(
        self,
-
        ctx: &Context,
-
        add_contents: impl FnOnce(&mut Ui) -> R,
-
    ) -> Option<InnerResponse<Option<R>>> {
+
        ctx: &Context<M>,
+
        add_contents: impl FnOnce(&mut Ui<M>) -> R,
+
    ) -> Option<InnerResponse<Option<R>>>
+
    where
+
        M: Clone,
+
    {
        self.show_dyn(ctx, Box::new(add_contents))
    }

-
    fn show_dyn<'c, R>(
+
    fn show_dyn<'c, M, R>(
        self,
-
        ctx: &Context,
-
        add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
-
    ) -> Option<InnerResponse<Option<R>>> {
+
        ctx: &Context<M>,
+
        add_contents: Box<dyn FnOnce(&mut Ui<M>) -> R + 'c>,
+
    ) -> Option<InnerResponse<Option<R>>>
+
    where
+
        M: Clone,
+
    {
        let mut ui = Ui::default()
            .with_area(ctx.frame_size())
            .with_ctx(ctx.clone())
@@ -92,15 +100,25 @@ impl<'a> Group<'a> {
        Self { len, focus }
    }

-
    pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
+
    pub fn show<M, R>(
+
        self,
+
        ui: &mut Ui<M>,
+
        add_contents: impl FnOnce(&mut Ui<M>) -> R,
+
    ) -> InnerResponse<R>
+
    where
+
        M: Clone,
+
    {
        self.show_dyn(ui, Box::new(add_contents))
    }

-
    pub fn show_dyn<'c, R>(
+
    pub fn show_dyn<'c, M, R>(
        self,
-
        ui: &mut Ui,
-
        add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
-
    ) -> InnerResponse<R> {
+
        ui: &mut Ui<M>,
+
        add_contents: Box<dyn FnOnce(&mut Ui<M>) -> R + 'c>,
+
    ) -> InnerResponse<R>
+
    where
+
        M: Clone,
+
    {
        let mut response = Response::default();

        let mut state = GroupState {
@@ -147,7 +165,7 @@ impl<'a> Label<'a> {
}

impl<'a> Widget for Label<'a> {
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response {
        let (area, _) = ui.next_area().unwrap_or_default();
        frame.render_widget(self.content, area);

@@ -272,7 +290,10 @@ impl<'a, R, const W: usize> Widget for Table<'a, R, W>
where
    R: ToRow<W> + Clone,
{
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone,
+
    {
        let mut response = Response::default();

        let (area, has_focus) = ui.next_area().unwrap_or_default();
@@ -441,7 +462,10 @@ impl<'a, R, const W: usize> Widget for HeaderedTable<'a, R, W>
where
    R: ToRow<W> + Clone,
{
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone,
+
    {
        let mut response = Response::default();

        let (_, has_focus) = ui.current_area().unwrap_or_default();
@@ -486,7 +510,10 @@ impl<'a> Columns<'a> {
}

impl<'a> Widget for Columns<'a> {
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone,
+
    {
        let (area, has_focus) = ui.next_area().unwrap_or_default();

        let border_style = if has_focus {
@@ -542,7 +569,10 @@ impl<'a> Bar<'a> {
}

impl<'a> Widget for Bar<'a> {
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone,
+
    {
        let (area, has_focus) = ui.next_area().unwrap_or_default();

        let border_style = if has_focus {
@@ -653,7 +683,10 @@ impl<'a> TextView<'a> {
}

impl<'a> Widget for TextView<'a> {
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone,
+
    {
        let mut response = Response::default();

        let (area, has_focus) = ui.next_area().unwrap_or_default();
@@ -868,7 +901,10 @@ impl<'a> TextEdit<'a> {
}

impl<'a> TextEdit<'a> {
-
    pub fn show(self, ui: &mut Ui, frame: &mut Frame) -> TextEditOutput {
+
    pub fn show<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> TextEditOutput
+
    where
+
        M: Clone,
+
    {
        let mut response = Response::default();

        let (area, has_focus) = ui.next_area().unwrap_or_default();
@@ -969,7 +1005,10 @@ impl<'a> TextEdit<'a> {
}

impl<'a> Widget for TextEdit<'a> {
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone,
+
    {
        self.show(ui, frame).response
    }
}
@@ -992,7 +1031,10 @@ impl Shortcuts {
}

impl Widget for Shortcuts {
-
    fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
    fn ui<M>(self, ui: &mut Ui<M>, frame: &mut Frame) -> Response
+
    where
+
        M: Clone,
+
    {
        use ratatui::widgets::Table;

        let (area, _) = ui.next_area().unwrap_or_default();