ratatui-kit 0.6.0

A framework for building interactive terminal user interfaces with ratatui
Documentation
use std::{pin::pin, task::Poll};

use crossterm::event::Event;
use futures::Stream;
use ratatui::layout::Rect;

use crate::{Hook, Hooks, TerminalEvents};

mod private {
    pub trait Sealed {}
    impl Sealed for crate::hooks::Hooks<'_, '_> {}
}

pub trait UseEvents: private::Sealed {
    /// 注册全局事件监听器,适合快捷键、全局输入等场景。
    fn use_events<F>(&mut self, f: F)
    where
        F: FnMut(Event) + Send + 'static;

    /// 注册仅作用于当前组件的事件监听器,适合局部交互。
    fn use_local_events<F>(&mut self, f: F)
    where
        F: FnMut(Event) + Send + 'static;
}

impl UseEvents for Hooks<'_, '_> {
    fn use_events<F>(&mut self, f: F)
    where
        F: FnMut(Event) + Send + 'static,
    {
        let h = self.use_hook(move || UseEventsImpl {
            events: None,
            component_area: Default::default(),
            in_component: false,
            f: None,
        });
        h.f = Some(Box::new(f));
    }

    fn use_local_events<F>(&mut self, f: F)
    where
        F: FnMut(Event) + Send + 'static,
    {
        let h = self.use_hook(move || UseEventsImpl {
            events: None,
            component_area: Default::default(),
            in_component: true,
            f: None,
        });
        h.f = Some(Box::new(f));
    }
}

struct UseEventsImpl {
    f: Option<Box<dyn FnMut(Event) + Send>>,
    events: Option<TerminalEvents<Event>>,
    in_component: bool,
    component_area: Rect,
}

impl Hook for UseEventsImpl {
    fn poll_change(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context,
    ) -> std::task::Poll<()> {
        while let Some(Poll::Ready(Some(event))) = self
            .events
            .as_mut()
            .map(|events| pin!(events).poll_next(cx))
        {
            let area = self.component_area;
            let in_component = self.in_component;
            if let Some(f) = &mut self.f {
                if in_component {
                    match event {
                        Event::Mouse(mouse_event) => {
                            if mouse_event.row >= area.y && mouse_event.column >= area.x {
                                let row = mouse_event.row - area.y;
                                let column = mouse_event.column - area.x;

                                if row < area.height && column < area.width {
                                    f(event);
                                }
                            }
                        }
                        _ => {
                            f(event);
                        }
                    }
                } else {
                    f(event);
                }
            }
        }
        Poll::Pending
    }

    fn post_component_update(&mut self, updater: &mut crate::ComponentUpdater) {
        if self.events.is_none() {
            self.events = updater.terminal().events().ok();
        }
    }

    fn pre_component_draw(&mut self, drawer: &mut crate::ComponentDrawer) {
        self.component_area = drawer.area;
    }
}