Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add table.scroll_to_row(s) and scroll_to_column(s) #9

Merged
merged 3 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions demo/src/table_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,37 @@ impl TableDemo {
self.prefetched.clear();
});

let mut scroll_to_column = None;
ui.horizontal(|ui| {
ui.label("Scroll horizontally to…");
if ui.button("left").clicked() {
scroll_to_column = Some(0);
}
if ui.button("middle").clicked() {
scroll_to_column = Some(self.num_columns / 2);
}
if ui.button("right").clicked() {
scroll_to_column = Some(self.num_columns.saturating_sub(1));
}
});

let mut scroll_to_row = None;
ui.horizontal(|ui| {
ui.label("Scroll vertically to…");
if ui.button("top").clicked() {
scroll_to_row = Some(0);
}
if ui.button("middle").clicked() {
scroll_to_row = Some(self.num_rows / 2);
}
if ui.button("bottom").clicked() {
scroll_to_row = Some(self.num_rows.saturating_sub(1));
}
});

ui.separator();

egui_table::Table::new()
let mut table = egui_table::Table::new()
.id_salt(id_salt)
.num_rows(self.num_rows)
.columns(vec![self.default_column; self.num_columns])
Expand All @@ -261,7 +289,15 @@ impl TableDemo {
egui_table::HeaderRow::new(self.top_row_height),
])
.row_height(self.row_height)
.auto_size_mode(self.auto_size_mode)
.show(ui, self);
.auto_size_mode(self.auto_size_mode);

if let Some(scroll_to_column) = scroll_to_column {
table = table.scroll_to_column(scroll_to_column, None);
}
if let Some(scroll_to_row) = scroll_to_row {
table = table.scroll_to_row(scroll_to_row, None);
}

