1use std::{
2 collections::{BTreeMap, btree_map::Entry},
3 ops::{Range, RangeInclusive},
4};
5
6use egui::{
7 Align, Context, Id, IdMap, Layout, NumExt as _, Rangef, Rect, Response, Ui, UiBuilder, Vec2,
8 Vec2b, vec2,
9};
10use vec1::Vec1;
11
12use crate::{SplitScroll, SplitScrollDelegate, columns::Column};
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
16pub enum AutoSizeMode {
17 #[default]
19 Never,
20
21 Always,
23
24 OnParentResize,
26}
27
28#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
29pub struct TableState {
30 pub col_widths: IdMap<f32>,
32
33 pub parent_width: Option<f32>,
34}
35
36impl TableState {
37 pub fn load(ctx: &egui::Context, id: Id) -> Option<Self> {
38 ctx.data_mut(|d| d.get_persisted(id))
39 }
40
41 pub fn store(self, ctx: &egui::Context, id: Id) {
42 ctx.data_mut(|d| d.insert_persisted(id, self));
43 }
44
45 pub fn id(ui: &Ui, id_salt: Id) -> Id {
46 ui.make_persistent_id(id_salt)
47 }
48
49 pub fn reset(ctx: &egui::Context, id: Id) {
50 ctx.data_mut(|d| {
51 d.remove::<Self>(id);
52 });
53 }
54}
55
56#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
60pub struct HeaderRow {
61 pub height: f32,
62
63 pub groups: Vec<Range<usize>>,
68}
69
70impl HeaderRow {
71 pub fn new(height: f32) -> Self {
72 Self {
73 height,
74 groups: Default::default(),
75 }
76 }
77}
78
79pub struct Table {
98 columns: Vec<Column>,
100
101 id_salt: Id,
106
107 num_sticky_cols: usize,
109
110 headers: Vec<HeaderRow>,
112
113 num_rows: u64,
115
116 auto_size_mode: AutoSizeMode,
118
119 scroll_to_columns: Option<(RangeInclusive<usize>, Option<Align>)>,
120 scroll_to_rows: Option<(RangeInclusive<u64>, Option<Align>)>,
121
122 stick_to_bottom: bool,
126}
127
128impl Default for Table {
129 fn default() -> Self {
130 Self {
131 columns: vec![],
132 id_salt: Id::new("table"),
133 num_sticky_cols: 0,
134 headers: vec![HeaderRow::new(16.0)],
135 num_rows: 0,
136 auto_size_mode: AutoSizeMode::default(),
137 scroll_to_columns: None,
138 scroll_to_rows: None,
139 stick_to_bottom: false,
140 }
141 }
142}
143
144#[derive(Clone, Debug)]
145#[non_exhaustive]
146pub struct CellInfo {
147 pub col_nr: usize,
148
149 pub row_nr: u64,
150
151 pub table_id: Id,
153 }
155
156#[derive(Clone, Debug)]
157#[non_exhaustive]
158pub struct HeaderCellInfo {
159 pub group_index: usize,
160
161 pub col_range: Range<usize>,
162
163 pub row_nr: usize,
165
166 pub table_id: Id,
168}
169
170#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
172#[non_exhaustive]
173pub struct PrefetchInfo {
174 pub num_sticky_columns: usize,
176
177 pub visible_columns: Range<usize>,
179
180 pub visible_rows: Range<u64>,
182
183 pub table_id: Id,
185}
186
187pub trait TableDelegate {
191 fn prepare(&mut self, _info: &PrefetchInfo) {}
195
196 fn header_cell_ui(&mut self, ui: &mut Ui, cell: &HeaderCellInfo);
200
201 fn row_ui(&mut self, _ui: &mut Ui, _row_nr: u64) {}
208
209 fn cell_ui(&mut self, ui: &mut Ui, cell: &CellInfo);
213
214 fn row_top_offset(&self, _ctx: &Context, _table_id: Id, row_nr: u64) -> f32 {
221 row_nr as f32 * self.default_row_height()
222 }
223
224 fn default_row_height(&self) -> f32 {
228 20.0
229 }
230}
231
232impl Table {
233 #[inline]
235 pub fn new() -> Self {
236 Self::default()
237 }
238
239 #[inline]
244 pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
245 self.id_salt = Id::new(id_salt);
246 self
247 }
248
249 #[inline]
251 pub fn num_rows(mut self, num_rows: u64) -> Self {
252 self.num_rows = num_rows;
253 self
254 }
255
256 #[inline]
258 pub fn columns(mut self, columns: impl Into<Vec<Column>>) -> Self {
259 self.columns = columns.into();
260 self
261 }
262
263 #[inline]
267 pub fn num_sticky_cols(mut self, num_sticky_cols: usize) -> Self {
268 self.num_sticky_cols = num_sticky_cols;
269 self
270 }
271
272 #[inline]
274 pub fn headers(mut self, headers: impl Into<Vec<HeaderRow>>) -> Self {
275 self.headers = headers.into();
276 self
277 }
278
279 #[inline]
281 pub fn auto_size_mode(mut self, auto_size_mode: AutoSizeMode) -> Self {
282 self.auto_size_mode = auto_size_mode;
283 self
284 }
285
286 #[inline]
293 pub fn stick_to_bottom(mut self, stick: bool) -> Self {
294 self.stick_to_bottom = stick;
295 self
296 }
297
298 #[inline]
301 pub fn get_id(&self, ui: &Ui) -> Id {
302 TableState::id(ui, self.id_salt)
303 }
304
305 #[inline]
313 pub fn scroll_to_row(self, row: u64, align: Option<Align>) -> Self {
314 self.scroll_to_rows(row..=row, align)
315 }
316
317 #[inline]
321 pub fn scroll_to_rows(mut self, rows: RangeInclusive<u64>, align: Option<Align>) -> Self {
322 self.scroll_to_rows = Some((rows, align));
323 self
324 }
325
326 #[inline]
334 pub fn scroll_to_column(self, column: usize, align: Option<Align>) -> Self {
335 self.scroll_to_columns(column..=column, align)
336 }
337
338 #[inline]
342 pub fn scroll_to_columns(
343 mut self,
344 columns: RangeInclusive<usize>,
345 align: Option<Align>,
346 ) -> Self {
347 self.scroll_to_columns = Some((columns, align));
348 self
349 }
350
351 #[expect(clippy::unused_self)] fn get_row_top_offset(
356 &self,
357 ctx: &Context,
358 table_id: Id,
359 table_delegate: &dyn TableDelegate,
360 row_nr: u64,
361 ) -> f32 {
362 table_delegate.row_top_offset(ctx, table_id, row_nr)
363 }
364
365 fn get_row_nr_at_y_offset(
367 &self,
368 ctx: &Context,
369 table_id: Id,
370 table_delegate: &dyn TableDelegate,
371 y_offset: f32,
372 ) -> u64 {
373 partition_point(0..=self.num_rows, |row_nr| {
374 y_offset <= self.get_row_top_offset(ctx, table_id, table_delegate, row_nr)
375 })
376 .saturating_sub(1)
377 }
378
379 pub fn show(mut self, ui: &mut Ui, table_delegate: &mut dyn TableDelegate) -> Response {
380 self.num_sticky_cols = self.num_sticky_cols.at_most(self.columns.len());
381
382 let id = TableState::id(ui, self.id_salt);
383 let state = TableState::load(ui, id);
384 let is_new = state.is_none();
385 let do_full_sizing_pass = is_new;
386 let mut state = state.unwrap_or_default();
387
388 for (i, column) in self.columns.iter_mut().enumerate() {
389 let column_id = column.id_for(i);
390 if let Some(existing_width) = state.col_widths.get(&column_id) {
391 column.current = *existing_width;
392 }
393 column.current = column.range.clamp(column.current);
394
395 if do_full_sizing_pass {
396 column.auto_size_this_frame = true;
397 }
398 }
399
400 let parent_width = ui.available_width();
401 let auto_size = match self.auto_size_mode {
402 AutoSizeMode::Never => false,
403 AutoSizeMode::Always => true,
404 AutoSizeMode::OnParentResize => state.parent_width != Some(parent_width),
405 };
406 if auto_size {
407 Column::auto_size(&mut self.columns, parent_width);
408 }
409 state.parent_width = Some(parent_width);
410
411 let col_x = {
412 let mut x = ui.cursor().min.x;
413 let mut col_x = Vec1::with_capacity(x, self.columns.len() + 1);
414 for column in &self.columns {
415 x += column.current;
416 col_x.push(x);
417 }
418 col_x
419 };
420
421 let header_row_y = {
422 let mut y = ui.cursor().min.y;
423 let mut sticky_row_y = Vec1::with_capacity(y, self.headers.len() + 1);
424 for header in &self.headers {
425 y += header.height;
426 sticky_row_y.push(y);
427 }
428 sticky_row_y
429 };
430
431 let sticky_size = Vec2::new(
432 self.columns[..self.num_sticky_cols]
433 .iter()
434 .map(|c| c.current)
435 .sum(),
436 self.headers.iter().map(|h| h.height).sum(),
437 );
438
439 let mut ui_builder = UiBuilder::new().layout(Layout::top_down(Align::Min));
440 if do_full_sizing_pass {
441 ui_builder = ui_builder.sizing_pass().invisible();
442 ui.request_discard("Full egui_table sizing");
443 }
444 let response = ui
445 .scope_builder(ui_builder, |ui| {
446 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); let num_columns = self.columns.len();
450
451 for (col_nr, column) in self.columns.iter_mut().enumerate() {
452 if column.resizable {
453 let column_resize_id = id.with(column.id_for(col_nr)).with("resize");
454 if let Some(response) = ui.read_response(column_resize_id)
455 && response.double_clicked()
456 {
457 column.auto_size_this_frame = true;
458 }
459 }
460 if column.auto_size_this_frame {
461 ui.request_discard("egui_table column sizing");
462 }
463 }
464
465 SplitScroll {
466 scroll_enabled: Vec2b::new(true, true),
467 fixed_size: sticky_size,
468 scroll_outer_size: (ui.available_size() - sticky_size).at_least(Vec2::ZERO),
469 scroll_content_size: Vec2::new(
470 self.columns[self.num_sticky_cols..]
471 .iter()
472 .map(|c| c.current)
473 .sum(),
474 self.get_row_top_offset(ui, id, table_delegate, self.num_rows),
475 ),
476 stick_to_bottom: self.stick_to_bottom,
477 }
478 .show(
479 ui,
480 &mut TableSplitScrollDelegate {
481 id,
482 table_delegate,
483 state: &mut state,
484 table: &mut self,
485 col_x,
486 header_row_y,
487 max_column_widths: vec![0.0; num_columns],
488 visible_column_lines: Default::default(),
489 do_full_sizing_pass,
490 has_prefetched: false,
491 egui_ctx: ui.clone(),
492 },
493 );
494 })
495 .response;
496
497 state.store(ui, id);
498 response
499 }
500}
501
502#[derive(Clone, Copy, Debug)]
503struct ColumnResizer {
504 scroll_offset: Vec2,
505
506 top: f32,
507}
508
509fn update(map: &mut BTreeMap<usize, ColumnResizer>, key: usize, value: ColumnResizer) {
510 match map.entry(key) {
511 Entry::Vacant(entry) => {
512 entry.insert(value);
513 }
514 Entry::Occupied(mut entry) => {
515 entry.get_mut().top = entry.get_mut().top.min(value.top);
516 }
517 }
518}
519
520struct TableSplitScrollDelegate<'a> {
521 id: Id,
522 table_delegate: &'a mut dyn TableDelegate,
523 table: &'a mut Table,
524 state: &'a mut TableState,
525
526 col_x: Vec1<f32>,
528
529 header_row_y: Vec1<f32>,
531
532 max_column_widths: Vec<f32>,
534
535 visible_column_lines: BTreeMap<usize, ColumnResizer>,
537
538 do_full_sizing_pass: bool,
539
540 has_prefetched: bool,
541
542 egui_ctx: Context,
543}
544
545impl TableSplitScrollDelegate<'_> {
546 fn get_row_top_offset(&self, row_nr: u64) -> f32 {
548 self.table
549 .get_row_top_offset(&self.egui_ctx, self.id, self.table_delegate, row_nr)
550 }
551
552 fn get_row_nr_at_y_offset(&self, y_offset: f32) -> u64 {
554 self.table
555 .get_row_nr_at_y_offset(&self.egui_ctx, self.id, self.table_delegate, y_offset)
556 }
557
558 fn header_ui(&mut self, ui: &mut Ui, scroll_offset: Vec2) {
559 for (row_nr, header_row) in self.table.headers.iter().enumerate() {
560 let groups = if header_row.groups.is_empty() {
561 (0..self.table.columns.len()).map(|i| i..i + 1).collect()
562 } else {
563 header_row.groups.clone()
564 };
565
566 let y_range = Rangef::new(self.header_row_y[row_nr], self.header_row_y[row_nr + 1]);
567
568 for (group_index, col_range) in groups.into_iter().enumerate() {
569 let start = col_range.start;
570 let end = col_range.end;
571
572 let mut header_rect =
573 Rect::from_x_y_ranges(self.col_x[start]..=self.col_x[end], y_range)
574 .translate(-scroll_offset);
575
576 if 0 < start
577 && self.table.columns[start - 1].resizable
578 && ui.clip_rect().x_range().contains(header_rect.left())
579 {
580 update(
582 &mut self.visible_column_lines,
583 start - 1,
584 ColumnResizer {
585 scroll_offset,
586 top: header_rect.top(),
587 },
588 );
589 }
590
591 let clip_rect = header_rect;
592
593 let last_column = &self.table.columns[end - 1];
594 let auto_size_this_frame = last_column.auto_size_this_frame; if auto_size_this_frame {
597 header_rect.max.x = header_rect.min.x
599 + self.table.columns[start..end]
600 .iter()
601 .map(|column| column.range.min)
602 .sum::<f32>();
603 }
604
605 let mut ui_builder = UiBuilder::new()
606 .max_rect(header_rect)
607 .id_salt(("header", row_nr, group_index))
608 .layout(egui::Layout::left_to_right(egui::Align::Center));
609 if auto_size_this_frame {
610 ui_builder = ui_builder.sizing_pass();
611 }
612 let mut cell_ui = ui.new_child(ui_builder);
613 cell_ui.shrink_clip_rect(clip_rect);
614
615 self.table_delegate.header_cell_ui(
616 &mut cell_ui,
617 &HeaderCellInfo {
618 group_index,
619 col_range,
620 row_nr,
621 table_id: self.id,
622 },
623 );
624
625 if start + 1 == end {
626 let col_nr = start;
628 let column = &self.table.columns[start];
629 let width = &mut self.max_column_widths[col_nr];
630 *width = width.max(cell_ui.min_size().x);
631
632 if column.resizable && ui.clip_rect().x_range().contains(header_rect.right()) {
634 update(
635 &mut self.visible_column_lines,
636 col_nr,
637 ColumnResizer {
638 scroll_offset,
639 top: header_rect.top(),
640 },
641 );
642 }
643 }
644 }
645 }
646 }
647
648 fn region_ui(&mut self, ui: &mut Ui, scroll_offset: Vec2, do_prefetch: bool) {
649 let viewport = ui.clip_rect().translate(scroll_offset);
651
652 let col_range = if self.table.columns.is_empty() || viewport.left() == viewport.right() {
653 0..0
654 } else if self.do_full_sizing_pass {
655 0..self.table.columns.len()
657 } else {
658 let col_idx_at = |x: f32| -> usize {
660 self.col_x
661 .partition_point(|&col_x| col_x < x)
662 .saturating_sub(1)
663 .at_most(self.table.columns.len() - 1)
664 };
665
666 col_idx_at(viewport.min.x)..col_idx_at(viewport.max.x) + 1
667 };
668
669 let row_range = if self.table.num_rows == 0 || viewport.top() == viewport.bottom() {
670 0..0
671 } else {
672 let row_idx_at = |y: f32| -> u64 {
674 let row_nr = self.get_row_nr_at_y_offset(y - self.header_row_y.last());
675 row_nr.at_most(self.table.num_rows.saturating_sub(1))
676 };
677
678 let margin = if do_prefetch {
679 1.0 } else {
681 0.0
682 };
683
684 row_idx_at(viewport.min.y - margin)..row_idx_at(viewport.max.y + margin) + 1
685 };
686
687 if do_prefetch {
688 self.table_delegate.prepare(&PrefetchInfo {
689 num_sticky_columns: self.table.num_sticky_cols,
690 visible_columns: col_range.clone(),
691 visible_rows: row_range.clone(),
692 table_id: self.id,
693 });
694 self.has_prefetched = true;
695 } else {
696 debug_assert!(
697 self.has_prefetched,
698 "SplitScroll delegate methods called in unexpected order"
699 );
700 }
701
702 for row_nr in row_range {
703 let y_range = Rangef::new(
704 self.header_row_y.last() + self.get_row_top_offset(row_nr),
705 self.header_row_y.last() + self.get_row_top_offset(row_nr + 1),
706 );
707
708 let row_x_range = self.col_x[0]..=self.col_x[self.col_x.len() - 1];
709 let row_rect = Rect::from_x_y_ranges(row_x_range, y_range).translate(-scroll_offset);
710
711 let mut row_ui = ui.new_child(
712 UiBuilder::new()
713 .max_rect(row_rect)
714 .id_salt(("row", row_nr))
715 .layout(egui::Layout::left_to_right(egui::Align::Center)),
716 );
717 row_ui.set_min_size(row_rect.size());
718
719 self.table_delegate.row_ui(&mut row_ui, row_nr);
720
721 for col_nr in col_range.clone() {
722 let column = &self.table.columns[col_nr];
723 let mut cell_rect =
724 Rect::from_x_y_ranges(self.col_x[col_nr]..=self.col_x[col_nr + 1], y_range)
725 .translate(-scroll_offset);
726 let clip_rect = cell_rect;
727 if column.auto_size_this_frame {
728 cell_rect.max.x = cell_rect.min.x + column.range.min;
730 }
731
732 let mut ui_builder = UiBuilder::new()
733 .max_rect(cell_rect)
734 .id_salt((row_nr, col_nr))
735 .layout(egui::Layout::left_to_right(egui::Align::Center));
736 if column.auto_size_this_frame {
737 ui_builder = ui_builder.sizing_pass();
738 }
739 let mut cell_ui = row_ui.new_child(ui_builder);
740 cell_ui.shrink_clip_rect(clip_rect);
741
742 self.table_delegate.cell_ui(
743 &mut cell_ui,
744 &CellInfo {
745 col_nr,
746 row_nr,
747 table_id: self.id,
748 },
749 );
750
751 let width = &mut self.max_column_widths[col_nr];
752 *width = width.max(cell_ui.min_size().x);
753 }
754 }
755
756 for col_nr in col_range.clone() {
758 let column = &self.table.columns[col_nr];
759 if column.resizable {
760 update(
761 &mut self.visible_column_lines,
762 col_nr,
763 ColumnResizer {
764 scroll_offset,
765 top: *self.header_row_y.last(),
766 },
767 );
768 }
769 }
770 }
771}
772
773impl SplitScrollDelegate for TableSplitScrollDelegate<'_> {
774 fn right_bottom_ui(&mut self, ui: &mut Ui) {
776 if self.table.scroll_to_columns.is_some() || self.table.scroll_to_rows.is_some() {
777 let mut target_rect = ui.clip_rect(); let mut target_align = None;
779
780 if let Some((column_range, align)) = &self.table.scroll_to_columns {
781 let scrollable_col_x_base = self.col_x[self.table.num_sticky_cols];
785 let x_from_column_nr = |col_nr: usize| -> f32 {
786 ui.min_rect().left() + (self.col_x[col_nr] - scrollable_col_x_base)
787 };
788
789 let sticky_width = scrollable_col_x_base - self.col_x.first();
790
791 target_rect.min.x = x_from_column_nr(*column_range.start()) - sticky_width;
795 target_rect.max.x = x_from_column_nr(*column_range.end() + 1);
796 target_align = target_align.or(*align);
797 }
798
799 if let Some((row_range, align)) = &self.table.scroll_to_rows {
800 let y_from_row_nr =
801 |row_nr: u64| -> f32 { ui.min_rect().top() + self.get_row_top_offset(row_nr) };
802
803 let sticky_height = self.header_row_y.last() - self.header_row_y.first();
804
805 target_rect.min.y = y_from_row_nr(*row_range.start()) - sticky_height;
809 target_rect.max.y = y_from_row_nr(*row_range.end() + 1);
810 target_align = target_align.or(*align);
811 }
812
813 ui.scroll_to_rect(target_rect, target_align);
814 }
815
816 let scroll_offset = ui.clip_rect().min - ui.min_rect().min;
817 self.region_ui(ui, scroll_offset, true);
818 }
819
820 fn left_top_ui(&mut self, ui: &mut Ui) {
821 self.header_ui(ui, Vec2::ZERO);
822 }
823
824 fn right_top_ui(&mut self, ui: &mut Ui) {
825 let scroll_offset = vec2(ui.clip_rect().min.x - ui.min_rect().min.x, 0.0);
826 self.header_ui(ui, scroll_offset);
827 }
828
829 fn left_bottom_ui(&mut self, ui: &mut Ui) {
830 self.region_ui(
831 ui,
832 vec2(0.0, ui.clip_rect().min.y - ui.min_rect().min.y),
833 false,
834 );
835 }
836
837 fn finish(&mut self, ui: &mut Ui) {
838 for (col_nr, ColumnResizer { scroll_offset, top }) in &self.visible_column_lines {
841 let col_nr = *col_nr;
842 let Some(column) = self.table.columns.get(col_nr) else {
843 continue;
844 };
845 if !column.resizable {
846 continue;
847 }
848
849 let column_id = column.id_for(col_nr);
850 let used_width = column.range.clamp(self.max_column_widths[col_nr]);
851
852 let column_width = self
853 .state
854 .col_widths
855 .entry(column_id)
856 .or_insert(column.current);
857
858 let layout_width = *column_width; if ui.is_sizing_pass() || column.auto_size_this_frame {
861 *column_width = used_width;
863 } else {
864 *column_width = column_width.max(used_width);
866 }
867
868 let column_resize_id = self.id.with(column.id_for(col_nr)).with("resize");
869
870 let mut x = self.col_x[col_nr + 1] - scroll_offset.x + (*column_width - layout_width);
872 let yrange = Rangef::new(*top, ui.clip_rect().bottom());
873 let line_rect = egui::Rect::from_x_y_ranges(x..=x, yrange)
874 .expand(ui.style().interaction.resize_grab_radius_side);
875
876 let resize_response =
877 ui.interact(line_rect, column_resize_id, egui::Sense::click_and_drag());
878
879 if resize_response.dragged()
880 && let Some(pointer) = ui.pointer_latest_pos()
881 {
882 let new_width = *column_width + pointer.x - x;
886 let new_width = column.range.clamp(new_width);
887 x += new_width - *column_width;
888 *column_width = new_width;
889 }
890
891 let dragging_something_else =
892 ui.input(|i| i.pointer.any_down() || i.pointer.any_pressed());
893 let resize_hover = resize_response.hovered() && !dragging_something_else;
894
895 if resize_hover || resize_response.dragged() {
896 ui.set_cursor_icon(egui::CursorIcon::ResizeColumn);
897 }
898
899 let stroke = if resize_response.dragged() {
900 ui.style().visuals.widgets.active.bg_stroke
901 } else if resize_hover {
902 ui.style().visuals.widgets.hovered.bg_stroke
903 } else {
904 ui.visuals().widgets.noninteractive.bg_stroke
906 };
907
908 ui.painter().vline(x, yrange, stroke);
909 }
910 }
911}
912
913fn partition_point(range: RangeInclusive<u64>, second_partition: impl Fn(u64) -> bool) -> u64 {
915 let mut min = *range.start();
916 let mut max = *range.end();
917
918 debug_assert!(min < max, "Bad call to partition_point");
919
920 while min < max {
921 let mid = min + (max - min) / 2;
922
923 if second_partition(mid) {
924 max = mid;
925 } else {
926 min = mid + 1;
927 }
928 }
929
930 min
931}
932
933#[cfg(test)]
934mod tests {
935 use crate::table::partition_point;
936
937 #[test]
938 fn test_partition_point() {
939 assert_eq!(partition_point(0..=17, |i| 8 <= i), 8);
940 assert_eq!(partition_point(0..=17, |i| 9 <= i), 9);
941 assert_eq!(partition_point(10..=17, |_| true), 10);
942 assert_eq!(partition_point(10..=17, |_| false), 17);
943 }
944}