cadmus_core/view/dictionary/
mod.rs

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('<', "&lt;").replace('>', "&gt;")
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('<', "&lt;").replace('>', "&gt;")
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('<', "&lt;").replace('>', "&gt;")
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}