use std::time::Duration; use anyhow::Result; use tuirealm::application::PollStrategy; use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers}; use tuirealm::props::{AttrValue, Attribute}; use tuirealm::tui::layout::{Constraint, Direction, Layout}; use tuirealm::{Application, Component, Frame, NoUserEvent, Sub, SubClause, SubEventClause}; use radicle_tui::ui; use radicle_tui::ui::components::{GlobalListener, LabeledContainer, PropertyList, ShortcutBar}; use radicle_tui::ui::theme; use radicle_tui::ui::widget::Widget; use radicle_tui::Tui; use radicle::identity::{Id, Project}; #[allow(dead_code)] pub struct App { id: Id, project: Project, quit: bool, } /// Messages handled by this application. #[derive(Debug, Eq, PartialEq)] pub enum Message { Quit, } /// All components known to this application. #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum ComponentId { Workspaces, ShortcutBar, GlobalListener, } /// Creates a new application using a tui-realm-application, mounts all /// components and sets focus to a default one. impl App { pub fn new(id: Id, project: Project) -> Self { Self { id, project, quit: false, } } } impl Tui for App { fn init(&mut self, app: &mut Application) -> Result<()> { let theme = theme::default_dark(); app.mount( ComponentId::Workspaces, ui::labeled_container( &theme, "about", ui::property_list( &theme, vec![ ui::property(&theme, "id", &self.id.to_string()), ui::property(&theme, "name", self.project.name()), ui::property(&theme, "description", self.project.description()), ], ) .to_boxed(), ) .to_boxed(), vec![], )?; app.mount( ComponentId::ShortcutBar, ui::shortcut_bar( &theme, vec![ ui::shortcut(&theme, "tab", "section"), ui::shortcut(&theme, "q", "quit"), ], ) .to_boxed(), vec![], )?; // Add global key listener and subscribe to key events app.mount( ComponentId::GlobalListener, ui::global_listener().to_boxed(), 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.active(&ComponentId::ShortcutBar)?; Ok(()) } fn view( &mut self, app: &mut Application, frame: &mut Frame, ) { let area = frame.size(); let margin_h = 1u16; let shortcuts_h = app .query(&ComponentId::ShortcutBar, Attribute::Height) .ok() .flatten() .unwrap_or(AttrValue::Size(0)) .unwrap_size(); let workspaces_h = area .height .saturating_sub(shortcuts_h.saturating_add(margin_h)); let layout = Layout::default() .direction(Direction::Vertical) .horizontal_margin(margin_h) .constraints( [ Constraint::Length(workspaces_h), Constraint::Length(shortcuts_h), ] .as_ref(), ) .split(area); app.view(&ComponentId::Workspaces, frame, layout[0]); app.view(&ComponentId::ShortcutBar, frame, layout[1]); } fn update(&mut self, app: &mut Application, interval: u64) { if let Ok(messages) = app.tick(PollStrategy::TryFor(Duration::from_millis(interval))) { for message in messages { match message { Message::Quit => self.quit = true, } } } } fn quit(&self) -> bool { self.quit } } /// Since the framework 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 for Widget { fn on(&mut self, event: Event) -> Option { match event { Event::Keyboard(KeyEvent { code: Key::Char('q'), .. }) => Some(Message::Quit), _ => None, } } } impl Component for Widget { fn on(&mut self, _event: Event) -> Option { None } } impl Component for Widget { fn on(&mut self, _event: Event) -> Option { None } } impl Component for Widget { fn on(&mut self, _event: Event) -> Option { None } }