|
1 | | -use egui::{Context, ScrollArea, Ui, vec2}; |
| 1 | +use std::{cmp::min, fmt::Write, ops::Range}; |
| 2 | + |
| 3 | +use egui::{Context, Layout, ScrollArea, Ui, Vec2, Widget}; |
2 | 4 | use entrace_core::display_error_context; |
3 | 5 | use tracing::{error, info}; |
4 | 6 |
|
5 | 7 | use crate::{ |
6 | | - App, LogState, LogStatus, SpanContext, row_height, |
| 8 | + App, LogState, LogStatus, SpanContext, |
7 | 9 | search::{Query, QueryError, QueryResult, QueryTiming, search_settings_dialog}, |
8 | 10 | span, |
9 | 11 | }; |
10 | 12 |
|
11 | | -#[derive(Debug)] |
12 | | -pub enum LayoutCommand { |
13 | | - Space(f32), |
14 | | - Span(u32), |
15 | | -} |
16 | | -#[derive(Debug)] |
17 | | -pub struct QueryLayoutCache { |
18 | | - /// this is a bool instead of an option, so we can re-use the same [commands](QueryLayoutCache::commands) allocation in multiple successive |
19 | | - /// frames, since it is going to be similar in length |
20 | | - pub is_valid: bool, |
21 | | - pub commands: Vec<LayoutCommand>, |
22 | | - pub last_scroll_offset: Option<f32>, |
23 | | - pub last_inner_height: Option<f32>, |
24 | | -} |
25 | | - |
26 | | -impl Default for QueryLayoutCache { |
27 | | - fn default() -> Self { |
28 | | - Self::new() |
29 | | - } |
30 | | -} |
31 | | - |
32 | | -impl QueryLayoutCache { |
33 | | - pub fn new() -> Self { |
34 | | - Self { |
35 | | - is_valid: false, |
36 | | - commands: Vec::with_capacity(32), |
37 | | - last_scroll_offset: None, |
38 | | - last_inner_height: None, |
39 | | - } |
40 | | - } |
41 | | - /// Convenience method for [QueryLayoutCache.commands::push] |
42 | | - pub fn push(&mut self, command: LayoutCommand) { |
43 | | - self.commands.push(command); |
44 | | - } |
45 | | - pub fn invalidate(&mut self) { |
46 | | - self.is_valid = false; |
47 | | - self.commands.clear(); |
48 | | - } |
49 | | -} |
50 | | - |
51 | 13 | pub fn query_windows(ui: &mut Ui, ctx: &Context, app: &mut App) { |
52 | 14 | search_settings_dialog(ui, &mut app.search_state); |
53 | 15 | for i in 0..app.search_state.queries.len() { |
@@ -118,159 +80,71 @@ pub fn query_windows(ui: &mut Ui, ctx: &Context, app: &mut App) { |
118 | 80 | i += 1; |
119 | 81 | } |
120 | 82 | } |
121 | | - |
122 | | -pub fn query_result_list(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) { |
123 | | - ui.label(format!("Got {} spans.", result.ids.len())); |
124 | | - let scroller = ScrollArea::new([false, true]) |
125 | | - .auto_shrink([false, false]) |
126 | | - .stick_to_bottom(false) |
127 | | - .show(ui, |ui| match result.layout_cache.is_valid { |
128 | | - true => result_list_cached(ui, result, log), |
129 | | - false => result_list_no_cache(ui, result, log), |
130 | | - }); |
131 | | - |
132 | | - let cache = &mut result.layout_cache; |
133 | | - let height = scroller.inner_rect.height(); |
134 | | - let new_offset = scroller.state.offset.y; |
135 | | - if matches!(cache.last_inner_height, Some(last) if last != height) |
136 | | - || matches!(cache.last_scroll_offset, Some(last) if last != new_offset) |
137 | | - { |
138 | | - cache.invalidate(); |
139 | | - } |
140 | | - |
141 | | - cache.last_inner_height = Some(height); |
142 | | - cache.last_scroll_offset = Some(new_offset); |
| 83 | +#[derive(Debug)] |
| 84 | +pub struct PaginatedResults { |
| 85 | + cur_page: usize, |
| 86 | + page_size: usize, |
| 87 | + nr_entries: usize, |
| 88 | + page_entry_text: String, |
143 | 89 | } |
144 | | -fn result_list_cached(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) { |
145 | | - let log_reader = log.trace_provider.read().unwrap(); |
146 | | - let cache = &mut result.layout_cache; |
147 | | - let mut ctx = SpanContext::QueryResults { |
148 | | - locating_state: &log.locating_state, |
149 | | - trace_provider: log.trace_provider.clone(), |
150 | | - }; |
151 | | - let mut invalidate_cache = false; |
152 | | - for command in &cache.commands { |
153 | | - match command { |
154 | | - LayoutCommand::Space(x) => { |
155 | | - ui.allocate_space(vec2(10.0, *x)); |
156 | | - } |
157 | | - LayoutCommand::Span(span_id) => { |
158 | | - let resp = span(ui, &mut ctx, &log_reader, *span_id); |
159 | | - if let Some(resp) = resp.header_response |
160 | | - && resp.clicked() |
161 | | - { |
162 | | - invalidate_cache = true; |
163 | | - result.cull_open_state.toggle(*span_id as usize); |
164 | | - } |
165 | | - } |
| 90 | +impl PaginatedResults { |
| 91 | + pub fn new(nr_entries: usize) -> PaginatedResults { |
| 92 | + PaginatedResults { |
| 93 | + cur_page: 0, |
| 94 | + page_size: 100, |
| 95 | + page_entry_text: String::from("0"), |
| 96 | + nr_entries, |
166 | 97 | } |
167 | 98 | } |
168 | | - if invalidate_cache { |
169 | | - cache.invalidate(); |
| 99 | + pub fn cur_range(&self) -> Range<usize> { |
| 100 | + let start = self.cur_page * self.page_size; |
| 101 | + start..min(start + self.page_size, self.nr_entries) |
170 | 102 | } |
171 | | -} |
172 | | -fn result_list_no_cache(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) { |
173 | | - let log_reader = log.trace_provider.read().unwrap(); |
174 | | - let row_height = row_height(ui); |
175 | | - let cache = &mut result.layout_cache; |
176 | | - cache.invalidate(); |
177 | | - let mut invalidate_cache = false; |
178 | | - let mut _non_culled_cnt = 0; |
179 | | - //let skippable_space = ui.clip_rect().min.y - ui.cursor().min.y; |
180 | | - //let skippable_rows = (skippable_space / row_height).max(0.0) as usize; |
181 | | - //ui.allocate_space(vec2(ui.available_width(), row_height * skippable_rows as f32)); |
182 | | - //println!("Skipped rows: {skippable_rows}"); |
183 | | - enum Region { |
184 | | - BeforeVisible(f32), |
185 | | - Visible, |
186 | | - AfterVisible(f32), |
| 103 | + pub fn set_page(&mut self, new: usize) { |
| 104 | + self.cur_page = min(new, self.page_cnt().saturating_sub(1)); |
| 105 | + self.page_entry_text.clear(); |
| 106 | + write!(self.page_entry_text, "{}", self.cur_page).ok(); |
187 | 107 | } |
188 | | - let mut region = Region::BeforeVisible(0.0); |
189 | | - //let mut region = Region::Visible; |
190 | | - let mut idx = 0; |
191 | | - let mut ctx = SpanContext::QueryResults { |
192 | | - locating_state: &log.locating_state, |
193 | | - trace_provider: log.trace_provider.clone(), |
194 | | - }; |
195 | | - while idx < result.ids.len() { |
196 | | - let span_id = result.ids[idx]; |
197 | | - |
198 | | - match region { |
199 | | - Region::BeforeVisible(space_before) => { |
200 | | - let span_end_lower_bound = |
201 | | - ui.cursor().min.y + space_before + row_height + ui.spacing().item_spacing.y; |
202 | | - let still_before_visible = span_end_lower_bound < ui.clip_rect().min.y; |
203 | | - //info!(span_end_lower_bound, clip_min = ui.clip_rect().min.y, "BeforeVisible"); |
204 | | - if !still_before_visible { |
205 | | - //println!("{idx} is (partially) visible. culled height before: {space_before}"); |
206 | | - ui.allocate_space(vec2(10.0, space_before.max(0.0))); |
207 | | - cache.push(LayoutCommand::Space(space_before.max(0.0))); |
208 | | - region = Region::Visible; |
209 | | - continue; |
210 | | - } |
211 | | - |
212 | | - if !result.cull_open_state.get(span_id as usize).unwrap_or(true) { |
213 | | - region = Region::BeforeVisible(space_before + row_height); |
214 | | - } else { |
215 | | - // if this span is open, then its children should be visible eveen |
216 | | - // if the header is not. so draw it. |
217 | | - // To draw it in the correct space, we allocate the (closed) space |
218 | | - // before, then go again from 0. |
219 | | - ui.allocate_space(vec2(10.0, space_before.max(0.0))); |
220 | | - cache.push(LayoutCommand::Space(space_before.max(0.0))); |
221 | | - // println!( |
222 | | - // "{idx} is open-culled, so allocated the current carry of {space_before} \ |
223 | | - // before" |
224 | | - // ); |
225 | | - span(ui, &mut ctx, &log_reader, span_id); |
226 | | - cache.push(LayoutCommand::Span(span_id)); |
227 | | - region = Region::BeforeVisible(0.0); |
228 | | - } |
229 | | - } |
230 | | - Region::Visible => { |
231 | | - //println!("Visible({idx})"); |
232 | | - let resp = span(ui, &mut ctx, &log_reader, span_id); |
233 | | - cache.push(LayoutCommand::Span(span_id)); |
234 | | - _non_culled_cnt += 1; |
235 | | - if let Some(resp) = resp.header_response { |
236 | | - if ui.clip_rect().max.y <= resp.rect.min.y { |
237 | | - region = Region::AfterVisible(0.0); |
238 | | - idx += 1; |
239 | | - continue; |
240 | | - } |
241 | | - if resp.clicked() { |
242 | | - invalidate_cache = true; |
243 | | - result.cull_open_state.toggle(idx); |
244 | | - } |
245 | | - } |
246 | | - if idx == result.ids.len().saturating_sub(1) { |
247 | | - ui.allocate_space(vec2(10.0, row_height * 3.0)); |
248 | | - cache.push(LayoutCommand::Space(row_height * 3.0)); |
249 | | - } |
250 | | - } |
251 | | - Region::AfterVisible(after_space) => { |
252 | | - if !result.cull_open_state.get(idx).unwrap_or(true) { |
253 | | - region = Region::AfterVisible(after_space + row_height); |
254 | | - } else { |
255 | | - ui.allocate_space(vec2(10.0, after_space.max(0.0))); |
256 | | - cache.push(LayoutCommand::Space(after_space.max(0.0))); |
257 | | - |
258 | | - //println!("{idx} is open-culled, so allocated the current carry of {after_space} before"); |
259 | | - span(ui, &mut ctx, &log_reader, span_id); |
260 | | - cache.push(LayoutCommand::Span(span_id)); |
261 | | - region = Region::AfterVisible(0.0); |
262 | | - } |
263 | | - } |
264 | | - } |
265 | | - idx += 1; |
266 | | - } |
267 | | - if let Region::AfterVisible(after_space) = region { |
268 | | - ui.allocate_space(vec2(10.0, after_space)); |
269 | | - cache.push(LayoutCommand::Space(after_space.max(0.0))); |
| 108 | + pub fn page_cnt(&self) -> usize { |
| 109 | + self.nr_entries.div_ceil(self.page_size) |
270 | 110 | } |
271 | | - if invalidate_cache { |
272 | | - cache.invalidate(); |
273 | | - } |
274 | | - cache.is_valid = true; |
275 | | - //println!("Not culled: {non_culled_cnt}"); |
| 111 | +} |
| 112 | +pub fn result_list_pagination(ui: &mut Ui, result: &mut QueryResult) { |
| 113 | + ui.allocate_ui_with_layout(Vec2::ZERO, Layout::left_to_right(egui::Align::Center), |ui| { |
| 114 | + if ui.button("<").clicked() { |
| 115 | + result.pages.set_page(result.pages.cur_page.saturating_sub(1)); |
| 116 | + } |
| 117 | + ui.label("Page "); |
| 118 | + let ed = egui::TextEdit::singleline(&mut result.pages.page_entry_text) |
| 119 | + .desired_width(0.0) |
| 120 | + .clip_text(false) |
| 121 | + .ui(ui); |
| 122 | + if ed.lost_focus() |
| 123 | + && let Ok(x) = str::parse::<usize>(&result.pages.page_entry_text) |
| 124 | + { |
| 125 | + result.pages.set_page(x); |
| 126 | + } |
| 127 | + ui.label(format!("/ {}", result.pages.page_cnt())); |
| 128 | + if ui.button(">").clicked() { |
| 129 | + result.pages.set_page(result.pages.cur_page.saturating_add(1)); |
| 130 | + } |
| 131 | + }); |
| 132 | +} |
| 133 | +pub fn query_result_list(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) { |
| 134 | + ui.label(format!("Got {} spans.", result.ids.len())); |
| 135 | + result_list_pagination(ui, result); |
| 136 | + ScrollArea::new([false, true]).auto_shrink([false, false]).stick_to_bottom(false).show( |
| 137 | + ui, |
| 138 | + |ui| { |
| 139 | + let result_range = result.pages.cur_range(); |
| 140 | + let log_reader = log.trace_provider.read().unwrap(); |
| 141 | + let mut ctx = SpanContext::QueryResults { |
| 142 | + locating_state: &log.locating_state, |
| 143 | + trace_provider: log.trace_provider.clone(), |
| 144 | + }; |
| 145 | + for id in result_range { |
| 146 | + span(ui, &mut ctx, &log_reader, result.ids[id]); |
| 147 | + } |
| 148 | + }, |
| 149 | + ); |
276 | 150 | } |
0 commit comments