1use itertools::Itertools;
2use std::{convert::TryFrom, fmt};
3use termion::color;
4
5use crate::utils::{format_str::FormattedString, str::CharacterLength};
6
7use super::{
8 frame::{self, FrameWidget, Title},
9 geometry, Widget,
10};
11
12lazy_static! {
13 static ref MARGIN: geometry::SideOffsets2D<u16> = geometry::SideOffsets2D::new(1, 2, 1, 2);
14 static ref BORDER: geometry::SideOffsets2D<u16> = geometry::SideOffsets2D::new_all_same(1);
15 static ref PADDING: geometry::SideOffsets2D<u16> = geometry::SideOffsets2D::new(1, 2, 1, 2);
16}
17
18#[derive(Debug)]
19pub struct HelpWidget {
20 pub bounds: geometry::Rect<u16>,
21}
22
23impl Widget for HelpWidget {
24 fn bounds(&self) -> geometry::Rect<u16> {
25 self.bounds
26 }
27}
28
29impl fmt::Display for HelpWidget {
30 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let frame_bounds = self.bounds.inner_rect(*MARGIN);
32 let inner_bounds = frame_bounds.inner_rect(*BORDER + *PADDING);
33
34 let frame_display = FrameWidget {
35 bounds: frame_bounds,
36 top_title: Some(Title::center("H E L P")),
37 bottom_title: Some(Title::right("Press any key to continue . . .")),
38 frame_style: &frame::DOUBLE,
39 };
40
41 write!(fmt, "{}", frame_display)?;
42
43 let inner_top_left = inner_bounds.origin;
44 let inner_top_middle = inner_top_left + geometry::vec2(inner_bounds.size.width / 2 + 1, 0);
45
46 for item in left_column_items(inner_top_left) {
47 write!(fmt, "{}", item)?;
48 }
49
50 for item in right_column_items(inner_top_middle) {
51 write!(fmt, "{}", item)?;
52 }
53
54 Ok(())
55 }
56}
57
58fn left_column_items(origin: geometry::Point2D<u16>) -> Vec<HelpItemWidget> {
59 let mut coord_iter = (0..).map(|index| origin + geometry::vec2(0, index));
60
61 vec![
62 HelpItemWidget::Mapping {
63 origin: coord_iter.next().unwrap(),
64 keys: HelpItemKeys::List(vec!["h", "j", "k", "l"]),
65 description: "Move",
66 },
67 HelpItemWidget::Mapping {
68 origin: coord_iter.next().unwrap(),
69 keys: HelpItemKeys::List(vec!["←", "↓", "↑", "→"]),
70 description: "Move",
71 },
72 HelpItemWidget::Skip {
73 origin: coord_iter.next().unwrap(),
74 },
75 HelpItemWidget::Mapping {
76 origin: coord_iter.next().unwrap(),
77 keys: HelpItemKeys::Single("s"),
78 description: "Go to Stock/Deck",
79 },
80 HelpItemWidget::Mapping {
81 origin: coord_iter.next().unwrap(),
82 keys: HelpItemKeys::Single("t"),
83 description: "Go to Talon/Waste",
84 },
85 HelpItemWidget::Mapping {
86 origin: coord_iter.next().unwrap(),
87 keys: HelpItemKeys::Single("f"),
88 description: "Go to next Foundation",
89 },
90 HelpItemWidget::Mapping {
91 origin: coord_iter.next().unwrap(),
92 keys: HelpItemKeys::Single("-"),
93 description: "Go back to previous area",
94 },
95 ]
96}
97
98fn right_column_items(origin: geometry::Point2D<u16>) -> Vec<HelpItemWidget> {
99 let mut coord_iter = (0..).map(|index| origin + geometry::vec2(0, index));
100
101 vec![
102 HelpItemWidget::Mapping {
103 origin: coord_iter.next().unwrap(),
104 keys: HelpItemKeys::Range("F1", "F4"),
105 description: "Go to Foundation",
106 },
107 HelpItemWidget::Mapping {
108 origin: coord_iter.next().unwrap(),
109 keys: HelpItemKeys::Range("1", "7"),
110 description: "Go to Tableaux",
111 },
112 HelpItemWidget::Skip {
113 origin: coord_iter.next().unwrap(),
114 },
115 HelpItemWidget::Mapping {
116 origin: coord_iter.next().unwrap(),
117 keys: HelpItemKeys::List(vec!["SPACE", "RETURN"]),
118 description: "Pick Up/Activate",
119 },
120 HelpItemWidget::Mapping {
121 origin: coord_iter.next().unwrap(),
122 keys: HelpItemKeys::Single("ESC"),
123 description: "Return Held Cards",
124 },
125 HelpItemWidget::Skip {
126 origin: coord_iter.next().unwrap(),
127 },
128 HelpItemWidget::Mapping {
129 origin: coord_iter.next().unwrap(),
130 keys: HelpItemKeys::Single("?"),
131 description: "Help",
132 },
133 HelpItemWidget::Mapping {
134 origin: coord_iter.next().unwrap(),
135 keys: HelpItemKeys::Single("q"),
136 description: "Quit",
137 },
138 ]
139}
140
141#[derive(Debug)]
142enum HelpItemKeys {
143 Single(&'static str),
144 List(Vec<&'static str>),
145 Range(&'static str, &'static str),
146}
147
148impl HelpItemKeys {
149 fn len(&self) -> usize {
150 match self {
151 Self::Single(key) => key.char_len(),
152 Self::List(keys) => keys.iter().map(|key| key.char_len()).intersperse(3).sum(),
153 Self::Range(start_key, end_key) => start_key.char_len() + end_key.char_len() + 5,
154 }
155 }
156}
157
158impl fmt::Display for HelpItemKeys {
159 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
160 match self {
161 Self::Single(key) => {
162 write!(fmt, "{key_style}{key}", key_style = key_style(), key = key)?;
163 }
164 Self::List(keys) => {
165 let formatted_keys_iter = keys
166 .iter()
167 .map(|key| FormattedString::new_with_formatting(key_style()).push_content(key))
168 .intersperse(
169 FormattedString::new_with_formatting(reset_style()).push_content(" / "),
170 );
171
172 for formatted_key in formatted_keys_iter {
173 write!(fmt, "{}", formatted_key)?;
174 }
175 }
176 Self::Range(start_key, end_key) => {
177 write!(
178 fmt,
179 "{key_style}{start_key}{reset} ... {key_style}{end_key}",
180 reset = reset_style(),
181 key_style = key_style(),
182 start_key = start_key,
183 end_key = end_key,
184 )?;
185 }
186 };
187
188 Ok(())
189 }
190}
191
192#[derive(Debug)]
193enum HelpItemWidget {
194 Mapping {
195 origin: geometry::Point2D<u16>,
196 keys: HelpItemKeys,
197 description: &'static str,
198 },
199 Skip {
200 origin: geometry::Point2D<u16>,
201 },
202}
203
204impl Widget for HelpItemWidget {
205 fn bounds(&self) -> geometry::Rect<u16> {
206 match self {
207 Self::Mapping {
208 origin,
209 keys,
210 description,
211 } => {
212 let length = u16::try_from(keys.len() + description.char_len() + 4).unwrap();
213 geometry::Rect::new(*origin, geometry::size2(length, 0))
214 }
215 Self::Skip { origin } => geometry::Rect::new(*origin, geometry::Size2D::zero()),
216 }
217 }
218}
219
220impl fmt::Display for HelpItemWidget {
221 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
222 if let Self::Mapping {
223 origin,
224 keys,
225 description,
226 } = self
227 {
228 let goto = geometry::goto(*origin);
229
230 write!(
231 fmt,
232 "{goto}{keys}{reset} : {desc_style}{desc}",
233 goto = goto,
234 reset = reset_style(),
235 desc_style = description_style(),
236 keys = keys,
237 desc = description,
238 )?;
239 }
240
241 Ok(())
242 }
243}
244
245fn key_style() -> impl fmt::Display {
246 color::Fg(color::Cyan)
247}
248
249fn reset_style() -> impl fmt::Display {
250 color::Fg(color::Reset)
251}
252
253fn description_style() -> impl fmt::Display {
254 color::Fg(color::White)
255}