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