Skip to main content

basalt_tui/
splash_modal.rs

1use std::marker::PhantomData;
2
3use basalt_core::obsidian::Vault;
4use ratatui::{
5    buffer::Buffer,
6    layout::{Constraint, Flex, Layout, Rect},
7    style::Stylize,
8    text::Text,
9    widgets::{BorderType, Clear, StatefulWidget, Widget},
10};
11
12use crate::{
13    app::Message as AppMessage,
14    vault_selector::{VaultSelector, VaultSelectorState},
15};
16
17#[derive(Clone, Debug, PartialEq)]
18pub enum Message {
19    Up,
20    Down,
21    Open,
22}
23
24pub fn update<'a>(message: &Message, state: &mut SplashModalState<'a>) -> Option<AppMessage<'a>> {
25    match message {
26        Message::Up => state.previous(),
27        Message::Down => state.next(),
28        Message::Open => {
29            state.select();
30            if let Some(vault) = state.selected_item() {
31                state.hide();
32                return Some(AppMessage::OpenVault(vault));
33            }
34        }
35    };
36
37    None
38}
39
40const TITLE: &str = "⋅𝕭𝖆𝖘𝖆𝖑𝖙⋅";
41
42pub const LOGO: [&str; 25] = [
43    "           ▒███▓░          ",
44    "          ▒█████▒░         ",
45    "        ▒███▒██▓▒▒░        ",
46    "      ▒████░██▓▒░▒▒░       ",
47    "     ▒███▒▒██▒▒░ ░▒▒░      ",
48    "   ▒████▓▓██▒░▒░  ░▒▒▒░    ",
49    " ▒█████▓▓▓██ ░▒░  ░░▒▒▒░   ",
50    "░████▓▓▒░░██ ░░ ░░░░░░▒▒░  ",
51    "▒██▓▓▒░░░▒██░░▒░░░    ░▒░  ",
52    "░███▓░░░░██▓░░▒▒▒▒░   ░▒▒  ",
53    " ▒███░░░░██░░░░▒▒▒▒▒░░░▒▒  ",
54    " ▒▒██▒░░░██░░░░░░░▒▒▒░ ░▒  ",
55    " ▓▒░██░░▒█▓░░ ░░▒▒▒▒░ ░░▒  ",
56    " █▒▒██▒░▓█░░ ░▒▒▒▒▒▒░ ░░▒░ ",
57    "▒█▒▓▒██░██░▒▒▒▒▒░░░░ ░░░▒▒░",
58    "▓█▒▓▒▓██▓█░░░░░░░░░  ░ ░░▒▒",
59    "██▓▓▒▒▓█▓▓ ░░░░░░░░░░░░░░▒▒",
60    "▒█▓▒░░ ▒▒▒░░░░ ░▒░░ ░░░▒▒▒░",
61    "░▒▒▒░░░ ░░░░░░░░░░░░░░░▒▒░ ",
62    " ░░▒▒░ ░ ░░░░░░░░░░░░▒▒░   ",
63    "   ░▒▒▒░ ░ ░░░░░░░░▒▒░░    ",
64    "     ░▒▒░░  ░░░░░░▒▒░      ",
65    "       ░▒▒░░░░░▒▒▒▒░       ",
66    "        ░░▒▒▒▒▒▒▒░         ",
67    "          ░░▒▒░            ",
68];
69
70#[derive(Debug, Default, Clone, PartialEq)]
71pub struct SplashModalState<'a> {
72    pub(crate) vault_selector_state: VaultSelectorState<'a>,
73    pub(crate) version: &'a str,
74    pub(crate) visible: bool,
75}
76
77impl<'a> SplashModalState<'a> {
78    pub fn new(version: &'a str, items: Vec<&'a Vault>, visible: bool) -> Self {
79        let vault_selector_state = VaultSelectorState::new(items);
80
81        SplashModalState {
82            version,
83            vault_selector_state,
84            visible,
85        }
86    }
87
88    pub fn hide(&mut self) {
89        self.visible = false;
90    }
91
92    pub fn select(&mut self) {
93        self.vault_selector_state.select();
94    }
95
96    pub fn items(self) -> Vec<&'a Vault> {
97        self.vault_selector_state.items
98    }
99
100    pub fn get_item(self, index: usize) -> Option<&'a Vault> {
101        self.vault_selector_state.items.get(index).cloned()
102    }
103
104    pub fn selected_item(&self) -> Option<&'a Vault> {
105        self.vault_selector_state
106            .selected()
107            .and_then(|index| self.vault_selector_state.items.get(index).cloned())
108    }
109
110    pub fn selected(&self) -> Option<usize> {
111        self.vault_selector_state.selected()
112    }
113
114    pub fn next(&mut self) {
115        self.vault_selector_state.next();
116    }
117
118    pub fn previous(&mut self) {
119        self.vault_selector_state.previous();
120    }
121}
122
123pub struct SplashModal<'a> {
124    _lifetime: PhantomData<&'a ()>,
125    pub border_type: BorderType,
126    pub vault_active: String,
127}
128
129impl<'a> SplashModal<'a> {
130    pub fn new(border_type: BorderType, vault_active: String) -> Self {
131        Self {
132            _lifetime: PhantomData,
133            border_type,
134            vault_active,
135        }
136    }
137}
138
139impl<'a> StatefulWidget for SplashModal<'a> {
140    type State = SplashModalState<'a>;
141
142    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
143        Clear.render(area, buf);
144
145        let [_, center, _] = Layout::horizontal([
146            Constraint::Fill(1),
147            Constraint::Length(79),
148            Constraint::Fill(1),
149        ])
150        .areas(area);
151
152        let [_, top, bottom, _, help] = Layout::vertical([
153            Constraint::Fill(1),
154            Constraint::Length(28),
155            Constraint::Min(6),
156            Constraint::Fill(1),
157            Constraint::Length(1),
158        ])
159        .flex(Flex::Center)
160        .margin(1)
161        .areas(center);
162
163        let [logo, title] =
164            Layout::vertical([Constraint::Fill(1), Constraint::Length(3)]).areas(top);
165
166        let [_, title, version] = Layout::horizontal([
167            Constraint::Fill(1),
168            Constraint::Fill(1),
169            Constraint::Fill(1),
170        ])
171        .flex(Flex::SpaceBetween)
172        .margin(1)
173        .areas(title);
174
175        let [bottom] = Layout::horizontal([Constraint::Length(60)])
176            .flex(Flex::Center)
177            .areas(bottom);
178
179        Text::from_iter(LOGO)
180            .dark_gray()
181            .centered()
182            .render(logo, buf);
183
184        Text::from(TITLE).dark_gray().centered().render(title, buf);
185
186        Text::from(state.version)
187            .dark_gray()
188            .italic()
189            .centered()
190            .render(version, buf);
191
192        Text::from("Press (?) for help")
193            .italic()
194            .dark_gray()
195            .centered()
196            .render(help, buf);
197
198        VaultSelector::new(self.border_type, self.vault_active).render(
199            bottom,
200            buf,
201            &mut state.vault_selector_state,
202        );
203    }
204}