basalt_tui/
start.rs

1use std::marker::PhantomData;
2
3use basalt_core::obsidian::Vault;
4use ratatui::{
5    buffer::Buffer,
6    layout::{Constraint, Flex, Layout, Rect, Size},
7    style::Stylize,
8    text::Text,
9    widgets::{StatefulWidgetRef, Widget},
10};
11
12use crate::vault_selector::{VaultSelector, VaultSelectorState};
13
14const TITLE: &str = "⋅𝕭𝖆𝖘𝖆𝖑𝖙⋅";
15
16pub const LOGO: [&str; 25] = [
17    "           ▒███▓░          ",
18    "          ▒█████▒░         ",
19    "        ▒███▒██▓▒▒░        ",
20    "      ▒████░██▓▒░▒▒░       ",
21    "     ▒███▒▒██▒▒░ ░▒▒░      ",
22    "   ▒████▓▓██▒░▒░  ░▒▒▒░    ",
23    " ▒█████▓▓▓██ ░▒░  ░░▒▒▒░   ",
24    "░████▓▓▒░░██ ░░ ░░░░░░▒▒░  ",
25    "▒██▓▓▒░░░▒██░░▒░░░    ░▒░  ",
26    "░███▓░░░░██▓░░▒▒▒▒░   ░▒▒  ",
27    " ▒███░░░░██░░░░▒▒▒▒▒░░░▒▒  ",
28    " ▒▒██▒░░░██░░░░░░░▒▒▒░ ░▒  ",
29    " ▓▒░██░░▒█▓░░ ░░▒▒▒▒░ ░░▒  ",
30    " █▒▒██▒░▓█░░ ░▒▒▒▒▒▒░ ░░▒░ ",
31    "▒█▒▓▒██░██░▒▒▒▒▒░░░░ ░░░▒▒░",
32    "▓█▒▓▒▓██▓█░░░░░░░░░  ░ ░░▒▒",
33    "██▓▓▒▒▓█▓▓ ░░░░░░░░░░░░░░▒▒",
34    "▒█▓▒░░ ▒▒▒░░░░ ░▒░░ ░░░▒▒▒░",
35    "░▒▒▒░░░ ░░░░░░░░░░░░░░░▒▒░ ",
36    " ░░▒▒░ ░ ░░░░░░░░░░░░▒▒░   ",
37    "   ░▒▒▒░ ░ ░░░░░░░░▒▒░░    ",
38    "     ░▒▒░░  ░░░░░░▒▒░      ",
39    "       ░▒▒░░░░░▒▒▒▒░       ",
40    "        ░░▒▒▒▒▒▒▒░         ",
41    "          ░░▒▒░            ",
42];
43
44#[derive(Debug, Default, Clone, PartialEq)]
45pub struct StartState<'a> {
46    pub(crate) vault_selector_state: VaultSelectorState<'a>,
47    pub(crate) size: Size,
48    pub(crate) version: &'a str,
49}
50
51impl<'a> StartState<'a> {
52    pub fn new(version: &'a str, size: Size, items: Vec<&'a Vault>) -> Self {
53        let vault_selector_state = VaultSelectorState::new(items);
54
55        StartState {
56            version,
57            size,
58            vault_selector_state,
59        }
60    }
61
62    pub fn select(&self) -> Self {
63        Self {
64            vault_selector_state: self.vault_selector_state.select(),
65            ..self.clone()
66        }
67    }
68
69    pub fn items(self) -> Vec<&'a Vault> {
70        self.vault_selector_state.items
71    }
72
73    pub fn get_item(self, index: usize) -> Option<&'a Vault> {
74        self.vault_selector_state.items.get(index).cloned()
75    }
76
77    pub fn selected(&self) -> Option<usize> {
78        self.vault_selector_state.selected()
79    }
80
81    pub fn next(self) -> Self {
82        Self {
83            vault_selector_state: self.vault_selector_state.next(),
84            ..self
85        }
86    }
87
88    pub fn previous(self) -> Self {
89        Self {
90            vault_selector_state: self.vault_selector_state.previous(),
91            ..self
92        }
93    }
94}
95
96#[derive(Default)]
97pub struct StartScreen<'a> {
98    _lifetime: PhantomData<&'a ()>,
99}
100
101impl<'a> StatefulWidgetRef for StartScreen<'a> {
102    type State = StartState<'a>;
103
104    fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
105        let [_, center, _] = Layout::horizontal([
106            Constraint::Fill(1),
107            Constraint::Length(79),
108            Constraint::Fill(1),
109        ])
110        .areas(area);
111
112        let [_, top, bottom, _, help] = Layout::vertical([
113            Constraint::Fill(1),
114            Constraint::Length(28),
115            Constraint::Min(6),
116            Constraint::Fill(1),
117            Constraint::Length(1),
118        ])
119        .flex(Flex::Center)
120        .margin(1)
121        .areas(center);
122
123        let [logo, title] =
124            Layout::vertical([Constraint::Fill(1), Constraint::Length(3)]).areas(top);
125
126        let [_, title, version] = Layout::horizontal([
127            Constraint::Fill(1),
128            Constraint::Fill(1),
129            Constraint::Fill(1),
130        ])
131        .flex(Flex::SpaceBetween)
132        .margin(1)
133        .areas(title);
134
135        let [bottom] = Layout::horizontal([Constraint::Length(60)])
136            .flex(Flex::Center)
137            .areas(bottom);
138
139        Text::from_iter(LOGO).black().centered().render(logo, buf);
140
141        Text::from(TITLE).dark_gray().centered().render(title, buf);
142
143        Text::from(state.version)
144            .black()
145            .italic()
146            .centered()
147            .render(version, buf);
148
149        Text::from("Press (?) for help")
150            .italic()
151            .black()
152            .centered()
153            .render(help, buf);
154
155        VaultSelector::default().render_ref(bottom, buf, &mut state.vault_selector_state);
156    }
157}