cadmus_core/view/reader/
tool_bar.rs

1use crate::color::{SEPARATOR_NORMAL, WHITE};
2use crate::context::Context;
3use crate::device::CURRENT_DEVICE;
4use crate::font::Fonts;
5use crate::framebuffer::{Framebuffer, UpdateMode};
6use crate::geom::Rectangle;
7use crate::gesture::GestureEvent;
8use crate::input::DeviceEvent;
9use crate::metadata::{ReaderInfo, TextAlign};
10use crate::metadata::{DEFAULT_CONTRAST_EXPONENT, DEFAULT_CONTRAST_GRAY};
11use crate::settings::ReaderSettings;
12use crate::unit::scale_by_dpi;
13use crate::view::filler::Filler;
14use crate::view::icon::Icon;
15use crate::view::labeled_icon::LabeledIcon;
16use crate::view::slider::Slider;
17use crate::view::{
18    Bus, Event, Hub, Id, RenderData, RenderQueue, SliderId, View, ViewId, ID_FEEDER,
19    THICKNESS_MEDIUM,
20};
21
22pub struct ToolBar {
23    id: Id,
24    rect: Rectangle,
25    children: Vec<Box<dyn View>>,
26    reflowable: bool,
27}
28
29impl ToolBar {
30    pub fn new(
31        rect: Rectangle,
32        reflowable: bool,
33        reader_info: Option<&ReaderInfo>,
34        reader_settings: &ReaderSettings,
35    ) -> ToolBar {
36        let id = ID_FEEDER.next();
37        let mut children = Vec::new();
38        let dpi = CURRENT_DEVICE.dpi;
39        let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
40        let side = (rect.height() as i32 + thickness) / 2 - thickness;
41
42        if reflowable {
43            let mut remaining_width = rect.width() as i32 - 3 * side;
44            let font_family_label_width = remaining_width / 2;
45            remaining_width -= font_family_label_width;
46            let margin_label_width = remaining_width / 2;
47            let line_height_label_width = remaining_width - margin_label_width;
48
49            // First row.
50
51            let mut x_offset = rect.min.x;
52            let margin_width = reader_info
53                .and_then(|r| r.margin_width)
54                .unwrap_or(reader_settings.margin_width);
55            let margin_icon = LabeledIcon::new(
56                "margin",
57                rect![
58                    x_offset,
59                    rect.min.y,
60                    x_offset + side + margin_label_width,
61                    rect.min.y + side
62                ],
63                Event::Show(ViewId::MarginWidthMenu),
64                format!("{} mm", margin_width),
65            );
66            children.push(Box::new(margin_icon) as Box<dyn View>);
67            x_offset += side + margin_label_width;
68
69            let font_family = reader_info
70                .and_then(|r| r.font_family.clone())
71                .unwrap_or_else(|| reader_settings.font_family.clone());
72            let font_family_icon = LabeledIcon::new(
73                "font_family",
74                rect![
75                    x_offset,
76                    rect.min.y,
77                    x_offset + side + font_family_label_width,
78                    rect.min.y + side
79                ],
80                Event::Show(ViewId::FontFamilyMenu),
81                font_family,
82            );
83            children.push(Box::new(font_family_icon) as Box<dyn View>);
84            x_offset += side + font_family_label_width;
85
86            let line_height = reader_info
87                .and_then(|r| r.line_height)
88                .unwrap_or(reader_settings.line_height);
89            let line_height_icon = LabeledIcon::new(
90                "line_height",
91                rect![
92                    x_offset,
93                    rect.min.y,
94                    x_offset + side + line_height_label_width,
95                    rect.min.y + side
96                ],
97                Event::Show(ViewId::LineHeightMenu),
98                format!("{:.1} em", line_height),
99            );
100            children.push(Box::new(line_height_icon) as Box<dyn View>);
101
102            // Separator.
103            let separator = Filler::new(
104                rect![rect.min.x, rect.min.y + side, rect.max.x, rect.max.y - side],
105                SEPARATOR_NORMAL,
106            );
107            children.push(Box::new(separator) as Box<dyn View>);
108
109            // Start of second row.
110            let text_align = reader_info
111                .and_then(|r| r.text_align)
112                .unwrap_or(reader_settings.text_align);
113            let text_align_rect =
114                rect![rect.min.x, rect.max.y - side, rect.min.x + side, rect.max.y];
115            let text_align_icon = Icon::new(
116                text_align.icon_name(),
117                text_align_rect,
118                Event::ToggleNear(ViewId::TextAlignMenu, text_align_rect),
119            );
120            children.push(Box::new(text_align_icon) as Box<dyn View>);
121
122            let font_size = reader_info
123                .and_then(|r| r.font_size)
124                .unwrap_or(reader_settings.font_size);
125            let font_size_rect = rect![
126                rect.min.x + side,
127                rect.max.y - side,
128                rect.min.x + 2 * side,
129                rect.max.y
130            ];
131            let font_size_icon = Icon::new(
132                "font_size",
133                font_size_rect,
134                Event::ToggleNear(ViewId::FontSizeMenu, font_size_rect),
135            );
136            children.push(Box::new(font_size_icon) as Box<dyn View>);
137
138            let slider = Slider::new(
139                rect![
140                    rect.min.x + 2 * side,
141                    rect.max.y - side,
142                    rect.max.x - 2 * side,
143                    rect.max.y
144                ],
145                SliderId::FontSize,
146                font_size,
147                reader_settings.min_font_size,
148                reader_settings.max_font_size,
149            );
150            children.push(Box::new(slider) as Box<dyn View>);
151        } else {
152            let remaining_width = rect.width() as i32 - 2 * side;
153            let slider_width = remaining_width / 2;
154            // First row.
155            let contrast_icon_rect =
156                rect![rect.min.x, rect.min.y, rect.min.x + side, rect.min.y + side];
157            let contrast_icon = Icon::new(
158                "contrast",
159                contrast_icon_rect,
160                Event::ToggleNear(ViewId::ContrastExponentMenu, contrast_icon_rect),
161            );
162            children.push(Box::new(contrast_icon) as Box<dyn View>);
163
164            let contrast_exponent = reader_info
165                .and_then(|r| r.contrast_exponent)
166                .unwrap_or(DEFAULT_CONTRAST_EXPONENT);
167            let slider = Slider::new(
168                rect![
169                    rect.min.x + side,
170                    rect.min.y,
171                    rect.min.x + side + slider_width,
172                    rect.min.y + side
173                ],
174                SliderId::ContrastExponent,
175                contrast_exponent,
176                1.0,
177                5.0,
178            );
179            children.push(Box::new(slider) as Box<dyn View>);
180
181            let gray_icon_rect = rect![
182                rect.min.x + side + slider_width,
183                rect.min.y,
184                rect.min.x + 2 * side + slider_width,
185                rect.min.y + side
186            ];
187            let gray_icon = Icon::new(
188                "gray",
189                gray_icon_rect,
190                Event::ToggleNear(ViewId::ContrastGrayMenu, gray_icon_rect),
191            );
192            children.push(Box::new(gray_icon) as Box<dyn View>);
193
194            let contrast_gray = reader_info
195                .and_then(|r| r.contrast_gray)
196                .unwrap_or(DEFAULT_CONTRAST_GRAY);
197            let slider = Slider::new(
198                rect![
199                    rect.min.x + 2 * side + slider_width,
200                    rect.min.y,
201                    rect.max.x - side / 3,
202                    rect.min.y + side
203                ],
204                SliderId::ContrastGray,
205                contrast_gray,
206                0.0,
207                255.0,
208            );
209            children.push(Box::new(slider) as Box<dyn View>);
210
211            let filler = Filler::new(
212                rect![
213                    rect.max.x - side / 3,
214                    rect.min.y,
215                    rect.max.x,
216                    rect.min.y + side
217                ],
218                WHITE,
219            );
220            children.push(Box::new(filler) as Box<dyn View>);
221
222            // Separator.
223            let separator = Filler::new(
224                rect![rect.min.x, rect.min.y + side, rect.max.x, rect.max.y - side],
225                SEPARATOR_NORMAL,
226            );
227            children.push(Box::new(separator) as Box<dyn View>);
228
229            // Start of second row.
230            let crop_icon = Icon::new(
231                "crop",
232                rect![rect.min.x, rect.max.y - side, rect.min.x + side, rect.max.y],
233                Event::Show(ViewId::MarginCropper),
234            );
235            children.push(Box::new(crop_icon) as Box<dyn View>);
236
237            let remaining_width = rect.width() as i32 - 3 * side;
238            let margin_label_width = (2 * side).min(remaining_width);
239            let big_padding = (remaining_width - margin_label_width) / 2;
240            let small_padding = remaining_width - margin_label_width - big_padding;
241
242            let filler = Filler::new(
243                rect![
244                    rect.min.x + side,
245                    rect.max.y - side,
246                    rect.min.x + side + small_padding,
247                    rect.max.y
248                ],
249                WHITE,
250            );
251            children.push(Box::new(filler) as Box<dyn View>);
252
253            let margin_width = reader_info.and_then(|r| r.screen_margin_width).unwrap_or(0);
254            let margin_icon = LabeledIcon::new(
255                "margin",
256                rect![
257                    rect.min.x + side + small_padding,
258                    rect.max.y - side,
259                    rect.max.x - 2 * side - big_padding,
260                    rect.max.y
261                ],
262                Event::Show(ViewId::MarginWidthMenu),
263                format!("{} mm", margin_width),
264            );
265            children.push(Box::new(margin_icon) as Box<dyn View>);
266
267            let filler = Filler::new(
268                rect![
269                    rect.max.x - 2 * side - big_padding,
270                    rect.max.y - side,
271                    rect.max.x - 2 * side,
272                    rect.max.y
273                ],
274                WHITE,
275            );
276            children.push(Box::new(filler) as Box<dyn View>);
277        }
278
279        // End of second row.
280
281        let search_icon = Icon::new(
282            "search",
283            rect![
284                rect.max.x - 2 * side,
285                rect.max.y - side,
286                rect.max.x - side,
287                rect.max.y
288            ],
289            Event::Show(ViewId::SearchBar),
290        );
291        children.push(Box::new(search_icon) as Box<dyn View>);
292
293        let toc_icon = Icon::new(
294            "toc",
295            rect![rect.max.x - side, rect.max.y - side, rect.max.x, rect.max.y],
296            Event::Show(ViewId::TableOfContents),
297        );
298        children.push(Box::new(toc_icon) as Box<dyn View>);
299
300        ToolBar {
301            id,
302            rect,
303            children,
304            reflowable,
305        }
306    }
307
308    pub fn update_margin_width(&mut self, margin_width: i32, rq: &mut RenderQueue) {
309        let index = if self.reflowable { 0 } else { 8 };
310        if let Some(labeled_icon) = self.children[index].downcast_mut::<LabeledIcon>() {
311            labeled_icon.update(&format!("{} mm", margin_width), rq);
312        }
313    }
314
315    pub fn update_font_family(&mut self, font_family: String, rq: &mut RenderQueue) {
316        if let Some(labeled_icon) = self.children[1].downcast_mut::<LabeledIcon>() {
317            labeled_icon.update(&font_family, rq);
318        }
319    }
320
321    pub fn update_line_height(&mut self, line_height: f32, rq: &mut RenderQueue) {
322        if let Some(labeled_icon) = self.children[2].downcast_mut::<LabeledIcon>() {
323            labeled_icon.update(&format!("{:.1} em", line_height), rq);
324        }
325    }
326
327    pub fn update_text_align_icon(&mut self, text_align: TextAlign, rq: &mut RenderQueue) {
328        let icon = self.child_mut(4).downcast_mut::<Icon>().unwrap();
329        let name = text_align.icon_name();
330        if icon.name != name {
331            icon.name = name.to_string();
332            rq.add(RenderData::new(icon.id(), *icon.rect(), UpdateMode::Gui));
333        }
334    }
335
336    pub fn update_font_size_slider(&mut self, font_size: f32, rq: &mut RenderQueue) {
337        let slider = self.children[6].as_mut().downcast_mut::<Slider>().unwrap();
338        slider.update(font_size, rq);
339    }
340
341    pub fn update_contrast_exponent_slider(&mut self, exponent: f32, rq: &mut RenderQueue) {
342        let slider = self.children[1].as_mut().downcast_mut::<Slider>().unwrap();
343        slider.update(exponent, rq);
344    }
345
346    pub fn update_contrast_gray_slider(&mut self, gray: f32, rq: &mut RenderQueue) {
347        let slider = self.children[3].as_mut().downcast_mut::<Slider>().unwrap();
348        slider.update(gray, rq);
349    }
350}
351
352impl View for ToolBar {
353    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, _bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
354    fn handle_event(
355        &mut self,
356        evt: &Event,
357        _hub: &Hub,
358        _bus: &mut Bus,
359        _rq: &mut RenderQueue,
360        _context: &mut Context,
361    ) -> bool {
362        match *evt {
363            Event::Gesture(GestureEvent::Tap(center))
364            | Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
365                if self.rect.includes(center) =>
366            {
367                true
368            }
369            Event::Gesture(GestureEvent::Swipe { start, .. }) if self.rect.includes(start) => true,
370            Event::Device(DeviceEvent::Finger { position, .. }) if self.rect.includes(position) => {
371                true
372            }
373            _ => false,
374        }
375    }
376
377    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
378    fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
379
380    fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
381        let dpi = CURRENT_DEVICE.dpi;
382        let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
383        let side = (rect.height() as i32 + thickness) / 2 - thickness;
384
385        let mut index = 0;
386
387        if self.reflowable {
388            let mut remaining_width = rect.width() as i32 - 3 * side;
389            let font_family_label_width = remaining_width / 2;
390            remaining_width -= font_family_label_width;
391            let margin_label_width = remaining_width / 2;
392            let line_height_label_width = remaining_width - margin_label_width;
393
394            // First row.
395
396            let mut x_offset = rect.min.x;
397            self.children[index].resize(
398                rect![
399                    x_offset,
400                    rect.min.y,
401                    x_offset + side + margin_label_width,
402                    rect.min.y + side
403                ],
404                hub,
405                rq,
406                context,
407            );
408            index += 1;
409            x_offset += side + margin_label_width;
410
411            self.children[index].resize(
412                rect![
413                    x_offset,
414                    rect.min.y,
415                    x_offset + side + font_family_label_width,
416                    rect.min.y + side
417                ],
418                hub,
419                rq,
420                context,
421            );
422            index += 1;
423            x_offset += side + font_family_label_width;
424
425            self.children[index].resize(
426                rect![
427                    x_offset,
428                    rect.min.y,
429                    x_offset + side + line_height_label_width,
430                    rect.min.y + side
431                ],
432                hub,
433                rq,
434                context,
435            );
436            index += 1;
437
438            // Separator.
439            self.children[index].resize(
440                rect![rect.min.x, rect.min.y + side, rect.max.x, rect.max.y - side],
441                hub,
442                rq,
443                context,
444            );
445            index += 1;
446
447            // Start of second row.
448            let text_align_rect =
449                rect![rect.min.x, rect.max.y - side, rect.min.x + side, rect.max.y];
450            self.children[index].resize(text_align_rect, hub, rq, context);
451            index += 1;
452
453            let font_size_rect = rect![
454                rect.min.x + side,
455                rect.max.y - side,
456                rect.min.x + 2 * side,
457                rect.max.y
458            ];
459            self.children[index].resize(font_size_rect, hub, rq, context);
460            index += 1;
461
462            self.children[index].resize(
463                rect![
464                    rect.min.x + 2 * side,
465                    rect.max.y - side,
466                    rect.max.x - 2 * side,
467                    rect.max.y
468                ],
469                hub,
470                rq,
471                context,
472            );
473        } else {
474            let remaining_width = rect.width() as i32 - 2 * side;
475            let slider_width = remaining_width / 2;
476
477            // First row.
478            let contrast_icon_rect =
479                rect![rect.min.x, rect.min.y, rect.min.x + side, rect.min.y + side];
480
481            self.children[index].resize(contrast_icon_rect, hub, rq, context);
482            index += 1;
483
484            self.children[index].resize(
485                rect![
486                    rect.min.x + side,
487                    rect.min.y,
488                    rect.min.x + side + slider_width,
489                    rect.min.y + side
490                ],
491                hub,
492                rq,
493                context,
494            );
495            index += 1;
496
497            let gray_icon_rect = rect![
498                rect.min.x + side + slider_width,
499                rect.min.y,
500                rect.min.x + 2 * side + slider_width,
501                rect.min.y + side
502            ];
503
504            self.children[index].resize(gray_icon_rect, hub, rq, context);
505            index += 1;
506
507            self.children[index].resize(
508                rect![
509                    rect.min.x + 2 * side + slider_width,
510                    rect.min.y,
511                    rect.max.x - side / 3,
512                    rect.min.y + side
513                ],
514                hub,
515                rq,
516                context,
517            );
518            index += 1;
519
520            self.children[index].resize(
521                rect![
522                    rect.max.x - side / 3,
523                    rect.min.y,
524                    rect.max.x,
525                    rect.min.y + side
526                ],
527                hub,
528                rq,
529                context,
530            );
531            index += 1;
532
533            // Separator.
534            self.children[index].resize(
535                rect![rect.min.x, rect.min.y + side, rect.max.x, rect.max.y - side],
536                hub,
537                rq,
538                context,
539            );
540            index += 1;
541
542            // Start of second row.
543            self.children[index].resize(
544                rect![rect.min.x, rect.max.y - side, rect.min.x + side, rect.max.y],
545                hub,
546                rq,
547                context,
548            );
549            index += 1;
550
551            let remaining_width = rect.width() as i32 - 3 * side;
552            let margin_label_width = self.children[index + 1].rect().width() as i32;
553            let big_padding = (remaining_width - margin_label_width) / 2;
554            let small_padding = remaining_width - margin_label_width - big_padding;
555
556            self.children[index].resize(
557                rect![
558                    rect.min.x + side,
559                    rect.max.y - side,
560                    rect.min.x + side + small_padding,
561                    rect.max.y
562                ],
563                hub,
564                rq,
565                context,
566            );
567
568            index += 1;
569            self.children[index].resize(
570                rect![
571                    rect.min.x + side + small_padding,
572                    rect.max.y - side,
573                    rect.max.x - 2 * side - big_padding,
574                    rect.max.y
575                ],
576                hub,
577                rq,
578                context,
579            );
580            index += 1;
581            self.children[index].resize(
582                rect![
583                    rect.max.x - 2 * side - big_padding,
584                    rect.max.y - side,
585                    rect.max.x - 2 * side,
586                    rect.max.y
587                ],
588                hub,
589                rq,
590                context,
591            );
592        }
593
594        index += 1;
595
596        // End of second row.
597
598        self.children[index].resize(
599            rect![
600                rect.max.x - 2 * side,
601                rect.max.y - side,
602                rect.max.x - side,
603                rect.max.y
604            ],
605            hub,
606            rq,
607            context,
608        );
609        index += 1;
610
611        self.children[index].resize(
612            rect![rect.max.x - side, rect.max.y - side, rect.max.x, rect.max.y],
613            hub,
614            rq,
615            context,
616        );
617        self.rect = rect;
618    }
619
620    fn rect(&self) -> &Rectangle {
621        &self.rect
622    }
623
624    fn rect_mut(&mut self) -> &mut Rectangle {
625        &mut self.rect
626    }
627
628    fn children(&self) -> &Vec<Box<dyn View>> {
629        &self.children
630    }
631
632    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
633        &mut self.children
634    }
635
636    fn id(&self) -> Id {
637        self.id
638    }
639}