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}