table.show(ui, self);
}
}
5 changes: 3 additions & 2 deletions egui_table/src/split_scroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ pub struct SplitScroll {

/// The contents of a [`SplitScroll`].
pub trait SplitScrollDelegate {
// TODO: add method for prefetching the visible viewport.

/// The fixed portion of the top left corner.
fn left_top_ui(&mut self, ui: &mut Ui);

Expand All @@ -51,6 +49,8 @@ pub trait SplitScrollDelegate {
fn left_bottom_ui(&mut self, ui: &mut Ui);

/// The fully scrollable portion.
///
/// First to be called.
fn right_bottom_ui(&mut self, ui: &mut Ui);

/// Called last.
Expand Down Expand Up @@ -83,6 +83,7 @@ impl SplitScroll {
// We could add something like `ScrollArea::with_scroll_bar_rect(bottom_right_rect)`

let mut scroll_ui = ui.new_child(UiBuilder::new().max_rect(rect));

egui::ScrollArea::new(scroll_enabled)
.auto_shrink(false)
.scroll_bar_rect(bottom_right_rect)
Expand Down
113 changes: 105 additions & 8 deletions egui_table/src/table.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{
collections::{btree_map::Entry, BTreeMap},
ops::Range,
ops::{Range, RangeInclusive},
};

use egui::{vec2, Id, IdMap, NumExt as _, Rangef, Rect, Ui, UiBuilder, Vec2, Vec2b};
use egui::{vec2, Align, Id, IdMap, NumExt as _, Rangef, Rect, Ui, UiBuilder, Vec2, Vec2b};
use vec1::Vec1;

use crate::{columns::Column, SplitScroll, SplitScrollDelegate};
Expand Down Expand Up @@ -113,18 +113,23 @@ pub struct Table {

/// How to do auto-sizing of columns, if at all.
auto_size_mode: AutoSizeMode,

scroll_to_columns: Option<(RangeInclusive<usize>, Option<Align>)>,
scroll_to_rows: Option<(RangeInclusive<u64>, Option<Align>)>,
}

impl Default for Table {
fn default() -> Self {
Self {
columns: vec![],
id_salt: Id::new("table"),
num_sticky_cols: 1,
num_sticky_cols: 0,
headers: vec![HeaderRow::new(16.0)],
row_height: 16.0,
num_rows: 0,
auto_size_mode: AutoSizeMode::default(),
scroll_to_columns: None,
scroll_to_rows: None,
}
}
}
Expand Down Expand Up @@ -210,7 +215,9 @@ impl Table {
self
}

/// Which columns are sticky (non-scrolling)?
/// How many columns are sticky (non-scrolling)?
///
/// Default is 0.
#[inline]
pub fn num_sticky_cols(mut self, num_sticky_cols: usize) -> Self {
self.num_sticky_cols = num_sticky_cols;
Expand Down Expand Up @@ -245,6 +252,52 @@ impl Table {
TableState::id(ui, self.id_salt)
}

/// Set a row to scroll to.
///
/// `align` specifies if the row should be positioned in the top, center, or bottom of the view
/// (using [`Align::TOP`], [`Align::Center`] or [`Align::BOTTOM`]).
/// If `align` is `None`, the table will scroll just enough to bring the cursor into view.
///
/// See also: [`Self::scroll_to_column`].
#[inline]
pub fn scroll_to_row(self, row: u64, align: Option<Align>) -> Self {
self.scroll_to_rows(row..=row, align)
}

/// Scroll to a range of rows.
///
/// See [`Self::scroll_to_row`] for details.
#[inline]
pub fn scroll_to_rows(mut self, rows: RangeInclusive<u64>, align: Option<Align>) -> Self {
self.scroll_to_rows = Some((rows, align));
self
}

/// Set a column to scroll to.
///
/// `align` specifies if the column should be positioned in the left, center, or right of the view
/// (using [`Align::LEFT`], [`Align::Center`] or [`Align::RIGHT`]).
/// If `align` is `None`, the table will scroll just enough to bring the cursor into view.
///
/// See also: [`Self::scroll_to_row`].
#[inline]
pub fn scroll_to_column(self, column: usize, align: Option<Align>) -> Self {
self.scroll_to_columns(column..=column, align)
}

/// Scroll to a range of columns.
///
/// See [`Self::scroll_to_column`] for details.
#[inline]
pub fn scroll_to_columns(
mut self,
columns: RangeInclusive<usize>,
align: Option<Align>,
) -> Self {
self.scroll_to_columns = Some((columns, align));
self
}

pub fn show(mut self, ui: &mut Ui, table_delegate: &mut dyn TableDelegate) {
self.num_sticky_cols = self.num_sticky_cols.at_most(self.columns.len());

Expand Down Expand Up @@ -595,6 +648,54 @@ impl<'a> TableSplitScrollDelegate<'a> {
}

impl<'a> SplitScrollDelegate for TableSplitScrollDelegate<'a> {
// First to be called
fn right_bottom_ui(&mut self, ui: &mut Ui) {
if self.table.scroll_to_columns.is_some() || self.table.scroll_to_rows.is_some() {
let mut target_rect = ui.clip_rect(); // no scrolling
let mut target_align = None;

if let Some((column_range, align)) = &self.table.scroll_to_columns {
let x_from_column_nr = |col_nr: usize| -> f32 {
let mut x = self.col_x[col_nr];

let sitcky_width = self.col_x[self.table.num_sticky_cols] - self.col_x.first();
if x < sitcky_width {
// We need to do some shenanigans here because how the `SplitScroll` works:
x -= sitcky_width;
}

ui.min_rect().left() + x
};

target_rect.min.x = x_from_column_nr(*column_range.start());
target_rect.max.x = x_from_column_nr(*column_range.end() + 1);
target_align = target_align.or(*align);
}

if let Some((row_range, align)) = &self.table.scroll_to_rows {
let y_from_row_nr = |row_nr: u64| -> f32 {
let mut y = row_nr as f32 * self.table.row_height;

let sticky_height = self.header_row_y.last() - self.header_row_y.first();
if y < sticky_height {
// We need to do some shenanigans here because how the `SplitScroll` works:
y -= sticky_height;
}

ui.min_rect().top() + y
};

target_rect.min.y = y_from_row_nr(*row_range.start());
target_rect.max.y = y_from_row_nr(*row_range.end() + 1);
target_align = target_align.or(*align);
}

ui.scroll_to_rect(target_rect, target_align);
}

self.region_ui(ui, ui.clip_rect().min - ui.min_rect().min, true);
}

fn left_top_ui(&mut self, ui: &mut Ui) {
self.header_ui(ui, Vec2::ZERO);
}
Expand All @@ -611,10 +712,6 @@ impl<'a> SplitScrollDelegate for TableSplitScrollDelegate<'a> {
);
}

fn right_bottom_ui(&mut self, ui: &mut Ui) {
self.region_ui(ui, ui.clip_rect().min - ui.min_rect().min, true);
}

fn finish(&mut self, ui: &mut Ui) {
// Paint column resize lines

Expand Down
Loading