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