1use tui::{
4 layout::{Constraint, Direction, Layout, Rect},
5 style::Modifier,
6 text::{Span, Spans, Text},
7 widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
8};
9
10use crate::{configs::keycode_to_string, App, InputMode, Quest};
11
12pub fn main_chunks(area: Rect) -> Vec<Rect> {
14 let chunks = Layout::default()
15 .direction(Direction::Vertical)
16 .margin(2)
17 .constraints(
18 [
19 Constraint::Min(1),
20 Constraint::Length(3),
21 Constraint::Length(1),
22 ]
23 .as_ref(),
24 )
25 .split(area);
26
27 chunks
28}
29
30pub fn quest_list(app: &App) -> List {
32 let quests: Vec<ListItem> = app
34 .quests
35 .iter()
36 .enumerate()
37 .map(|q| indexed_quest_item(app, q))
38 .collect();
39
40 List::new(quests).style(app.default_style()).block(
41 Block::default()
42 .title("Quests")
43 .borders(Borders::ALL)
44 .border_type(BorderType::Rounded)
45 .style(app.default_style()),
46 )
47}
48
49fn indexed_quest_item<'a>(app: &'a App, (index, quest): (usize, &Quest)) -> ListItem<'a> {
51 if let Some(selected_index) = app.selected_quest {
52 quest_item(
53 quest.title.clone(),
54 quest.completed,
55 selected_index == index,
56 app,
57 )
58 } else {
59 quest_item(quest.title.clone(), quest.completed, false, app)
60 }
61}
62
63fn quest_item(title: String, completed: bool, selected: bool, app: &App) -> ListItem {
65 let style = if selected {
66 app.selection_style()
67 } else {
68 app.default_style()
69 };
70
71 let quest = if completed {
72 ListItem::new(Spans::from(vec![
73 Span::styled("✔ ", app.check_sign_style(selected)),
74 Span::styled(title, app.checked_quest_style(selected)),
75 ]))
76 } else {
77 ListItem::new(Spans::from(vec![
78 Span::styled(" ", style),
79 Span::styled(title, style),
80 ]))
81 };
82
83 quest.style(style)
84}
85
86pub fn quest_input(app: &App) -> Paragraph {
88 let style = match app.input_mode {
89 InputMode::Normal => app.default_style(),
90 InputMode::Adding => app.default_style().fg(app.configs.colors.selection_bg),
91 };
92
93 let input = Paragraph::new(app.input.as_ref()).style(style).block(
94 Block::default()
95 .borders(Borders::ALL)
96 .title("New Quest")
97 .border_type(BorderType::Rounded)
98 .style(style),
99 );
100
101 input
102}
103
104pub fn navigation_hint(app: &App) -> Paragraph {
106 let keybindings = &app.configs.keybindings;
107
108 let (msg, style) = match app.input_mode {
109 InputMode::Normal => (
110 vec![
111 Span::styled(
112 keycode_to_string(keybindings.exit_app),
113 app.default_style().add_modifier(Modifier::BOLD),
114 ),
115 Span::styled(" exit | ", app.default_style()),
116 Span::styled(
117 keycode_to_string(keybindings.new_quest),
118 app.default_style().add_modifier(Modifier::BOLD),
119 ),
120 Span::styled(" new quest | ", app.default_style()),
121 Span::styled(
122 keycode_to_string(keybindings.check_and_uncheck_quest),
123 app.default_style().add_modifier(Modifier::BOLD),
124 ),
125 Span::styled(" check/uncheck quest | ", app.default_style()),
126 Span::styled(
127 format!(
128 "{}/{}",
129 keycode_to_string(keybindings.list_up),
130 keycode_to_string(keybindings.list_down)
131 ),
132 app.default_style().add_modifier(Modifier::BOLD),
133 ),
134 Span::styled(" navigate list | ", app.default_style()),
135 Span::styled(
136 keycode_to_string(keybindings.delete_quest),
137 app.default_style().add_modifier(Modifier::BOLD),
138 ),
139 Span::styled(" delete quest", app.default_style()),
140 ],
141 app.default_style().add_modifier(Modifier::RAPID_BLINK),
142 ),
143 InputMode::Adding => (
144 vec![
145 Span::styled(
146 keycode_to_string(keybindings.exit_adding),
147 app.default_style().add_modifier(Modifier::BOLD),
148 ),
149 Span::styled(" stop adding | ", app.default_style()),
150 Span::styled(
151 keycode_to_string(keybindings.save_quest),
152 app.default_style().add_modifier(Modifier::BOLD),
153 ),
154 Span::styled(" save quest", app.default_style()),
155 ],
156 app.default_style(),
157 ),
158 };
159
160 let mut help_text = Text::from(Spans::from(msg));
161 help_text.patch_style(style);
162 Paragraph::new(help_text).style(app.default_style())
163}