1mod bottom_bar;
2
3use self::bottom_bar::BottomBar;
4use crate::color::BLACK;
5use crate::context::Context;
6use crate::device::CURRENT_DEVICE;
7use crate::document::html::HtmlDocument;
8use crate::document::{Document, Location};
9use crate::font::Fonts;
10use crate::framebuffer::{Framebuffer, Pixmap, UpdateMode};
11use crate::geom::{halves, CycleDir, Dir, Point, Rectangle};
12use crate::gesture::GestureEvent;
13use crate::input::{ButtonCode, ButtonStatus, DeviceEvent};
14use crate::unit::scale_by_dpi;
15use crate::view::common::{locate, locate_by_id};
16use crate::view::common::{toggle_battery_menu, toggle_clock_menu, toggle_main_menu};
17use crate::view::filler::Filler;
18use crate::view::image::Image;
19use crate::view::keyboard::Keyboard;
20use crate::view::menu::{Menu, MenuKind};
21use crate::view::named_input::NamedInput;
22use crate::view::search_bar::SearchBar;
23use crate::view::top_bar::{TopBar, TopBarVariant};
24use crate::view::{Bus, Event, Hub, RenderData, RenderQueue, View};
25use crate::view::{EntryId, EntryKind, Id, ViewId, ID_FEEDER};
26use crate::view::{BIG_BAR_HEIGHT, SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
27use regex::Regex;
28use tracing::error;
29
30const VIEWER_STYLESHEET: &str = "css/dictionary.css";
31const USER_STYLESHEET: &str = "css/dictionary-user.css";
32
33pub struct Dictionary {
34 id: Id,
35 rect: Rectangle,
36 children: Vec<Box<dyn View>>,
37 doc: HtmlDocument,
38 location: usize,
39 fuzzy: bool,
40 query: String,
41 language: String,
42 target: Option<String>,
43 focus: Option<ViewId>,
44}
45
46fn query_to_content(
47 query: &str,
48 language: &String,
49 fuzzy: bool,
50 target: Option<&String>,
51 context: &mut Context,
52) -> String {
53 let mut content = String::new();
54
55 for (name, dict) in context.dictionaries.iter_mut() {
56 if target.is_some() && target != Some(name) {
57 continue;
58 }
59
60 if target.is_none()
61 && !language.is_empty()
62 && context.settings.dictionary.languages.contains_key(name)
63 && !context.settings.dictionary.languages[name].contains(language)
64 {
65 continue;
66 }
67
68 if let Some(results) = dict
69 .lookup(query, fuzzy)
70 .map_err(|e| error!("Can't search dictionary: {:#}.", e))
71 .ok()
72 .filter(|r| !r.is_empty())
73 {
74 if target.is_none() {
75 content.push_str(&format!(
76 "<h1 class=\"dictname\">{}</h1>\n",
77 name.replace('<', "<").replace('>', ">")
78 ));
79 }
80 for [head, body] in results {
81 if !body.trim_start().starts_with("<h2") {
82 content.push_str(&format!(
83 "<h2 class=\"headword\">{}</h2>\n",
84 head.replace('<', "<").replace('>', ">")
85 ));
86 }
87 if body.trim_start().starts_with('<') {
88 content.push_str(&body);
89 } else {
90 content.push_str(&format!(
91 "<pre>{}</pre>",
92 body.replace('<', "<").replace('>', ">")
93 ));
94 }
95 }
96 }
97 }
98
99 if content.is_empty() {
100 if context.dictionaries.is_empty() {
101 content.push_str("<p class=\"info\">No dictionaries present.</p>");
102 } else {
103 content.push_str("<p class=\"info\">No definitions found.</p>");
104 }
105 }
106
107 content
108}
109
110impl Dictionary {
111 pub fn new(
112 rect: Rectangle,
113 query: &str,
114 language: &str,
115 hub: &Hub,
116 rq: &mut RenderQueue,
117 context: &mut Context,
118 ) -> Dictionary {
119 let id = ID_FEEDER.next();
120 let mut children = Vec::new();
121 let dpi = CURRENT_DEVICE.dpi;
122 let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
123 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
124 let (small_thickness, big_thickness) = halves(thickness);
125
126 let top_bar = TopBar::new(
127 rect![
128 rect.min.x,
129 rect.min.y,
130 rect.max.x,
131 rect.min.y + small_height - small_thickness
132 ],
133 TopBarVariant::Back,
134 "Dictionary".to_string(),
135 context,
136 );
137 children.push(Box::new(top_bar) as Box<dyn View>);
138
139 let separator = Filler::new(
140 rect![
141 rect.min.x,
142 rect.min.y + small_height - small_thickness,
143 rect.max.x,
144 rect.min.y + small_height + big_thickness
145 ],
146 BLACK,
147 );
148 children.push(Box::new(separator) as Box<dyn View>);
149
150 let search_bar = SearchBar::new(
151 rect![
152 rect.min.x,
153 rect.min.y + small_height + big_thickness,
154 rect.max.x,
155 rect.min.y + 2 * small_height - small_thickness
156 ],
157 ViewId::DictionarySearchInput,
158 "",
159 query,
160 context,
161 );
162 children.push(Box::new(search_bar) as Box<dyn View>);
163
164 let separator = Filler::new(
165 rect![
166 rect.min.x,
167 rect.min.y + 2 * small_height - small_thickness,
168 rect.max.x,
169 rect.min.y + 2 * small_height + big_thickness
170 ],
171 BLACK,
172 );
173 children.push(Box::new(separator) as Box<dyn View>);
174
175 let langs = &context.settings.dictionary.languages;
176 let matches = context
177 .dictionaries
178 .keys()
179 .filter(|&k| langs.contains_key(k) && langs[k].contains(&language.to_string()))
180 .collect::<Vec<&String>>();
181 let target = if matches.len() == 1 {
182 Some(matches[0].clone())
183 } else {
184 if context.dictionaries.len() == 1 {
185 Some(context.dictionaries.keys().next().cloned().unwrap())
186 } else {
187 None
188 }
189 };
190
191 let image_rect = rect![
192 rect.min.x,
193 rect.min.y + 2 * small_height + big_thickness,
194 rect.max.x,
195 rect.max.y - small_height - small_thickness
196 ];
197
198 let image = Image::new(image_rect, Pixmap::new(1, 1, 1));
199 children.push(Box::new(image) as Box<dyn View>);
200
201 let mut doc = HtmlDocument::new_from_memory("");
202 doc.layout(
203 image_rect.width(),
204 image_rect.height(),
205 context.settings.dictionary.font_size,
206 dpi,
207 );
208 doc.set_margin_width(context.settings.dictionary.margin_width);
209 doc.set_viewer_stylesheet(VIEWER_STYLESHEET);
210 doc.set_user_stylesheet(USER_STYLESHEET);
211
212 let separator = Filler::new(
213 rect![
214 rect.min.x,
215 rect.max.y - small_height - small_thickness,
216 rect.max.x,
217 rect.max.y - small_height + big_thickness
218 ],
219 BLACK,
220 );
221 children.push(Box::new(separator) as Box<dyn View>);
222
223 let bottom_bar = BottomBar::new(
224 rect![
225 rect.min.x,
226 rect.max.y - small_height + big_thickness,
227 rect.max.x,
228 rect.max.y
229 ],
230 target.as_deref().unwrap_or("All"),
231 false,
232 false,
233 );
234 children.push(Box::new(bottom_bar) as Box<dyn View>);
235
236 rq.add(RenderData::new(id, rect, UpdateMode::Gui));
237
238 if query.is_empty() {
239 hub.send(Event::Focus(Some(ViewId::DictionarySearchInput)))
240 .ok();
241 } else {
242 hub.send(Event::Define(query.to_string())).ok();
243 }
244
245 Dictionary {
246 id,
247 rect,
248 children,
249 doc,
250 location: 0,
251 fuzzy: false,
252 query: query.to_string(),
253 language: language.to_string(),
254 target,
255 focus: None,
256 }
257 }
258
259 pub fn toggle_title_menu(
260 &mut self,
261 rect: Rectangle,
262 enable: Option<bool>,
263 rq: &mut RenderQueue,
264 context: &mut Context,
265 ) {
266 if let Some(index) = locate_by_id(self, ViewId::TitleMenu) {
267 if let Some(true) = enable {
268 return;
269 }
270
271 rq.add(RenderData::expose(
272 *self.child(index).rect(),
273 UpdateMode::Gui,
274 ));
275 self.children.remove(index);
276 } else {
277 if let Some(false) = enable {
278 return;
279 }
280 let entries = vec![EntryKind::Command(
281 "Reload Dictionaries".to_string(),
282 EntryId::ReloadDictionaries,
283 )];
284 let title_menu = Menu::new(
285 rect,
286 ViewId::TitleMenu,
287 MenuKind::DropDown,
288 entries,
289 context,
290 );
291 rq.add(RenderData::new(
292 title_menu.id(),
293 *title_menu.rect(),
294 UpdateMode::Gui,
295 ));
296 self.children.push(Box::new(title_menu) as Box<dyn View>);
297 }
298 }
299
300 fn toggle_search_menu(
301 &mut self,
302 rect: Rectangle,
303 enable: Option<bool>,
304 rq: &mut RenderQueue,
305 context: &mut Context,
306 ) {
307 if let Some(index) = locate_by_id(self, ViewId::SearchMenu) {
308 if let Some(true) = enable {
309 return;
310 }
311
312 rq.add(RenderData::expose(
313 *self.child(index).rect(),
314 UpdateMode::Gui,
315 ));
316 self.children.remove(index);
317 } else {
318 if let Some(false) = enable {
319 return;
320 }
321 let entries = vec![EntryKind::CheckBox(
322 "Fuzzy".to_string(),
323 EntryId::ToggleFuzzy,
324 self.fuzzy,
325 )];
326 let search_menu = Menu::new(
327 rect,
328 ViewId::SearchMenu,
329 MenuKind::Contextual,
330 entries,
331 context,
332 );
333 rq.add(RenderData::new(
334 search_menu.id(),
335 *search_menu.rect(),
336 UpdateMode::Gui,
337 ));
338 self.children.push(Box::new(search_menu) as Box<dyn View>);
339 }
340 }
341
342 fn toggle_search_target_menu(
343 &mut self,
344 rect: Rectangle,
345 enable: Option<bool>,
346 rq: &mut RenderQueue,
347 context: &mut Context,
348 ) {
349 if let Some(index) = locate_by_id(self, ViewId::SearchTargetMenu) {
350 if let Some(true) = enable {
351 return;
352 }
353
354 rq.add(RenderData::expose(
355 *self.child(index).rect(),
356 UpdateMode::Gui,
357 ));
358 self.children.remove(index);
359 } else {
360 if let Some(false) = enable {
361 return;
362 }
363 let mut entries = context
364 .dictionaries
365 .keys()
366 .map(|k| {
367 EntryKind::RadioButton(
368 k.to_string(),
369 EntryId::SetSearchTarget(Some(k.to_string())),
370 self.target == Some(k.to_string()),
371 )
372 })
373 .collect::<Vec<EntryKind>>();
374 if !entries.is_empty() {
375 entries.push(EntryKind::Separator);
376 }
377 entries.push(EntryKind::RadioButton(
378 "All".to_string(),
379 EntryId::SetSearchTarget(None),
380 self.target.is_none(),
381 ));
382 let search_target_menu = Menu::new(
383 rect,
384 ViewId::SearchTargetMenu,
385 MenuKind::DropDown,
386 entries,
387 context,
388 );
389 rq.add(RenderData::new(
390 search_target_menu.id(),
391 *search_target_menu.rect(),
392 UpdateMode::Gui,
393 ));
394 self.children
395 .push(Box::new(search_target_menu) as Box<dyn View>);
396 }
397 }
398
399 fn toggle_keyboard(
400 &mut self,
401 enable: bool,
402 id: Option<ViewId>,
403 hub: &Hub,
404 rq: &mut RenderQueue,
405 context: &mut Context,
406 ) {
407 if let Some(index) = locate::<Keyboard>(self) {
408 if enable {
409 return;
410 }
411
412 let mut rect = *self.child(index).rect();
413 rect.absorb(self.child(index - 1).rect());
414 self.children.drain(index - 1..=index);
415
416 context.kb_rect = Rectangle::default();
417 rq.add(RenderData::expose(rect, UpdateMode::Gui));
418 hub.send(Event::Focus(None)).ok();
419 } else {
420 if !enable {
421 return;
422 }
423
424 let dpi = CURRENT_DEVICE.dpi;
425 let (small_height, big_height) = (
426 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
427 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
428 );
429 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
430 let (small_thickness, big_thickness) = halves(thickness);
431
432 let mut kb_rect = rect![
433 self.rect.min.x,
434 self.rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
435 self.rect.max.x,
436 self.rect.max.y - small_height - small_thickness
437 ];
438
439 let number = id == Some(ViewId::GoToPageInput);
440 let index = locate::<BottomBar>(self).unwrap() + 1;
441
442 let keyboard = Keyboard::new(&mut kb_rect, number, context);
443 self.children
444 .insert(index, Box::new(keyboard) as Box<dyn View>);
445
446 let separator = Filler::new(
447 rect![
448 self.rect.min.x,
449 kb_rect.min.y - thickness,
450 self.rect.max.x,
451 kb_rect.min.y
452 ],
453 BLACK,
454 );
455 self.children
456 .insert(index, Box::new(separator) as Box<dyn View>);
457
458 for i in index..=index + 1 {
459 rq.add(RenderData::new(
460 self.child(i).id(),
461 *self.child(i).rect(),
462 UpdateMode::Gui,
463 ));
464 }
465 }
466 }
467
468 fn toggle_edit_languages(
469 &mut self,
470 enable: Option<bool>,
471 hub: &Hub,
472 rq: &mut RenderQueue,
473 context: &mut Context,
474 ) {
475 if let Some(index) = locate_by_id(self, ViewId::EditLanguages) {
476 if let Some(true) = enable {
477 return;
478 }
479
480 rq.add(RenderData::expose(
481 *self.child(index).rect(),
482 UpdateMode::Gui,
483 ));
484 self.children.remove(index);
485
486 if self
487 .focus
488 .map(|focus_id| focus_id == ViewId::EditLanguagesInput)
489 .unwrap_or(false)
490 {
491 self.toggle_keyboard(false, None, hub, rq, context);
492 }
493 } else {
494 if let Some(false) = enable {
495 return;
496 }
497
498 let mut edit_languages = NamedInput::new(
499 "Languages".to_string(),
500 ViewId::EditLanguages,
501 ViewId::EditLanguagesInput,
502 16,
503 context,
504 );
505 if let Some(langs) = self
506 .target
507 .as_ref()
508 .and_then(|name| context.settings.dictionary.languages.get(name))
509 .filter(|langs| !langs.is_empty())
510 {
511 edit_languages.set_text(&langs.join(", "), &mut RenderQueue::new(), context);
512 }
513
514 rq.add(RenderData::new(
515 edit_languages.id(),
516 *edit_languages.rect(),
517 UpdateMode::Gui,
518 ));
519 hub.send(Event::Focus(Some(ViewId::EditLanguagesInput)))
520 .ok();
521
522 self.children
523 .push(Box::new(edit_languages) as Box<dyn View>);
524 }
525 }
526
527 fn reseed(&mut self, rq: &mut RenderQueue, context: &mut Context) {
528 if let Some(top_bar) = self.child_mut(0).downcast_mut::<TopBar>() {
529 top_bar.reseed(rq, context);
530 }
531
532 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
533 }
534
535 fn go_to_neighbor(&mut self, dir: CycleDir, rq: &mut RenderQueue) {
536 let location = match dir {
537 CycleDir::Previous => Location::Previous(self.location),
538 CycleDir::Next => Location::Next(self.location),
539 };
540 if let Some(image) = self.children[4].downcast_mut::<Image>() {
541 if let Some((pixmap, loc)) =
542 self.doc
543 .pixmap(location, 1.0, CURRENT_DEVICE.color_samples())
544 {
545 image.update(pixmap, rq);
546 self.location = loc;
547 }
548 }
549 if let Some(bottom_bar) = self.children[6].downcast_mut::<BottomBar>() {
550 bottom_bar.update_icons(
551 self.doc
552 .resolve_location(Location::Previous(self.location))
553 .is_some(),
554 self.doc
555 .resolve_location(Location::Next(self.location))
556 .is_some(),
557 rq,
558 );
559 }
560 }
561
562 fn define(&mut self, text: Option<&str>, rq: &mut RenderQueue, context: &mut Context) {
563 if let Some(query) = text {
564 self.query = query.to_string();
565 if let Some(search_bar) = self.children[2].downcast_mut::<SearchBar>() {
566 search_bar.set_text(query, rq, context);
567 }
568 }
569 let content = query_to_content(
570 &self.query,
571 &self.language,
572 self.fuzzy,
573 self.target.as_ref(),
574 context,
575 );
576 self.doc.update(&content);
577 if let Some(image) = self.children[4].downcast_mut::<Image>() {
578 if let Some((pixmap, loc)) =
579 self.doc
580 .pixmap(Location::Exact(0), 1.0, CURRENT_DEVICE.color_samples())
581 {
582 image.update(pixmap, rq);
583 self.location = loc;
584 }
585 }
586 if let Some(bottom_bar) = self.children[6].downcast_mut::<BottomBar>() {
587 bottom_bar.update_icons(
588 false,
589 self.doc
590 .resolve_location(Location::Next(self.location))
591 .is_some(),
592 rq,
593 );
594 }
595 }
596
597 fn underlying_word(&mut self, pt: Point) -> Option<String> {
598 let dpi = CURRENT_DEVICE.dpi;
599 let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
600 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
601 let (_, big_thickness) = halves(thickness);
602 let offset = pt!(
603 self.rect.min.x,
604 self.rect.min.y + 2 * small_height + big_thickness
605 );
606
607 if let Some((words, _)) = self.doc.words(Location::Exact(self.location)) {
608 for word in words {
609 let rect = word.rect.to_rect() + offset;
610 if rect.includes(pt) {
611 return Some(word.text);
612 }
613 }
614 }
615
616 None
617 }
618
619 fn follow_link(&mut self, pt: Point, rq: &mut RenderQueue, context: &mut Context) {
620 let dpi = CURRENT_DEVICE.dpi;
621 let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
622 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
623 let (_, big_thickness) = halves(thickness);
624 let offset = pt!(
625 self.rect.min.x,
626 self.rect.min.y + 2 * small_height + big_thickness
627 );
628
629 if let Some((links, _)) = self.doc.links(Location::Exact(self.location)) {
630 for link in links {
631 let rect = link.rect.to_rect() + offset;
632 if rect.includes(pt) && link.text.starts_with('?') {
633 self.define(Some(&link.text[1..]), rq, context);
634 return;
635 }
636 }
637 }
638
639 let half_width = self.rect.width() as i32 / 2;
640 if pt.x - offset.x < half_width {
641 self.go_to_neighbor(CycleDir::Previous, rq);
642 } else {
643 self.go_to_neighbor(CycleDir::Next, rq);
644 }
645 }
646}
647
648impl View for Dictionary {
649 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
650 fn handle_event(
651 &mut self,
652 evt: &Event,
653 hub: &Hub,
654 _bus: &mut Bus,
655 rq: &mut RenderQueue,
656 context: &mut Context,
657 ) -> bool {
658 match *evt {
659 Event::Define(ref query) => {
660 self.define(Some(query), rq, context);
661 true
662 }
663 Event::Submit(ViewId::DictionarySearchInput, ref text) => {
664 if !text.is_empty() {
665 self.toggle_keyboard(false, None, hub, rq, context);
666 self.define(Some(text), rq, context);
667 }
668 true
669 }
670 Event::Page(dir) => {
671 self.go_to_neighbor(dir, rq);
672 true
673 }
674 Event::Gesture(GestureEvent::Swipe { dir, start, .. }) if self.rect.includes(start) => {
675 match dir {
676 Dir::West => self.go_to_neighbor(CycleDir::Next, rq),
677 Dir::East => self.go_to_neighbor(CycleDir::Previous, rq),
678 _ => (),
679 }
680 true
681 }
682 Event::Device(DeviceEvent::Button {
683 code,
684 status: ButtonStatus::Released,
685 ..
686 }) => {
687 let cd = match code {
688 ButtonCode::Backward => Some(CycleDir::Previous),
689 ButtonCode::Forward => Some(CycleDir::Next),
690 _ => None,
691 };
692 if let Some(cd) = cd {
693 let loc = self.location;
694 self.go_to_neighbor(cd, rq);
695 if self.location == loc {
696 hub.send(Event::Back).ok();
697 }
698 }
699 true
700 }
701 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
702 self.follow_link(center, rq, context);
703 true
704 }
705 Event::Gesture(GestureEvent::HoldFingerLong(pt, _)) => {
706 if let Some(text) = self.underlying_word(pt) {
707 let query = text
708 .trim_matches(|c: char| !c.is_alphanumeric())
709 .to_string();
710 self.define(Some(&query), rq, context);
711 }
712 true
713 }
714 Event::Select(EntryId::SetSearchTarget(ref target)) => {
715 if *target != self.target {
716 self.target = target.clone();
717 let name = self.target.as_deref().unwrap_or("All");
718 if let Some(bottom_bar) = self.children[6].downcast_mut::<BottomBar>() {
719 bottom_bar.update_name(name, rq);
720 }
721 if !self.query.is_empty() {
722 self.define(None, rq, context);
723 }
724 }
725 true
726 }
727 Event::Select(EntryId::ToggleFuzzy) => {
728 self.fuzzy = !self.fuzzy;
729 if !self.query.is_empty() {
730 self.define(None, rq, context);
731 }
732 true
733 }
734 Event::Select(EntryId::ReloadDictionaries) => {
735 context.dictionaries.clear();
736 context.load_dictionaries();
737 if let Some(name) = self.target.as_ref() {
738 if !context.dictionaries.contains_key(name) {
739 self.target = None;
740 if let Some(bottom_bar) = self.child_mut(6).downcast_mut::<BottomBar>() {
741 bottom_bar.update_name("All", rq);
742 }
743 }
744 }
745 true
746 }
747 Event::EditLanguages => {
748 if self.target.is_some() {
749 self.toggle_edit_languages(None, hub, rq, context);
750 }
751 true
752 }
753 Event::Submit(ViewId::EditLanguagesInput, ref text) => {
754 if let Some(name) = self.target.as_ref() {
755 let re = Regex::new(r"\s*,\s*").unwrap();
756 context
757 .settings
758 .dictionary
759 .languages
760 .insert(name.clone(), re.split(text).map(String::from).collect());
761 if self.target.is_none() && !self.query.is_empty() {
762 self.define(None, rq, context);
763 }
764 }
765 true
766 }
767 Event::Close(ViewId::EditLanguages) => {
768 self.toggle_keyboard(false, None, hub, rq, context);
769 false
770 }
771 Event::Close(ViewId::SearchBar) => {
772 hub.send(Event::Back).ok();
773 true
774 }
775 Event::Focus(v) => {
776 self.focus = v;
777 if v.is_some() {
778 self.toggle_keyboard(true, v, hub, rq, context);
779 }
780 true
781 }
782 Event::ToggleNear(ViewId::TitleMenu, rect) => {
783 self.toggle_title_menu(rect, None, rq, context);
784 true
785 }
786 Event::ToggleNear(ViewId::SearchMenu, rect) => {
787 self.toggle_search_menu(rect, None, rq, context);
788 true
789 }
790 Event::ToggleNear(ViewId::SearchTargetMenu, rect) => {
791 self.toggle_search_target_menu(rect, None, rq, context);
792 true
793 }
794 Event::ToggleNear(ViewId::MainMenu, rect) => {
795 toggle_main_menu(self, rect, None, rq, context);
796 true
797 }
798 Event::ToggleNear(ViewId::BatteryMenu, rect) => {
799 toggle_battery_menu(self, rect, None, rq, context);
800 true
801 }
802 Event::ToggleNear(ViewId::ClockMenu, rect) => {
803 toggle_clock_menu(self, rect, None, rq, context);
804 true
805 }
806 Event::Reseed => {
807 self.reseed(rq, context);
808 true
809 }
810 Event::Gesture(GestureEvent::Cross(_)) => {
811 hub.send(Event::Back).ok();
812 true
813 }
814 _ => false,
815 }
816 }
817
818 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
819 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
820
821 fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
822 let dpi = CURRENT_DEVICE.dpi;
823 let (small_height, big_height) = (
824 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
825 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
826 );
827 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
828 let (small_thickness, big_thickness) = halves(thickness);
829
830 self.children[0].resize(
831 rect![
832 rect.min.x,
833 rect.min.y,
834 rect.max.x,
835 rect.min.y + small_height - small_thickness
836 ],
837 hub,
838 rq,
839 context,
840 );
841
842 self.children[1].resize(
843 rect![
844 rect.min.x,
845 rect.min.y + small_height - small_thickness,
846 rect.max.x,
847 rect.min.y + small_height + big_thickness
848 ],
849 hub,
850 rq,
851 context,
852 );
853
854 self.children[2].resize(
855 rect![
856 rect.min.x,
857 rect.min.y + small_height + big_thickness,
858 rect.max.x,
859 rect.min.y + 2 * small_height - small_thickness
860 ],
861 hub,
862 rq,
863 context,
864 );
865
866 self.children[3].resize(
867 rect![
868 rect.min.x,
869 rect.min.y + 2 * small_height - small_thickness,
870 rect.max.x,
871 rect.min.y + 2 * small_height + big_thickness
872 ],
873 hub,
874 rq,
875 context,
876 );
877
878 let image_rect = rect![
879 rect.min.x,
880 rect.min.y + 2 * small_height + big_thickness,
881 rect.max.x,
882 rect.max.y - small_height - small_thickness
883 ];
884 self.doc.layout(
885 image_rect.width(),
886 image_rect.height(),
887 context.settings.dictionary.font_size,
888 dpi,
889 );
890 if let Some(image) = self.children[4].downcast_mut::<Image>() {
891 if let Some((pixmap, loc)) = self.doc.pixmap(
892 Location::Exact(self.location),
893 1.0,
894 CURRENT_DEVICE.color_samples(),
895 ) {
896 image.update(pixmap, &mut RenderQueue::new());
897 self.location = loc;
898 }
899 }
900 self.children[4].resize(image_rect, hub, rq, context);
901
902 self.children[5].resize(
903 rect![
904 rect.min.x,
905 rect.max.y - small_height - small_thickness,
906 rect.max.x,
907 rect.max.y - small_height + big_thickness
908 ],
909 hub,
910 rq,
911 context,
912 );
913
914 self.children[6].resize(
915 rect![
916 rect.min.x,
917 rect.max.y - small_height + big_thickness,
918 rect.max.x,
919 rect.max.y
920 ],
921 hub,
922 rq,
923 context,
924 );
925 if let Some(bottom_bar) = self.children[6].downcast_mut::<BottomBar>() {
926 bottom_bar.update_icons(
927 self.doc
928 .resolve_location(Location::Previous(self.location))
929 .is_some(),
930 self.doc
931 .resolve_location(Location::Next(self.location))
932 .is_some(),
933 &mut RenderQueue::new(),
934 );
935 }
936 let mut index = 7;
937 if self.len() >= 9 {
938 if self.children[8].is::<Keyboard>() {
939 let kb_rect = rect![
940 rect.min.x,
941 rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
942 rect.max.x,
943 rect.max.y - small_height - small_thickness
944 ];
945 self.children[8].resize(kb_rect, hub, rq, context);
946 let kb_rect = *self.children[8].rect();
947 self.children[7].resize(
948 rect![
949 rect.min.x,
950 kb_rect.min.y - thickness,
951 rect.max.x,
952 kb_rect.min.y
953 ],
954 hub,
955 rq,
956 context,
957 );
958 index = 9;
959 }
960 }
961
962 for i in index..self.children.len() {
963 self.children[i].resize(rect, hub, rq, context);
964 }
965
966 self.rect = rect;
967 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Full));
968 }
969
970 fn rect(&self) -> &Rectangle {
971 &self.rect
972 }
973
974 fn rect_mut(&mut self) -> &mut Rectangle {
975 &mut self.rect
976 }
977
978 fn children(&self) -> &Vec<Box<dyn View>> {
979 &self.children
980 }
981
982 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
983 &mut self.children
984 }
985
986 fn id(&self) -> Id {
987 self.id
988 }
989}