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}