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

Grid: Row/column colors #364

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 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
225 changes: 205 additions & 20 deletions egui/src/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ pub(crate) struct State {
row_heights: Vec<f32>,
}

/// Describe the dimensions of a grid state
#[derive(Clone, Copy, Debug)]
pub(crate) struct Dimensions {
x: usize,
y: usize,
}

skairunner marked this conversation as resolved.
Show resolved Hide resolved
impl State {
fn set_min_col_width(&mut self, col: usize, width: f32) {
self.col_widths
Expand All @@ -20,6 +27,13 @@ impl State {
self.row_heights[row] = self.row_heights[row].max(height);
}

fn dimensions(&self) -> Dimensions {
Dimensions {
x: self.row_heights.len(),
y: self.col_widths.len(),
}
}

fn col_width(&self, col: usize) -> Option<f32> {
self.col_widths.get(col).copied()
}
Expand All @@ -32,6 +46,61 @@ impl State {
self.col_widths.iter().sum::<f32>()
+ (self.col_widths.len().at_least(1) - 1) as f32 * x_spacing
}

fn full_height(&self, y_spacing: f32) -> f32 {
self.row_heights.iter().sum::<f32>()
+ (self.row_heights.len().at_least(1) - 1) as f32 * y_spacing
}
}

// ----------------------------------------------------------------------------

/// Either a single color, or a pair of colors corresponding to light/dark scheme
#[derive(Clone, Copy)]
pub enum GuiColor {
Single(Rgba),
LightDark { light: Rgba, dark: Rgba },
}

impl From<Rgba> for GuiColor {
fn from(color: Rgba) -> Self {
Self::Single(color)
}
}

impl GuiColor {
/// Create a single color
pub fn single(color: Rgba) -> Self {
Self::Single(color)
}

/// Create a light/dark scheme pair
pub fn light_dark(light: Rgba, dark: Rgba) -> Self {
Self::LightDark { light, dark }
}

/// Select a color appropriate to the referenced Style.
pub fn pick(&self, style: &Style) -> Rgba {
match &self {
Self::Single(color) => *color,
Self::LightDark { light, dark } => match style.visuals.dark_mode {
true => *dark,
false => *light,
},
}
}
}

/// Provided either a row or column number, return a color to paint for the background.
/// Returning None means painting is skipped.
pub type ColorPickerFn = fn(usize) -> Option<GuiColor>;

/// Describe conditional colors for a grid.
/// All predicates are evaluated for each row/col, and colors will be painted on top of each other.
#[derive(Default)]
pub(crate) struct ColorSpec {
pub row_pickers: Vec<ColorPickerFn>,
pub col_pickers: Vec<ColorPickerFn>,
}

// ----------------------------------------------------------------------------
Expand All @@ -49,7 +118,7 @@ pub(crate) struct GridLayout {

spacing: Vec2,

striped: bool,
color_spec: ColorSpec,
initial_x: f32,
min_cell_size: Vec2,
max_cell_size: Vec2,
Expand Down Expand Up @@ -77,7 +146,7 @@ impl GridLayout {
prev_state,
curr_state: State::default(),
spacing: ui.spacing().item_spacing,
striped: false,
color_spec: Default::default(),
initial_x,
min_cell_size: ui.spacing().interact_size,
max_cell_size: Vec2::INFINITY,
Expand Down Expand Up @@ -182,6 +251,72 @@ impl GridLayout {
cursor.min.x += frame_rect.width() + self.spacing.x;
}

/// Paint a row.
pub(crate) fn paint_row(&self, min: Pos2, color: GuiColor, painter: &Painter) {
let color = color.pick(&self.style);
if let Some(height) = self.prev_state.row_height(self.row) {
// Paint background for coming row:
let size = Vec2::new(self.prev_state.full_width(self.spacing.x), height);
let rect = Rect::from_min_size(min, size);
let mut rect = rect.expand2(Vec2::new(2.0, 0.0));
rect.max += Vec2::new(1.0, 0.5 * self.spacing.y + 1.5);

painter.rect_filled(rect, 2.0, color);
}
}

/// Paint a column
pub(crate) fn paint_column(&self, col: usize, min: Pos2, color: GuiColor, painter: &Painter) {
let col_f = col as f32;

let color = color.pick(&self.style);

// Paint a column:
// Offset from the cursor to paint the col at the right spot.
let min_offset = min
+ Vec2::new(
// Sum up all the previous widths and add the padding
self.prev_state.col_widths.iter().take(col).sum::<f32>()
+ (self.spacing.x + 1.0) * (col_f - 1.0),
-1.0,
);
let size = Vec2::new(
self.prev_col_width(col),
self.prev_state.full_height(self.spacing.y),
);
let size = size
+ Vec2::new(
// Add padding
0.5 * self.spacing.x + 5.0,
3.0,
);

let rect = Rect::from_min_size(min_offset, size);

painter.rect_filled(rect, 2.0, color);
}

/// Paint the background for the next row & columns.
pub(crate) fn do_paint(&mut self, min: Pos2, painter: &Painter) {
skairunner marked this conversation as resolved.
Show resolved Hide resolved
for p in &self.color_spec.row_pickers {
if let Some(color) = p(self.row) {
self.paint_row(min, color, painter)
}
}
// Only paint columns when we're on the first row.
// They are painted for the entire grid.
if self.row == 0 {
for p in &self.color_spec.col_pickers {
// Iterate over columns
for col in 0..self.prev_state.dimensions().y {
if let Some(color) = p(col) {
self.paint_column(col, min, color, painter);
}
}
skairunner marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

pub(crate) fn end_row(&mut self, cursor: &mut Rect, painter: &Painter) {
let row_height = self.prev_row_height(self.row);

Expand All @@ -190,22 +325,7 @@ impl GridLayout {
self.col = 0;
self.row += 1;

if self.striped && self.row % 2 == 1 {
if let Some(height) = self.prev_state.row_height(self.row) {
// Paint background for coming row:
let size = Vec2::new(self.prev_state.full_width(self.spacing.x), height);
let rect = Rect::from_min_size(cursor.min, size);
let rect = rect.expand2(0.5 * self.spacing.y * Vec2::Y);
let rect = rect.expand2(2.0 * Vec2::X); // HACK: just looks better with some spacing on the sides

let color = if self.style.visuals.dark_mode {
Rgba::from_white_alpha(0.0075)
} else {
Rgba::from_black_alpha(0.075)
};
painter.rect_filled(rect, 2.0, color);
}
}
self.do_paint(cursor.min, painter);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do_paint checks if row == 0 which it never is here. Better to just call paint_row here to avoid confusion:

Suggested change
self.do_paint(cursor.min, painter);
self.paint_row(cursor, painter);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paint_row only paints a single row. The paint method iterates through all the row pickers and paints for every case that fits. If you prefer, I can break paint into paint_rows and paint_cols, but I personally feel that it's okay to call a single paint method that handles everything.

}

pub(crate) fn save(&self) {
Expand Down Expand Up @@ -250,10 +370,12 @@ impl GridLayout {
pub struct Grid {
id_source: Id,
striped: bool,
header_row: bool,
min_col_width: Option<f32>,
min_row_height: Option<f32>,
max_cell_size: Vec2,
spacing: Option<Vec2>,
color_spec: ColorSpec,
start_row: usize,
}

Expand All @@ -263,14 +385,40 @@ impl Grid {
Self {
id_source: Id::new(id_source),
striped: false,
header_row: false,
min_col_width: None,
min_row_height: None,
max_cell_size: Vec2::INFINITY,
spacing: None,
color_spec: Default::default(),
start_row: 0,
}
}

/// Replace the color picker for columns
pub fn with_column_color(mut self, predicate: ColorPickerFn) -> Self {
self.color_spec.col_pickers = vec![predicate];
self
}

/// Add a `ColorPickerFn` to the color spec without overwriting existing column colors.
pub fn add_column_color(mut self, predicate: ColorPickerFn) -> Self {
self.color_spec.col_pickers.push(predicate);
self
}

/// Replace the color picker for rows
pub fn with_row_color(mut self, predicate: ColorPickerFn) -> Self {
skairunner marked this conversation as resolved.
Show resolved Hide resolved
self.color_spec.row_pickers = vec![predicate];
self
}

/// Add a `ColorPickerFn` to the color spec without overwriting existing row colors.
pub fn add_row_color(mut self, predicate: ColorPickerFn) -> Self {
self.color_spec.row_pickers.push(predicate);
self
}

/// If `true`, add a subtle background color to every other row.
///
/// This can make a table easier to read.
Expand All @@ -280,6 +428,11 @@ impl Grid {
self
}

pub fn header_row(mut self, header_row: bool) -> Self {
self.header_row = header_row;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just add to color_spec.row_pickers directly. Same for striped

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that if someone subsequently calls header_row(false), it's not trivial to figure out how to remove the added row coloration from the row_pickers, and also kinda weird to be comparing closures/fn pointers/whatever. Would appreciate thoughts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps header_row() can be a method with no arguments that, when called, irreversibly sets the header coloration, and same for stripes?

self
}

/// Set minimum width of each column.
/// Default: [`crate::style::Spacing::interact_size`]`.x`.
pub fn min_col_width(mut self, min_col_width: f32) -> Self {
Expand Down Expand Up @@ -320,10 +473,12 @@ impl Grid {
let Self {
id_source,
striped,
header_row,
min_col_width,
min_row_height,
max_cell_size,
spacing,
color_spec,
start_row,
} = self;
let min_col_width = min_col_width.unwrap_or_else(|| ui.spacing().interact_size.x);
Expand All @@ -336,16 +491,46 @@ impl Grid {
// which we do here:
ui.horizontal(|ui| {
let id = ui.make_persistent_id(id_source);
let grid = GridLayout {
striped,
let mut grid = GridLayout {
spacing,
min_cell_size: vec2(min_col_width, min_row_height),
max_cell_size,
color_spec,
row: start_row,
..GridLayout::new(ui, id)
};

// Set convenience color specs
if striped {
grid.color_spec.row_pickers.push(|row| {
if row % 2 == 0 {
Some(GuiColor::light_dark(
Rgba::from_black_alpha(0.075),
Rgba::from_white_alpha(0.0075),
))
} else {
None
}
});
}

if header_row {
grid.color_spec.row_pickers.push(|row| {
if row == 0 {
Some(GuiColor::light_dark(
Rgba::from_black_alpha(0.75),
Rgba::from_white_alpha(0.075),
))
} else {
None
}
});
}

// ---

ui.set_grid(grid);
ui.start_row();
skairunner marked this conversation as resolved.
Show resolved Hide resolved
let r = add_contents(ui);
ui.save_grid();
r
Expand Down
6 changes: 6 additions & 0 deletions egui/src/placer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ impl Placer {
self.region.expand_to_include_rect(frame_rect); // e.g. for centered layouts: pretend we used whole frame
}

pub(crate) fn start_row(&mut self, painter: &Painter) {
if let Some(grid) = &mut self.grid {
grid.do_paint(self.region.cursor.min, painter)
}
}

skairunner marked this conversation as resolved.
Show resolved Hide resolved
/// Move to the next row in a grid layout or wrapping layout.
/// Otherwise does nothing.
pub(crate) fn end_row(&mut self, item_spacing: Vec2, painter: &Painter) {
Expand Down
4 changes: 4 additions & 0 deletions egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,10 @@ impl Ui {
.end_row(self.spacing().item_spacing, &self.painter().clone());
}

pub(crate) fn start_row(&mut self) {
self.placer.start_row(&self.painter().clone());
}

skairunner marked this conversation as resolved.
Show resolved Hide resolved
/// Set row height in horizontal wrapping layout.
pub fn set_row_height(&mut self, height: f32) {
self.placer.set_row_height(height);
Expand Down
2 changes: 1 addition & 1 deletion egui_demo_lib/src/apps/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl Default for Tests {
Box::new(super::tests::InputTest::default()),
Box::new(super::layout_test::LayoutTest::default()),
Box::new(super::tests::ManualLayoutTest::default()),
Box::new(super::tests::TableTest::default()),
Box::new(super::tests::GridTest::default()),
skairunner marked this conversation as resolved.
Show resolved Hide resolved
])
}
}
Expand Down
Loading