1use super::menu::{Menu, MenuKind};
2use super::notification::Notification;
3use super::{AppCmd, EntryId, EntryKind, RenderData, RenderQueue, View, ViewId};
4use crate::context::Context;
5use crate::device::CURRENT_DEVICE;
6use crate::framebuffer::UpdateMode;
7use crate::geom::{Point, Rectangle};
8use crate::settings::{ButtonScheme, RotationLock};
9use chrono::Local;
10use std::sync::mpsc;
11
12pub fn shift(view: &mut dyn View, delta: Point) {
13 *view.rect_mut() += delta;
14 for child in view.children_mut().iter_mut() {
15 shift(child.as_mut(), delta);
16 }
17}
18
19pub fn locate<T: View>(view: &dyn View) -> Option<usize> {
20 for (index, child) in view.children().iter().enumerate() {
21 if child.as_ref().is::<T>() {
22 return Some(index);
23 }
24 }
25 None
26}
27
28pub fn rlocate<T: View>(view: &dyn View) -> Option<usize> {
29 for (index, child) in view.children().iter().enumerate().rev() {
30 if child.as_ref().is::<T>() {
31 return Some(index);
32 }
33 }
34 None
35}
36
37pub fn locate_by_id(view: &dyn View, id: ViewId) -> Option<usize> {
38 view.children()
39 .iter()
40 .position(|c| c.view_id().map_or(false, |i| i == id))
41}
42
43pub fn overlapping_rectangle(view: &dyn View) -> Rectangle {
44 let mut rect = *view.rect();
45 for child in view.children() {
46 rect.absorb(&overlapping_rectangle(child.as_ref()));
47 }
48 rect
49}
50
51pub fn transfer_notifications(
53 view1: &mut dyn View,
54 view2: &mut dyn View,
55 rq: &mut RenderQueue,
56 context: &mut Context,
57) {
58 for index in (0..view1.len()).rev() {
59 if view1.child(index).is::<Notification>() {
60 let mut child = view1.children_mut().remove(index);
61 if view2.rect() != view1.rect() {
62 let (tx, _rx) = mpsc::channel();
63 child.resize(*view2.rect(), &tx, rq, context);
64 }
65 view2.children_mut().push(child);
66 }
67 }
68}
69
70pub fn find_notification_mut(view: &mut dyn View, id: ViewId) -> Option<&mut Notification> {
86 if view.is::<Notification>() && view.view_id() == Some(id) {
87 return view.downcast_mut::<Notification>();
88 }
89
90 for child in view.children_mut() {
91 if let Some(notif) = find_notification_mut(child.as_mut(), id) {
92 return Some(notif);
93 }
94 }
95
96 None
97}
98
99pub fn toggle_main_menu(
100 view: &mut dyn View,
101 rect: Rectangle,
102 enable: Option<bool>,
103 rq: &mut RenderQueue,
104 context: &mut Context,
105) {
106 if let Some(index) = locate_by_id(view, ViewId::MainMenu) {
107 if let Some(true) = enable {
108 return;
109 }
110 rq.add(RenderData::expose(
111 *view.child(index).rect(),
112 UpdateMode::Gui,
113 ));
114 view.children_mut().remove(index);
115 } else {
116 if let Some(false) = enable {
117 return;
118 }
119
120 let rotation = CURRENT_DEVICE.to_canonical(context.display.rotation);
121 let rotate = (0..4)
122 .map(|n| {
123 EntryKind::RadioButton(
124 (n as i16 * 90).to_string(),
125 EntryId::Rotate(CURRENT_DEVICE.from_canonical(n)),
126 n == rotation,
127 )
128 })
129 .collect::<Vec<EntryKind>>();
130
131 let apps = vec![
132 EntryKind::Command(
133 "Dictionary".to_string(),
134 EntryId::Launch(AppCmd::Dictionary {
135 query: "".to_string(),
136 language: "".to_string(),
137 }),
138 ),
139 EntryKind::Command(
140 "Calculator".to_string(),
141 EntryId::Launch(AppCmd::Calculator),
142 ),
143 EntryKind::Command("Sketch".to_string(), EntryId::Launch(AppCmd::Sketch)),
144 EntryKind::Separator,
145 EntryKind::Command(
146 "Touch Events".to_string(),
147 EntryId::Launch(AppCmd::TouchEvents),
148 ),
149 EntryKind::Command(
150 "Rotation Values".to_string(),
151 EntryId::Launch(AppCmd::RotationValues),
152 ),
153 ];
154 let mut entries = vec![
155 EntryKind::Command("About".to_string(), EntryId::About),
156 EntryKind::Command("System Info".to_string(), EntryId::SystemInfo),
157 EntryKind::Command(
158 "Settings".to_string(),
159 EntryId::Launch(AppCmd::SettingsEditor),
160 ),
161 EntryKind::Command("Check for Updates".to_string(), EntryId::CheckForUpdates),
162 EntryKind::Separator,
163 ];
164
165 if CURRENT_DEVICE.has_gyroscope() {
166 let rotation_lock = context.settings.rotation_lock;
167 let gyro = vec![
168 EntryKind::RadioButton(
169 "Auto".to_string(),
170 EntryId::SetRotationLock(None),
171 rotation_lock.is_none(),
172 ),
173 EntryKind::Separator,
174 EntryKind::RadioButton(
175 "Portrait".to_string(),
176 EntryId::SetRotationLock(Some(RotationLock::Portrait)),
177 rotation_lock == Some(RotationLock::Portrait),
178 ),
179 EntryKind::RadioButton(
180 "Landscape".to_string(),
181 EntryId::SetRotationLock(Some(RotationLock::Landscape)),
182 rotation_lock == Some(RotationLock::Landscape),
183 ),
184 EntryKind::RadioButton(
185 "Ignore".to_string(),
186 EntryId::SetRotationLock(Some(RotationLock::Current)),
187 rotation_lock == Some(RotationLock::Current),
188 ),
189 ];
190 entries.push(EntryKind::SubMenu("Gyroscope".to_string(), gyro));
191 }
192
193 if CURRENT_DEVICE.has_page_turn_buttons() {
194 let button_scheme = context.settings.button_scheme;
195 let button_schemes = vec![
196 EntryKind::RadioButton(
197 ButtonScheme::Natural.to_string(),
198 EntryId::SetButtonScheme(ButtonScheme::Natural),
199 button_scheme == ButtonScheme::Natural,
200 ),
201 EntryKind::RadioButton(
202 ButtonScheme::Inverted.to_string(),
203 EntryId::SetButtonScheme(ButtonScheme::Inverted),
204 button_scheme == ButtonScheme::Inverted,
205 ),
206 ];
207 entries.push(EntryKind::SubMenu(
208 "Button Scheme".to_string(),
209 button_schemes,
210 ));
211 }
212
213 entries.extend(vec![
214 EntryKind::CheckBox(
215 "Invert Colors".to_string(),
216 EntryId::ToggleInverted,
217 context.fb.inverted(),
218 ),
219 EntryKind::CheckBox(
220 "Enable WiFi".to_string(),
221 EntryId::ToggleWifi,
222 context.settings.wifi,
223 ),
224 EntryKind::Separator,
225 EntryKind::SubMenu("Rotate".to_string(), rotate),
226 EntryKind::Command("Take Screenshot".to_string(), EntryId::TakeScreenshot),
227 EntryKind::Separator,
228 EntryKind::SubMenu("Applications".to_string(), apps),
229 EntryKind::Separator,
230 EntryKind::Command("Restart".to_string(), EntryId::Restart),
231 EntryKind::Command("Reboot".to_string(), EntryId::Reboot),
232 EntryKind::Command("Quit".to_string(), EntryId::Quit),
233 ]);
234
235 let main_menu = Menu::new(rect, ViewId::MainMenu, MenuKind::DropDown, entries, context);
236 rq.add(RenderData::new(
237 main_menu.id(),
238 *main_menu.rect(),
239 UpdateMode::Gui,
240 ));
241 view.children_mut()
242 .push(Box::new(main_menu) as Box<dyn View>);
243 }
244}
245
246pub fn toggle_battery_menu(
247 view: &mut dyn View,
248 rect: Rectangle,
249 enable: Option<bool>,
250 rq: &mut RenderQueue,
251 context: &mut Context,
252) {
253 if let Some(index) = locate_by_id(view, ViewId::BatteryMenu) {
254 if let Some(true) = enable {
255 return;
256 }
257 rq.add(RenderData::expose(
258 *view.child(index).rect(),
259 UpdateMode::Gui,
260 ));
261 view.children_mut().remove(index);
262 } else {
263 if let Some(false) = enable {
264 return;
265 }
266
267 let mut entries = Vec::new();
268
269 match context
270 .battery
271 .status()
272 .ok()
273 .zip(context.battery.capacity().ok())
274 {
275 Some((status, capacity)) => {
276 for (i, (s, c)) in status.iter().zip(capacity.iter()).enumerate() {
277 entries.push(EntryKind::Message(
278 format!("{:?} {}%", s, c),
279 if i > 0 {
280 Some("cover".to_string())
281 } else {
282 None
283 },
284 ));
285 }
286 }
287 _ => {
288 entries.push(EntryKind::Message(
289 "Information Unavailable".to_string(),
290 None,
291 ));
292 }
293 }
294
295 let battery_menu = Menu::new(
296 rect,
297 ViewId::BatteryMenu,
298 MenuKind::DropDown,
299 entries,
300 context,
301 );
302 rq.add(RenderData::new(
303 battery_menu.id(),
304 *battery_menu.rect(),
305 UpdateMode::Gui,
306 ));
307 view.children_mut()
308 .push(Box::new(battery_menu) as Box<dyn View>);
309 }
310}
311
312pub fn toggle_clock_menu(
313 view: &mut dyn View,
314 rect: Rectangle,
315 enable: Option<bool>,
316 rq: &mut RenderQueue,
317 context: &mut Context,
318) {
319 if let Some(index) = locate_by_id(view, ViewId::ClockMenu) {
320 if let Some(true) = enable {
321 return;
322 }
323 rq.add(RenderData::expose(
324 *view.child(index).rect(),
325 UpdateMode::Gui,
326 ));
327 view.children_mut().remove(index);
328 } else {
329 if let Some(false) = enable {
330 return;
331 }
332 let text = Local::now()
333 .format(&context.settings.date_format)
334 .to_string();
335 let entries = vec![EntryKind::Message(text, None)];
336 let clock_menu = Menu::new(
337 rect,
338 ViewId::ClockMenu,
339 MenuKind::DropDown,
340 entries,
341 context,
342 );
343 rq.add(RenderData::new(
344 clock_menu.id(),
345 *clock_menu.rect(),
346 UpdateMode::Gui,
347 ));
348 view.children_mut()
349 .push(Box::new(clock_menu) as Box<dyn View>);
350 }
351}
352
353pub fn toggle_input_history_menu(
354 view: &mut dyn View,
355 id: ViewId,
356 rect: Rectangle,
357 enable: Option<bool>,
358 rq: &mut RenderQueue,
359 context: &mut Context,
360) {
361 if let Some(index) = locate_by_id(view, ViewId::InputHistoryMenu) {
362 if let Some(true) = enable {
363 return;
364 }
365 rq.add(RenderData::expose(
366 *view.child(index).rect(),
367 UpdateMode::Gui,
368 ));
369 view.children_mut().remove(index);
370 } else {
371 if let Some(false) = enable {
372 return;
373 }
374 let entries = context.input_history.get(&id).map(|h| {
375 h.iter()
376 .map(|s| {
377 EntryKind::Command(s.to_string(), EntryId::SetInputText(id, s.to_string()))
378 })
379 .collect::<Vec<EntryKind>>()
380 });
381 if let Some(entries) = entries {
382 let menu_kind = match id {
383 ViewId::HomeSearchInput
384 | ViewId::ReaderSearchInput
385 | ViewId::DictionarySearchInput
386 | ViewId::CalculatorInput => MenuKind::DropDown,
387 _ => MenuKind::Contextual,
388 };
389 let input_history_menu =
390 Menu::new(rect, ViewId::InputHistoryMenu, menu_kind, entries, context);
391 rq.add(RenderData::new(
392 input_history_menu.id(),
393 *input_history_menu.rect(),
394 UpdateMode::Gui,
395 ));
396 view.children_mut()
397 .push(Box::new(input_history_menu) as Box<dyn View>);
398 }
399 }
400}
401
402pub fn toggle_keyboard_layout_menu(
403 view: &mut dyn View,
404 rect: Rectangle,
405 enable: Option<bool>,
406 rq: &mut RenderQueue,
407 context: &mut Context,
408) {
409 if let Some(index) = locate_by_id(view, ViewId::KeyboardLayoutMenu) {
410 if let Some(true) = enable {
411 return;
412 }
413 rq.add(RenderData::expose(
414 *view.child(index).rect(),
415 UpdateMode::Gui,
416 ));
417 view.children_mut().remove(index);
418 } else {
419 if let Some(false) = enable {
420 return;
421 }
422 let entries = context
423 .keyboard_layouts
424 .keys()
425 .map(|s| EntryKind::Command(s.to_string(), EntryId::SetKeyboardLayout(s.to_string())))
426 .collect::<Vec<EntryKind>>();
427 let keyboard_layout_menu = Menu::new(
428 rect,
429 ViewId::KeyboardLayoutMenu,
430 MenuKind::Contextual,
431 entries,
432 context,
433 );
434 rq.add(RenderData::new(
435 keyboard_layout_menu.id(),
436 *keyboard_layout_menu.rect(),
437 UpdateMode::Gui,
438 ));
439 view.children_mut()
440 .push(Box::new(keyboard_layout_menu) as Box<dyn View>);
441 }
442}