mirror of
https://seed.radicle.dev/rad:z39mP9rQAaGmERfUMPULfPUi473tY.git
synced 2026-10-18 19:10:36 +08:00
168 lines
4.3 KiB
Rust
168 lines
4.3 KiB
Rust
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use anyhow::Result;
|
|
|
|
use ratatui::layout::{Constraint, Layout};
|
|
use ratatui::style::Stylize;
|
|
use ratatui::text::Span;
|
|
use ratatui::widgets::Cell;
|
|
use ratatui::{Frame, Viewport};
|
|
|
|
use radicle_tui as tui;
|
|
|
|
use tui::event::Key;
|
|
use tui::task::EmptyProcessors;
|
|
use tui::ui::im::widget::Window;
|
|
use tui::ui::im::{Borders, Context};
|
|
use tui::ui::Spacing;
|
|
use tui::ui::{Column, ToRow};
|
|
use tui::Channel;
|
|
use tui::{
|
|
store::Update,
|
|
ui::im::{widget::TableState, Show},
|
|
Exit,
|
|
};
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct Item {
|
|
id: usize,
|
|
title: String,
|
|
timestamp: usize,
|
|
}
|
|
|
|
impl ToRow<3> for Item {
|
|
fn to_row(&self) -> [Cell<'_>; 3] {
|
|
[
|
|
Span::raw(self.id.to_string()).magenta().dim().into(),
|
|
Span::raw(self.title.clone()).into(),
|
|
Span::raw(self.timestamp.to_string())
|
|
.dark_gray()
|
|
.italic()
|
|
.into(),
|
|
]
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct App {
|
|
items: Vec<Item>,
|
|
selector: TableState,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
enum Message {
|
|
SelectionChanged { state: TableState },
|
|
Return,
|
|
Quit,
|
|
}
|
|
|
|
impl Update<Message> for App {
|
|
type Return = usize;
|
|
|
|
fn update(&mut self, message: Message) -> Option<tui::Exit<Self::Return>> {
|
|
match message {
|
|
Message::SelectionChanged { state } => {
|
|
self.selector = state;
|
|
None
|
|
}
|
|
Message::Return => self
|
|
.selector
|
|
.selected()
|
|
.and_then(|selected| self.items.get(selected))
|
|
.map(|item| Exit {
|
|
value: Some(item.id),
|
|
}),
|
|
Message::Quit => Some(Exit { value: None }),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Show<Message> for App {
|
|
fn show(&self, ctx: &Context<Message>, frame: &mut Frame) -> Result<()> {
|
|
Window::default().show(ctx, |ui| {
|
|
ui.layout(
|
|
Layout::vertical([
|
|
Constraint::Length(1),
|
|
Constraint::Min(1),
|
|
Constraint::Length(1),
|
|
]),
|
|
Some(1),
|
|
|ui| {
|
|
let columns = [
|
|
Column::new(Span::raw("Id").bold(), Constraint::Length(4)),
|
|
Column::new(Span::raw("Title").bold(), Constraint::Fill(1)),
|
|
Column::new(Span::raw("Timestamp").bold(), Constraint::Fill(1)),
|
|
]
|
|
.to_vec();
|
|
let mut selected = self.selector.selected();
|
|
|
|
ui.column_bar(
|
|
frame,
|
|
columns.clone(),
|
|
Spacing::default(),
|
|
Some(Borders::None),
|
|
);
|
|
|
|
let table = ui.table(
|
|
frame,
|
|
&mut selected,
|
|
&self.items,
|
|
columns,
|
|
None,
|
|
Some(Borders::None),
|
|
);
|
|
if table.changed {
|
|
ui.send_message(Message::SelectionChanged {
|
|
state: TableState::new(selected),
|
|
})
|
|
}
|
|
|
|
ui.shortcuts(frame, &[("q", "quit")], '|');
|
|
|
|
if ui.has_input(|key| key == Key::Enter) {
|
|
ui.send_message(Message::Return);
|
|
}
|
|
},
|
|
);
|
|
|
|
if ui.has_input(|key| key == Key::Char('q')) {
|
|
ui.send_message(Message::Quit);
|
|
}
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
pub async fn main() -> Result<()> {
|
|
let app = App {
|
|
items: (0..200)
|
|
.map(|id| Item {
|
|
id,
|
|
title: format!("Title of item #{id}"),
|
|
timestamp: SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.expect("Could not read system time")
|
|
.as_secs() as usize,
|
|
})
|
|
.collect(),
|
|
selector: TableState::new(Some(0)),
|
|
};
|
|
|
|
if let Some(exit) = tui::im(
|
|
app,
|
|
Viewport::Inline(12),
|
|
Channel::default(),
|
|
EmptyProcessors::new(),
|
|
)
|
|
.await?
|
|
{
|
|
println!("{exit}");
|
|
} else {
|
|
anyhow::bail!("No selection");
|
|
}
|
|
|
|
Ok(())
|
|
}
|