1use super::chapter_label::ChapterLabel;
2use crate::color::WHITE;
3use crate::context::Context;
4use crate::document::{Document, Neighbors, TocEntry};
5use crate::font::Fonts;
6use crate::framebuffer::{Framebuffer, UpdateMode};
7use crate::geom::{halves, CycleDir, Rectangle};
8use crate::gesture::GestureEvent;
9use crate::input::DeviceEvent;
10use crate::view::filler::Filler;
11use crate::view::icon::Icon;
12use crate::view::page_label::PageLabel;
13use crate::view::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER};
14
15pub struct BottomBar {
16 id: Id,
17 rect: Rectangle,
18 children: Vec<Box<dyn View>>,
19 is_prev_disabled: bool,
20 is_next_disabled: bool,
21}
22
23impl BottomBar {
24 pub fn new(
25 rect: Rectangle,
26 doc: &mut dyn Document,
27 toc: Option<Vec<TocEntry>>,
28 current_page: usize,
29 pages_count: usize,
30 neighbors: &Neighbors,
31 synthetic: bool,
32 ) -> BottomBar {
33 let id = ID_FEEDER.next();
34 let mut children = Vec::new();
35 let side = rect.height() as i32;
36 let is_prev_disabled = neighbors.previous_page.is_none();
37 let is_next_disabled = neighbors.next_page.is_none();
38
39 let prev_rect = rect![rect.min, rect.min + side];
40
41 if is_prev_disabled {
42 let prev_filler = Filler::new(prev_rect, WHITE);
43 children.push(Box::new(prev_filler) as Box<dyn View>);
44 } else {
45 let prev_icon = Icon::new("arrow-left", prev_rect, Event::Page(CycleDir::Previous));
46 children.push(Box::new(prev_icon) as Box<dyn View>);
47 }
48
49 let (small_half_width, big_half_width) = halves(rect.width() as i32 - 2 * side);
50
51 let chapter_rect = rect![
52 pt!(rect.min.x + side, rect.min.y),
53 pt!(rect.min.x + side + small_half_width, rect.max.y)
54 ];
55
56 let rtoc = toc.or_else(|| doc.toc());
57 let chapter = rtoc.as_ref().and_then(|toc| doc.chapter(current_page, toc));
58 let title = chapter.map(|(c, _)| c.title.clone()).unwrap_or_default();
59 let progress = chapter.map(|(_, p)| p).unwrap_or_default();
60 let chapter_label = ChapterLabel::new(chapter_rect, title, progress);
61 children.push(Box::new(chapter_label) as Box<dyn View>);
62
63 let page_label = PageLabel::new(
64 rect![
65 pt!(rect.max.x - side - big_half_width, rect.min.y),
66 pt!(rect.max.x - side, rect.max.y)
67 ],
68 current_page,
69 pages_count,
70 synthetic,
71 );
72 children.push(Box::new(page_label) as Box<dyn View>);
73
74 let next_rect = rect![rect.max - side, rect.max];
75
76 if is_next_disabled {
77 let next_filler = Filler::new(next_rect, WHITE);
78 children.push(Box::new(next_filler) as Box<dyn View>);
79 } else {
80 let next_icon = Icon::new(
81 "arrow-right",
82 rect![rect.max - side, rect.max],
83 Event::Page(CycleDir::Next),
84 );
85 children.push(Box::new(next_icon) as Box<dyn View>);
86 }
87
88 BottomBar {
89 id,
90 rect,
91 children,
92 is_prev_disabled,
93 is_next_disabled,
94 }
95 }
96
97 pub fn update_chapter_label(&mut self, title: String, progress: f32, rq: &mut RenderQueue) {
98 let chapter_label = self.child_mut(1).downcast_mut::<ChapterLabel>().unwrap();
99 chapter_label.update(title, progress, rq);
100 }
101
102 pub fn update_page_label(
103 &mut self,
104 current_page: usize,
105 pages_count: usize,
106 rq: &mut RenderQueue,
107 ) {
108 let page_label = self.child_mut(2).downcast_mut::<PageLabel>().unwrap();
109 page_label.update(current_page, pages_count, rq);
110 }
111
112 pub fn update_icons(&mut self, neighbors: &Neighbors, rq: &mut RenderQueue) {
113 let is_prev_disabled = neighbors.previous_page.is_none();
114
115 if self.is_prev_disabled != is_prev_disabled {
116 let index = 0;
117 let prev_rect = *self.child(index).rect();
118 if is_prev_disabled {
119 let prev_filler = Filler::new(prev_rect, WHITE);
120 self.children[index] = Box::new(prev_filler) as Box<dyn View>;
121 } else {
122 let prev_icon = Icon::new("arrow-left", prev_rect, Event::Page(CycleDir::Previous));
123 self.children[index] = Box::new(prev_icon) as Box<dyn View>;
124 }
125 self.is_prev_disabled = is_prev_disabled;
126 rq.add(RenderData::new(self.id, prev_rect, UpdateMode::Gui));
127 }
128
129 let is_next_disabled = neighbors.next_page.is_none();
130
131 if self.is_next_disabled != is_next_disabled {
132 let index = self.len() - 1;
133 let next_rect = *self.child(index).rect();
134 if is_next_disabled {
135 let next_filler = Filler::new(next_rect, WHITE);
136 self.children[index] = Box::new(next_filler) as Box<dyn View>;
137 } else {
138 let next_icon = Icon::new("arrow-right", next_rect, Event::Page(CycleDir::Next));
139 self.children[index] = Box::new(next_icon) as Box<dyn View>;
140 }
141 self.is_next_disabled = is_next_disabled;
142 rq.add(RenderData::new(self.id, next_rect, UpdateMode::Gui));
143 }
144 }
145}
146
147impl View for BottomBar {
148 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, _bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
149 fn handle_event(
150 &mut self,
151 evt: &Event,
152 _hub: &Hub,
153 _bus: &mut Bus,
154 _rq: &mut RenderQueue,
155 _context: &mut Context,
156 ) -> bool {
157 match *evt {
158 Event::Gesture(GestureEvent::Tap(center))
159 | Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
160 if self.rect.includes(center) =>
161 {
162 true
163 }
164 Event::Gesture(GestureEvent::Swipe { start, end, .. })
165 if self.rect.includes(start) && self.rect.includes(end) =>
166 {
167 true
168 }
169 Event::Device(DeviceEvent::Finger { position, .. }) if self.rect.includes(position) => {
170 true
171 }
172 _ => false,
173 }
174 }
175
176 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
177 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
178
179 fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
180 let side = rect.height() as i32;
181 let (small_half_width, big_half_width) = halves(rect.width() as i32 - 2 * side);
182 let prev_rect = rect![rect.min, rect.min + side];
183 self.children[0].resize(prev_rect, hub, rq, context);
184 let chapter_rect = rect![
185 pt!(rect.min.x + side, rect.min.y),
186 pt!(rect.min.x + side + small_half_width, rect.max.y)
187 ];
188 self.children[1].resize(chapter_rect, hub, rq, context);
189 let page_label_rect = rect![
190 pt!(rect.max.x - side - big_half_width, rect.min.y),
191 pt!(rect.max.x - side, rect.max.y)
192 ];
193 self.children[2].resize(page_label_rect, hub, rq, context);
194 let next_rect = rect![rect.max - side, rect.max];
195 self.children[3].resize(next_rect, hub, rq, context);
196 self.rect = rect;
197 }
198
199 fn rect(&self) -> &Rectangle {
200 &self.rect
201 }
202
203 fn rect_mut(&mut self) -> &mut Rectangle {
204 &mut self.rect
205 }
206
207 fn children(&self) -> &Vec<Box<dyn View>> {
208 &self.children
209 }
210
211 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
212 &mut self.children
213 }
214
215 fn id(&self) -> Id {
216 self.id
217 }
218}