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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}