diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 6523805bab8..edb0fec3f20 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -12,9 +12,12 @@ pub struct TableDemo { demo: DemoType, striped: bool, resizable: bool, + clickable: bool, num_rows: usize, scroll_to_row_slider: usize, scroll_to_row: Option, + selection: std::collections::HashSet, + checked: bool, } impl Default for TableDemo { @@ -23,9 +26,12 @@ impl Default for TableDemo { demo: DemoType::Manual, striped: true, resizable: true, + clickable: true, num_rows: 10_000, scroll_to_row_slider: 0, scroll_to_row: None, + selection: Default::default(), + checked: false, } } } @@ -54,6 +60,7 @@ impl super::View for TableDemo { ui.horizontal(|ui| { ui.checkbox(&mut self.striped, "Striped"); ui.checkbox(&mut self.resizable, "Resizable columns"); + ui.checkbox(&mut self.clickable, "Clickable rows"); }); ui.label("Table type:"); @@ -121,20 +128,28 @@ impl TableDemo { fn table_ui(&mut self, ui: &mut egui::Ui) { use egui_extras::{Column, TableBuilder}; - let text_height = egui::TextStyle::Body.resolve(ui.style()).size; + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); let mut table = TableBuilder::new(ui) .striped(self.striped) .resizable(self.resizable) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto()) + .column(Column::auto()) .column(Column::initial(100.0).range(40.0..=300.0)) .column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::remainder()) .min_scrolled_height(0.0); - if let Some(row_nr) = self.scroll_to_row.take() { - table = table.scroll_to_row(row_nr, None); + if self.clickable { + table = table.sense(egui::Sense::click()); + } + + if let Some(row_index) = self.scroll_to_row.take() { + table = table.scroll_to_row(row_index, None); } table @@ -142,6 +157,9 @@ impl TableDemo { header.col(|ui| { ui.strong("Row"); }); + header.col(|ui| { + ui.strong("Interaction"); + }); header.col(|ui| { ui.strong("Expanding content"); }); @@ -158,9 +176,14 @@ impl TableDemo { let is_thick = thick_row(row_index); let row_height = if is_thick { 30.0 } else { 18.0 }; body.row(row_height, |mut row| { + row.set_selected(self.selection.contains(&row_index)); + row.col(|ui| { ui.label(row_index.to_string()); }); + row.col(|ui| { + ui.checkbox(&mut self.checked, "Click me"); + }); row.col(|ui| { expanding_content(ui); }); @@ -175,14 +198,22 @@ impl TableDemo { ui.label("Normal row"); } }); + + self.toggle_row_selection(row_index, &row.response()); }); } } DemoType::ManyHomogeneous => { - body.rows(text_height, self.num_rows, |row_index, mut row| { + body.rows(text_height, self.num_rows, |mut row| { + let row_index = row.index(); + row.set_selected(self.selection.contains(&row_index)); + row.col(|ui| { ui.label(row_index.to_string()); }); + row.col(|ui| { + ui.checkbox(&mut self.checked, "Click me"); + }); row.col(|ui| { expanding_content(ui); }); @@ -194,41 +225,52 @@ impl TableDemo { egui::Label::new("Thousands of rows of even height").wrap(false), ); }); + + self.toggle_row_selection(row_index, &row.response()); }); } DemoType::ManyHeterogenous => { - fn row_thickness(row_index: usize) -> f32 { - if thick_row(row_index) { - 30.0 - } else { - 18.0 - } - } - body.heterogeneous_rows( - (0..self.num_rows).map(row_thickness), - |row_index, mut row| { - row.col(|ui| { - ui.label(row_index.to_string()); - }); - row.col(|ui| { - expanding_content(ui); - }); - row.col(|ui| { - ui.label(long_text(row_index)); - }); - row.col(|ui| { - ui.style_mut().wrap = Some(false); - if thick_row(row_index) { - ui.heading("Extra thick row"); - } else { - ui.label("Normal row"); - } - }); - }, - ); + let row_height = |i: usize| if thick_row(i) { 30.0 } else { 18.0 }; + body.heterogeneous_rows((0..self.num_rows).map(row_height), |mut row| { + let row_index = row.index(); + row.set_selected(self.selection.contains(&row_index)); + + row.col(|ui| { + ui.label(row_index.to_string()); + }); + row.col(|ui| { + ui.checkbox(&mut self.checked, "Click me"); + }); + row.col(|ui| { + expanding_content(ui); + }); + row.col(|ui| { + ui.label(long_text(row_index)); + }); + row.col(|ui| { + ui.style_mut().wrap = Some(false); + if thick_row(row_index) { + ui.heading("Extra thick row"); + } else { + ui.label("Normal row"); + } + }); + + self.toggle_row_selection(row_index, &row.response()); + }); } }); } + + fn toggle_row_selection(&mut self, row_index: usize, row_response: &egui::Response) { + if row_response.clicked() { + if self.selection.contains(&row_index) { + self.selection.remove(&row_index); + } else { + self.selection.insert(row_index); + } + } + } } fn expanding_content(ui: &mut egui::Ui) { diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index fbd15e7add9..e2bfc7b353a 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -26,6 +26,15 @@ pub(crate) enum CellDirection { Vertical, } +/// Flags used by [`StripLayout::add`]. +#[derive(Clone, Copy, Default)] +pub(crate) struct StripLayoutFlags { + pub(crate) clip: bool, + pub(crate) striped: bool, + pub(crate) hovered: bool, + pub(crate) selected: bool, +} + /// Positions cells in [`CellDirection`] and starts a new line on [`StripLayout::end_line`] pub struct StripLayout<'l> { pub(crate) ui: &'l mut Ui, @@ -38,10 +47,16 @@ pub struct StripLayout<'l> { max: Pos2, cell_layout: egui::Layout, + sense: Sense, } impl<'l> StripLayout<'l> { - pub(crate) fn new(ui: &'l mut Ui, direction: CellDirection, cell_layout: egui::Layout) -> Self { + pub(crate) fn new( + ui: &'l mut Ui, + direction: CellDirection, + cell_layout: egui::Layout, + sense: Sense, + ) -> Self { let rect = ui.available_rect_before_wrap(); let pos = rect.left_top(); @@ -52,6 +67,7 @@ impl<'l> StripLayout<'l> { cursor: pos, max: pos, cell_layout, + sense, } } @@ -94,34 +110,53 @@ impl<'l> StripLayout<'l> { /// Return the used space (`min_rect`) plus the [`Response`] of the whole cell. pub(crate) fn add( &mut self, - clip: bool, - striped: bool, + flags: StripLayoutFlags, width: CellSize, height: CellSize, add_cell_contents: impl FnOnce(&mut Ui), ) -> (Rect, Response) { let max_rect = self.cell_rect(&width, &height); - if striped { - // Make sure we don't have a gap in the stripe background: - let stripe_rect = max_rect.expand2(0.5 * self.ui.spacing().item_spacing); + // Make sure we don't have a gap in the stripe/frame/selection background: + let item_spacing = self.ui.spacing().item_spacing; + let gapless_rect = max_rect.expand2(0.5 * item_spacing); + + if flags.striped { + self.ui.painter().rect_filled( + gapless_rect, + egui::Rounding::ZERO, + self.ui.visuals().faint_bg_color, + ); + } + + if flags.selected { + self.ui.painter().rect_filled( + gapless_rect, + egui::Rounding::ZERO, + self.ui.visuals().selection.bg_fill, + ); + } - self.ui - .painter() - .rect_filled(stripe_rect, 0.0, self.ui.visuals().faint_bg_color); + if flags.hovered && !flags.selected && self.sense.interactive() { + self.ui.painter().rect_filled( + gapless_rect, + egui::Rounding::ZERO, + self.ui.visuals().widgets.hovered.bg_fill, + ); } - let used_rect = self.cell(clip, max_rect, add_cell_contents); + let response = self.ui.allocate_rect(max_rect, self.sense); + let used_rect = self.cell(flags, max_rect, add_cell_contents); self.set_pos(max_rect); - let allocation_rect = if clip { + let allocation_rect = if flags.clip { max_rect } else { max_rect.union(used_rect) }; - let response = self.ui.allocate_rect(allocation_rect, Sense::hover()); + let response = response.with_new_rect(allocation_rect); (used_rect, response) } @@ -148,10 +183,15 @@ impl<'l> StripLayout<'l> { self.ui.allocate_rect(rect, Sense::hover()); } - fn cell(&mut self, clip: bool, rect: Rect, add_cell_contents: impl FnOnce(&mut Ui)) -> Rect { + fn cell( + &mut self, + flags: StripLayoutFlags, + rect: Rect, + add_cell_contents: impl FnOnce(&mut Ui), + ) -> Rect { let mut child_ui = self.ui.child_ui(rect, self.cell_layout); - if clip { + if flags.clip { let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin); let margin = margin.min(0.5 * self.ui.spacing().item_spacing); let clip_rect = rect.expand2(margin); diff --git a/crates/egui_extras/src/strip.rs b/crates/egui_extras/src/strip.rs index 71b30f7bc9e..67903d1cb79 100644 --- a/crates/egui_extras/src/strip.rs +++ b/crates/egui_extras/src/strip.rs @@ -1,5 +1,5 @@ use crate::{ - layout::{CellDirection, CellSize, StripLayout}, + layout::{CellDirection, CellSize, StripLayout, StripLayoutFlags}, sizing::Sizing, Size, }; @@ -46,6 +46,7 @@ pub struct StripBuilder<'a> { sizing: Sizing, clip: bool, cell_layout: egui::Layout, + sense: egui::Sense, } impl<'a> StripBuilder<'a> { @@ -55,8 +56,9 @@ impl<'a> StripBuilder<'a> { Self { ui, sizing: Default::default(), - cell_layout, clip: false, + cell_layout, + sense: egui::Sense::hover(), } } @@ -74,6 +76,13 @@ impl<'a> StripBuilder<'a> { self } + /// What should strip cells sense for? Default: [`egui::Sense::hover()`]. + #[inline] + pub fn sense(mut self, sense: egui::Sense) -> Self { + self.sense = sense; + self + } + /// Allocate space for one column/row. #[inline] pub fn size(mut self, size: Size) -> Self { @@ -102,7 +111,12 @@ impl<'a> StripBuilder<'a> { self.ui.available_rect_before_wrap().width(), self.ui.spacing().item_spacing.x, ); - let mut layout = StripLayout::new(self.ui, CellDirection::Horizontal, self.cell_layout); + let mut layout = StripLayout::new( + self.ui, + CellDirection::Horizontal, + self.cell_layout, + self.sense, + ); strip(Strip { layout: &mut layout, direction: CellDirection::Horizontal, @@ -125,7 +139,12 @@ impl<'a> StripBuilder<'a> { self.ui.available_rect_before_wrap().height(), self.ui.spacing().item_spacing.y, ); - let mut layout = StripLayout::new(self.ui, CellDirection::Vertical, self.cell_layout); + let mut layout = StripLayout::new( + self.ui, + CellDirection::Vertical, + self.cell_layout, + self.sense, + ); strip(Strip { layout: &mut layout, direction: CellDirection::Vertical, @@ -171,9 +190,11 @@ impl<'a, 'b> Strip<'a, 'b> { #[cfg_attr(debug_assertions, track_caller)] pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) { let (width, height) = self.next_cell_size(); - let striped = false; - self.layout - .add(self.clip, striped, width, height, add_contents); + let flags = StripLayoutFlags { + clip: self.clip, + ..Default::default() + }; + self.layout.add(flags, width, height, add_contents); } /// Add an empty cell. diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 6659ed395ea..2e6655c706f 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -6,7 +6,7 @@ use egui::{Align, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b}; use crate::{ - layout::{CellDirection, CellSize}, + layout::{CellDirection, CellSize, StripLayoutFlags}, StripLayout, }; @@ -231,6 +231,7 @@ pub struct TableBuilder<'a> { resizable: bool, cell_layout: egui::Layout, scroll_options: TableScrollOptions, + sense: egui::Sense, } impl<'a> TableBuilder<'a> { @@ -243,6 +244,7 @@ impl<'a> TableBuilder<'a> { resizable: false, cell_layout, scroll_options: Default::default(), + sense: egui::Sense::hover(), } } @@ -255,6 +257,13 @@ impl<'a> TableBuilder<'a> { self } + /// What should table cells sense for? (default: [`egui::Sense::hover()`]). + #[inline] + pub fn sense(mut self, sense: egui::Sense) -> Self { + self.sense = sense; + self + } + /// Make the columns resizable by dragging. /// /// You can set this for individual columns with [`Column::resizable`]. @@ -398,6 +407,7 @@ impl<'a> TableBuilder<'a> { resizable, cell_layout, scroll_options, + sense, } = self; let striped = striped.unwrap_or(ui.visuals().striped); @@ -415,15 +425,20 @@ impl<'a> TableBuilder<'a> { // Hide first-frame-jitters when auto-sizing. ui.add_visible_ui(!first_frame_auto_size_columns, |ui| { - let mut layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout); + let mut layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense); + let mut response: Option = None; add_header_row(TableRow { layout: &mut layout, columns: &columns, widths: &state.column_widths, max_used_widths: &mut max_used_widths, + row_index: 0, col_index: 0, - striped: false, height, + striped: false, + hovered: false, + selected: false, + response: &mut response, }); layout.allocate_rect(); }); @@ -441,6 +456,7 @@ impl<'a> TableBuilder<'a> { striped, cell_layout, scroll_options, + sense, } } @@ -458,6 +474,7 @@ impl<'a> TableBuilder<'a> { resizable, cell_layout, scroll_options, + sense, } = self; let striped = striped.unwrap_or(ui.visuals().striped); @@ -486,6 +503,7 @@ impl<'a> TableBuilder<'a> { striped, cell_layout, scroll_options, + sense, } .body(add_body_contents); } @@ -546,6 +564,8 @@ pub struct Table<'a> { cell_layout: egui::Layout, scroll_options: TableScrollOptions, + + sense: egui::Sense, } impl<'a> Table<'a> { @@ -574,6 +594,7 @@ impl<'a> Table<'a> { striped, cell_layout, scroll_options, + sense, } = self; let TableScrollOptions { @@ -612,7 +633,14 @@ impl<'a> Table<'a> { // Hide first-frame-jitters when auto-sizing. ui.add_visible_ui(!first_frame_auto_size_columns, |ui| { - let layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout); + let hovered_row_index_id = self.state_id.with("__table_hovered_row"); + let hovered_row_index = ui.memory_mut(|w| { + let hovered_row = w.data.get_temp(hovered_row_index_id); + w.data.remove::(hovered_row_index_id); + hovered_row + }); + + let layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense); add_body_contents(TableBody { layout, @@ -620,11 +648,13 @@ impl<'a> Table<'a> { widths: widths_ref, max_used_widths: max_used_widths_ref, striped, - row_nr: 0, + row_index: 0, start_y: clip_rect.top(), end_y: clip_rect.bottom(), scroll_to_row: scroll_to_row.map(|(r, _)| r), scroll_to_y_range: &mut scroll_to_y_range, + hovered_row_index, + hovered_row_index_id, }); if scroll_to_row.is_some() && scroll_to_y_range.is_none() { @@ -755,7 +785,7 @@ pub struct TableBody<'a> { max_used_widths: &'a mut [f32], striped: bool, - row_nr: usize, + row_index: usize, start_y: f32, end_y: f32, @@ -765,6 +795,11 @@ pub struct TableBody<'a> { /// If we find the correct row to scroll to, /// this is set to the y-range of the row. scroll_to_y_range: &'a mut Option, + + hovered_row_index: Option, + + /// Used to store the hovered row index between frames. + hovered_row_index_id: egui::Id, } impl<'a> TableBody<'a> { @@ -800,23 +835,29 @@ impl<'a> TableBody<'a> { /// ⚠️ It is much more performant to use [`Self::rows`] or [`Self::heterogeneous_rows`], /// as those functions will only render the visible rows. pub fn row(&mut self, height: f32, add_row_content: impl FnOnce(TableRow<'a, '_>)) { + let mut response: Option = None; let top_y = self.layout.cursor.y; add_row_content(TableRow { layout: &mut self.layout, columns: self.columns, widths: self.widths, max_used_widths: self.max_used_widths, + row_index: self.row_index, col_index: 0, - striped: self.striped && self.row_nr % 2 == 0, height, + striped: self.striped && self.row_index % 2 == 0, + hovered: self.hovered_row_index == Some(self.row_index), + selected: false, + response: &mut response, }); + self.capture_hover_state(&response, self.row_index); let bottom_y = self.layout.cursor.y; - if Some(self.row_nr) == self.scroll_to_row { + if Some(self.row_index) == self.scroll_to_row { *self.scroll_to_y_range = Some(Rangef::new(top_y, bottom_y)); } - self.row_nr += 1; + self.row_index += 1; } /// Add many rows with same height. @@ -834,9 +875,10 @@ impl<'a> TableBody<'a> { /// .body(|mut body| { /// let row_height = 18.0; /// let num_rows = 10_000; - /// body.rows(row_height, num_rows, |row_index, mut row| { + /// body.rows(row_height, num_rows, |mut row| { + /// let row_index = row.index(); /// row.col(|ui| { - /// ui.label("First column"); + /// ui.label(format!("First column of row {row_index}")); /// }); /// }); /// }); @@ -846,7 +888,7 @@ impl<'a> TableBody<'a> { mut self, row_height_sans_spacing: f32, total_rows: usize, - mut add_row_content: impl FnMut(usize, TableRow<'_, '_>), + mut add_row_content: impl FnMut(TableRow<'_, '_>), ) { let spacing = self.layout.ui.spacing().item_spacing; let row_height_with_spacing = row_height_sans_spacing + spacing.y; @@ -874,19 +916,22 @@ impl<'a> TableBody<'a> { ((scroll_offset_y + max_height) / row_height_with_spacing).ceil() as usize + 1; let max_row = max_row.min(total_rows); - for idx in min_row..max_row { - add_row_content( - idx, - TableRow { - layout: &mut self.layout, - columns: self.columns, - widths: self.widths, - max_used_widths: self.max_used_widths, - col_index: 0, - striped: self.striped && (idx + self.row_nr) % 2 == 0, - height: row_height_sans_spacing, - }, - ); + for row_index in min_row..max_row { + let mut response: Option = None; + add_row_content(TableRow { + layout: &mut self.layout, + columns: self.columns, + widths: self.widths, + max_used_widths: self.max_used_widths, + row_index, + col_index: 0, + height: row_height_sans_spacing, + striped: self.striped && (row_index + self.row_index) % 2 == 0, + hovered: self.hovered_row_index == Some(row_index), + selected: false, + response: &mut response, + }); + self.capture_hover_state(&response, row_index); } if total_rows - max_row > 0 { @@ -911,7 +956,8 @@ impl<'a> TableBody<'a> { /// .column(Column::remainder().at_least(100.0)) /// .body(|mut body| { /// let row_heights: Vec = vec![60.0, 18.0, 31.0, 240.0]; - /// body.heterogeneous_rows(row_heights.into_iter(), |row_index, mut row| { + /// body.heterogeneous_rows(row_heights.into_iter(), |mut row| { + /// let row_index = row.index(); /// let thick = row_index % 6 == 0; /// row.col(|ui| { /// ui.centered_and_justified(|ui| { @@ -925,7 +971,7 @@ impl<'a> TableBody<'a> { pub fn heterogeneous_rows( mut self, heights: impl Iterator, - mut add_row_content: impl FnMut(usize, TableRow<'_, '_>), + mut add_row_content: impl FnMut(TableRow<'_, '_>), ) { let spacing = self.layout.ui.spacing().item_spacing; let mut enumerated_heights = heights.enumerate(); @@ -952,19 +998,21 @@ impl<'a> TableBody<'a> { if cursor_y >= scroll_offset_y { // This row is visible: self.add_buffer(old_cursor_y as f32); // skip all the invisible rows - - add_row_content( + let mut response: Option = None; + add_row_content(TableRow { + layout: &mut self.layout, + columns: self.columns, + widths: self.widths, + max_used_widths: self.max_used_widths, row_index, - TableRow { - layout: &mut self.layout, - columns: self.columns, - widths: self.widths, - max_used_widths: self.max_used_widths, - col_index: 0, - striped: self.striped && (row_index + self.row_nr) % 2 == 0, - height: row_height, - }, - ); + col_index: 0, + height: row_height, + striped: self.striped && (row_index + self.row_index) % 2 == 0, + hovered: self.hovered_row_index == Some(row_index), + selected: false, + response: &mut response, + }); + self.capture_hover_state(&response, row_index); break; } } @@ -972,18 +1020,21 @@ impl<'a> TableBody<'a> { // populate visible rows: for (row_index, row_height) in &mut enumerated_heights { let top_y = cursor_y; - add_row_content( + let mut response: Option = None; + add_row_content(TableRow { + layout: &mut self.layout, + columns: self.columns, + widths: self.widths, + max_used_widths: self.max_used_widths, row_index, - TableRow { - layout: &mut self.layout, - columns: self.columns, - widths: self.widths, - max_used_widths: self.max_used_widths, - col_index: 0, - striped: self.striped && (row_index + self.row_nr) % 2 == 0, - height: row_height, - }, - ); + col_index: 0, + height: row_height, + striped: self.striped && (row_index + self.row_index) % 2 == 0, + hovered: self.hovered_row_index == Some(row_index), + selected: false, + response: &mut response, + }); + self.capture_hover_state(&response, row_index); cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { @@ -1031,6 +1082,17 @@ impl<'a> TableBody<'a> { fn add_buffer(&mut self, height: f32) { self.layout.skip_space(egui::vec2(0.0, height)); } + + // Capture the hover information for the just created row. This is used in the next render + // to ensure that the entire row is highlighted. + fn capture_hover_state(&mut self, response: &Option, row_index: usize) { + let is_row_hovered = response.as_ref().map_or(false, |r| r.hovered()); + if is_row_hovered { + self.layout + .ui + .memory_mut(|w| w.data.insert_temp(self.hovered_row_index_id, row_index)); + } + } } impl<'a> Drop for TableBody<'a> { @@ -1049,15 +1111,21 @@ pub struct TableRow<'a, 'b> { /// grows during building with the maximum widths max_used_widths: &'b mut [f32], + row_index: usize, col_index: usize, - striped: bool, height: f32, + + striped: bool, + hovered: bool, + selected: bool, + + response: &'b mut Option, } impl<'a, 'b> TableRow<'a, 'b> { /// Add the contents of a column. /// - /// Return the used space (`min_rect`) plus the [`Response`] of the whole cell. + /// Returns the used space (`min_rect`) plus the [`Response`] of the whole cell. #[cfg_attr(debug_assertions, track_caller)] pub fn col(&mut self, add_cell_contents: impl FnOnce(&mut Ui)) -> (Rect, Response) { let col_index = self.col_index; @@ -1078,19 +1146,58 @@ impl<'a, 'b> TableRow<'a, 'b> { let width = CellSize::Absolute(width); let height = CellSize::Absolute(self.height); - let (used_rect, response) = - self.layout - .add(clip, self.striped, width, height, add_cell_contents); + let flags = StripLayoutFlags { + clip, + striped: self.striped, + hovered: self.hovered, + selected: self.selected, + }; + + let (used_rect, response) = self.layout.add(flags, width, height, add_cell_contents); if let Some(max_w) = self.max_used_widths.get_mut(col_index) { *max_w = max_w.max(used_rect.width()); } + *self.response = Some( + self.response + .as_ref() + .map_or(response.clone(), |r| r.union(response.clone())), + ); + (used_rect, response) } + + /// Set the selection highlight state for cells added after a call to this function. + #[inline] + pub fn set_selected(&mut self, selected: bool) { + self.selected = selected; + } + + /// Returns a union of the [`Response`]s of the cells added to the row up to this point. + /// + /// You need to add at least one row to the table before calling this function. + pub fn response(&self) -> Response { + self.response + .clone() + .expect("Should only be called after `col`") + } + + /// Returns the index of the row. + #[inline] + pub fn index(&self) -> usize { + self.row_index + } + + /// Returns the index of the column. Incremented after a column is added. + #[inline] + pub fn col_index(&self) -> usize { + self.col_index + } } impl<'a, 'b> Drop for TableRow<'a, 'b> { + #[inline] fn drop(&mut self) { self.layout.end_line(); }