cadmus_core/view/
page_label.rs

1use super::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ViewId, ID_FEEDER};
2use crate::color::{BLACK, WHITE};
3use crate::context::Context;
4use crate::device::CURRENT_DEVICE;
5use crate::document::BYTES_PER_PAGE;
6use crate::font::{font_from_style, Fonts, NORMAL_STYLE};
7use crate::framebuffer::{Framebuffer, UpdateMode};
8use crate::geom::Rectangle;
9use crate::gesture::GestureEvent;
10
11pub struct PageLabel {
12    id: Id,
13    rect: Rectangle,
14    children: Vec<Box<dyn View>>,
15    current_page: usize,
16    pages_count: usize,
17    synthetic: bool,
18}
19
20impl PageLabel {
21    pub fn new(
22        rect: Rectangle,
23        current_page: usize,
24        pages_count: usize,
25        synthetic: bool,
26    ) -> PageLabel {
27        PageLabel {
28            id: ID_FEEDER.next(),
29            rect,
30            children: Vec::new(),
31            current_page,
32            pages_count,
33            synthetic,
34        }
35    }
36
37    pub fn update(&mut self, current_page: usize, pages_count: usize, rq: &mut RenderQueue) {
38        let mut render = false;
39        if self.current_page != current_page {
40            self.current_page = current_page;
41            render = true;
42        }
43        if self.pages_count != pages_count {
44            self.pages_count = pages_count;
45            render = true;
46        }
47        if render {
48            rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
49        }
50    }
51
52    pub fn text(&self, size: u8) -> String {
53        if self.pages_count == 0 {
54            return "No pages".to_string();
55        }
56        let (current_page, pages_count, precision) = if self.synthetic {
57            (
58                self.current_page as f64 / BYTES_PER_PAGE,
59                self.pages_count as f64 / BYTES_PER_PAGE,
60                1,
61            )
62        } else {
63            (self.current_page as f64 + 1.0, self.pages_count as f64, 0)
64        };
65        let percent = 100.0 * self.current_page as f32 / self.pages_count as f32;
66        match size {
67            0 => format!(
68                "Page {1:.0$} of {2:.0$} ({3:.1}%)",
69                precision, current_page, pages_count, percent
70            ),
71            1 => format!(
72                "P. {1:.0$} of {2:.0$} ({3:.1}%)",
73                precision, current_page, pages_count, percent
74            ),
75            2 => format!(
76                "{1:.0$}/{2:.0$} ({3:.1}%)",
77                precision, current_page, pages_count, percent
78            ),
79            3 => format!("{1:.0$} ({2:.1}%)", precision, current_page, percent),
80            _ => format!("{:.1}%", percent),
81        }
82    }
83}
84
85impl View for PageLabel {
86    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
87    fn handle_event(
88        &mut self,
89        evt: &Event,
90        _hub: &Hub,
91        bus: &mut Bus,
92        _rq: &mut RenderQueue,
93        _context: &mut Context,
94    ) -> bool {
95        match *evt {
96            Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
97                bus.push_back(Event::Toggle(ViewId::GoToPage));
98                true
99            }
100            Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
101                if self.rect.includes(center) =>
102            {
103                bus.push_back(Event::ToggleNear(ViewId::PageMenu, self.rect));
104                true
105            }
106            _ => false,
107        }
108    }
109
110    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
111    fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
112        let dpi = CURRENT_DEVICE.dpi;
113        let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
114        let padding = font.em() as i32 / 2;
115        let max_width = self.rect.width().saturating_sub(2 * padding as u32) as i32;
116        let mut plan = font.plan(&self.text(0), None, None);
117        for size in 1..=4 {
118            if plan.width <= max_width {
119                break;
120            }
121            plan = font.plan(&self.text(size), None, None);
122        }
123        font.crop_right(&mut plan, max_width);
124        let dx = padding + (max_width - plan.width) / 2;
125        let dy = (self.rect.height() as i32 - font.x_heights.0 as i32) / 2;
126        let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy);
127        fb.draw_rectangle(&self.rect, WHITE);
128        font.render(fb, BLACK, &plan, pt);
129    }
130
131    fn rect(&self) -> &Rectangle {
132        &self.rect
133    }
134
135    fn rect_mut(&mut self) -> &mut Rectangle {
136        &mut self.rect
137    }
138
139    fn children(&self) -> &Vec<Box<dyn View>> {
140        &self.children
141    }
142
143    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
144        &mut self.children
145    }
146
147    fn id(&self) -> Id {
148        self.id
149    }
150}