1use super::book::Book;
2use crate::color::{SEPARATOR_NORMAL, WHITE};
3use crate::context::Context;
4use crate::device::CURRENT_DEVICE;
5use crate::font::Fonts;
6use crate::framebuffer::{Framebuffer, UpdateMode};
7use crate::geom::divide;
8use crate::geom::{halves, CycleDir, Dir, Rectangle};
9use crate::gesture::GestureEvent;
10use crate::metadata::Info;
11use crate::settings::{FirstColumn, SecondColumn};
12use crate::unit::scale_by_dpi;
13use crate::view::filler::Filler;
14use crate::view::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER};
15use crate::view::{BIG_BAR_HEIGHT, THICKNESS_MEDIUM};
16
17pub struct Shelf {
18 id: Id,
19 pub rect: Rectangle,
20 children: Vec<Box<dyn View>>,
21 pub max_lines: usize,
22 first_column: FirstColumn,
23 second_column: SecondColumn,
24 thumbnail_previews: bool,
25}
26
27impl Shelf {
28 pub fn new(
29 rect: Rectangle,
30 first_column: FirstColumn,
31 second_column: SecondColumn,
32 thumbnail_previews: bool,
33 ) -> Shelf {
34 let dpi = CURRENT_DEVICE.dpi;
35 let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
36 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
37 let max_lines = ((rect.height() as i32 + thickness) / big_height) as usize;
38 Shelf {
39 id: ID_FEEDER.next(),
40 rect,
41 children: Vec::new(),
42 max_lines,
43 first_column,
44 second_column,
45 thumbnail_previews,
46 }
47 }
48
49 pub fn set_first_column(&mut self, first_column: FirstColumn) {
50 self.first_column = first_column;
51 }
52
53 pub fn set_second_column(&mut self, second_column: SecondColumn) {
54 self.second_column = second_column;
55 }
56
57 pub fn set_thumbnail_previews(&mut self, thumbnail_previews: bool) {
58 self.thumbnail_previews = thumbnail_previews;
59 }
60
61 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, rq, context)))]
62 pub fn update(&mut self, metadata: &[Info], rq: &mut RenderQueue, context: &Context) {
63 self.children.clear();
64 let dpi = CURRENT_DEVICE.dpi;
65 let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
66 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
67 let (small_thickness, big_thickness) = halves(thickness);
68 let max_lines = ((self.rect.height() as i32 + thickness) / big_height) as usize;
69 let book_heights = divide(self.rect.height() as i32, max_lines as i32);
70 let mut y_pos = self.rect.min.y;
71
72 #[cfg(feature = "tracing")]
73 let _span = tracing::info_span!("processing metadata").entered();
74 for (index, info) in metadata.iter().enumerate() {
75 #[cfg(feature = "tracing")]
76 let _span = tracing::info_span!("processing metadata entry", info = ?info).entered();
77
78 let y_min = y_pos + if index > 0 { big_thickness } else { 0 };
79 let y_max = y_pos + book_heights[index]
80 - if index < max_lines - 1 {
81 small_thickness
82 } else {
83 0
84 };
85
86 let preview = if self.thumbnail_previews {
87 let existing = context.library.thumbnail_preview(&info.file.path);
88 if existing.is_none() {
89 tracing::debug!(path = %info.file.path.display(), "no preview");
90 }
91 existing
92 } else {
93 None
94 };
95
96 let book = Book::new(
97 rect![self.rect.min.x, y_min, self.rect.max.x, y_max],
98 info.clone(),
99 index,
100 self.first_column,
101 self.second_column,
102 preview,
103 );
104 self.children.push(Box::new(book) as Box<dyn View>);
105
106 if index < max_lines - 1 {
107 let separator = Filler::new(
108 rect![self.rect.min.x, y_max, self.rect.max.x, y_max + thickness],
109 SEPARATOR_NORMAL,
110 );
111 self.children.push(Box::new(separator) as Box<dyn View>);
112 }
113
114 y_pos += book_heights[index];
115 }
116
117 if metadata.len() < max_lines {
118 let y_start = y_pos + if metadata.is_empty() { 0 } else { thickness };
119 let filler = Filler::new(
120 rect![self.rect.min.x, y_start, self.rect.max.x, self.rect.max.y],
121 WHITE,
122 );
123 self.children.push(Box::new(filler) as Box<dyn View>);
124 }
125
126 self.max_lines = max_lines;
127 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Partial));
128 }
129}
130
131impl View for Shelf {
132 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _hub, bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
133 fn handle_event(
134 &mut self,
135 evt: &Event,
136 _hub: &Hub,
137 bus: &mut Bus,
138 _rq: &mut RenderQueue,
139 _context: &mut Context,
140 ) -> bool {
141 match *evt {
142 Event::Gesture(GestureEvent::Swipe { dir, start, .. }) if self.rect.includes(start) => {
143 match dir {
144 Dir::West => {
145 bus.push_back(Event::Page(CycleDir::Next));
146 true
147 }
148 Dir::East => {
149 bus.push_back(Event::Page(CycleDir::Previous));
150 true
151 }
152 _ => false,
153 }
154 }
155 _ => false,
156 }
157 }
158
159 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
160 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
161
162 fn rect(&self) -> &Rectangle {
163 &self.rect
164 }
165
166 fn rect_mut(&mut self) -> &mut Rectangle {
167 &mut self.rect
168 }
169
170 fn children(&self) -> &Vec<Box<dyn View>> {
171 &self.children
172 }
173
174 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
175 &mut self.children
176 }
177
178 fn id(&self) -> Id {
179 self.id
180 }
181}