diff --git a/egui_demo_lib/src/apps/demo/strip_demo.rs b/egui_demo_lib/src/apps/demo/strip_demo.rs index 5d1d4c25..ebaeb2b2 100644 --- a/egui_demo_lib/src/apps/demo/strip_demo.rs +++ b/egui_demo_lib/src/apps/demo/strip_demo.rs @@ -31,14 +31,14 @@ impl super::View for StripDemo { .size(Size::relative(0.5).at_least(60.0)) .size(Size::exact(10.0)) .vertical(|mut strip| { - strip.cell_clip(|ui| { + strip.cell(|ui| { ui.painter() .rect_filled(ui.available_rect_before_wrap(), 0.0, Color32::BLUE); ui.label("Full width and 50px height"); }); strip.strip(|builder| { builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { - strip.cell_clip(|ui| { + strip.cell(|ui| { ui.painter().rect_filled( ui.available_rect_before_wrap(), 0.0, @@ -49,7 +49,7 @@ impl super::View for StripDemo { strip.strip(|builder| { builder.sizes(Size::remainder(), 3).vertical(|mut strip| { strip.empty(); - strip.cell_clip(|ui| { + strip.cell(|ui| { ui.painter().rect_filled( ui.available_rect_before_wrap(), 0.0, @@ -76,7 +76,7 @@ impl super::View for StripDemo { .size(Size::remainder()) .vertical(|mut strip| { strip.empty(); - strip.cell_clip(|ui| { + strip.cell(|ui| { ui.painter().rect_filled( ui.available_rect_before_wrap(), 0.0, @@ -87,7 +87,7 @@ impl super::View for StripDemo { }); }); strip.empty(); - strip.cell_clip(|ui| { + strip.cell(|ui| { ui.painter().rect_filled( ui.available_rect_before_wrap(), 0.0, @@ -97,7 +97,7 @@ impl super::View for StripDemo { }); }); }); - strip.cell_clip(|ui| { + strip.cell(|ui| { ui.vertical_centered(|ui| { ui.add(crate::__egui_github_link_file!()); }); diff --git a/egui_demo_lib/src/apps/demo/table_demo.rs b/egui_demo_lib/src/apps/demo/table_demo.rs index 2e235d02..641e003f 100644 --- a/egui_demo_lib/src/apps/demo/table_demo.rs +++ b/egui_demo_lib/src/apps/demo/table_demo.rs @@ -1,3 +1,4 @@ +use egui::TextStyle; use egui_extras::{Size, StripBuilder, TableBuilder}; /// Shows off a table with dynamic layout @@ -35,7 +36,7 @@ impl super::View for TableDemo { .size(Size::remainder()) // for the table .size(Size::exact(10.0)) // for the source code link .vertical(|mut strip| { - strip.cell_clip(|ui| { + strip.cell(|ui| { self.table_ui(ui); }); strip.cell(|ui| { @@ -49,57 +50,70 @@ impl super::View for TableDemo { impl TableDemo { fn table_ui(&mut self, ui: &mut egui::Ui) { + let text_height = TextStyle::Body.resolve(ui.style()).size; + TableBuilder::new(ui) .striped(true) .column(Size::initial(60.0).at_least(40.0)) - .column(Size::remainder().at_least(60.0)) .column(Size::initial(60.0).at_least(40.0)) + .column(Size::remainder().at_least(60.0)) .resizable(self.resizable) .header(20.0, |mut header| { - header.col_clip(|ui| { - ui.heading("Left"); + header.col(|ui| { + ui.centered_and_justified(|ui| { + ui.heading("Row"); + }); }); - header.col_clip(|ui| { - ui.heading("Middle"); + header.col(|ui| { + ui.centered_and_justified(|ui| { + ui.heading("Clock"); + }); }); - header.col_clip(|ui| { - ui.heading("Right"); + header.col(|ui| { + ui.centered_and_justified(|ui| { + ui.heading("Content"); + }); }); }) .body(|mut body| { if self.virtual_scroll { - body.rows(20.0, 100_000, |index, mut row| { - row.col_clip(|ui| { - ui.label(index.to_string()); + body.rows(text_height, 100_000, |row_index, mut row| { + row.col(|ui| { + ui.label(row_index.to_string()); }); - row.col_clip(|ui| { + row.col(|ui| { + ui.label(clock_emoji(row_index)); + }); + row.col(|ui| { ui.add( - egui::Label::new("virtual scroll, easily with thousands of rows!") - .wrap(false), + egui::Label::new("Thousands of rows of even height").wrap(false), ); }); - row.col_clip(|ui| { - ui.label(index.to_string()); - }); }); } else { - for i in 0..20 { - let thick = i % 4 == 0; - let height = if thick { 25.0 } else { 15.0 }; - body.row(height, |mut row| { - row.col_clip(|ui| { - ui.label(i.to_string()); + for row_index in 0..20 { + let thick = row_index % 6 == 0; + let row_height = if thick { 30.0 } else { 18.0 }; + body.row(row_height, |mut row| { + row.col(|ui| { + ui.centered_and_justified(|ui| { + ui.label(row_index.to_string()); + }); }); - row.col_clip(|ui| { - ui.style_mut().wrap = Some(false); - if thick { - ui.heading("Extra thick row"); - } else { - ui.label("Normal row"); - } + row.col(|ui| { + ui.centered_and_justified(|ui| { + ui.label(clock_emoji(row_index)); + }); }); - row.col_clip(|ui| { - ui.label(i.to_string()); + row.col(|ui| { + ui.centered_and_justified(|ui| { + ui.style_mut().wrap = Some(false); + if thick { + ui.heading("Extra thick row"); + } else { + ui.label("Normal row"); + } + }); }); }); } @@ -107,3 +121,9 @@ impl TableDemo { }); } } + +fn clock_emoji(row_index: usize) -> String { + char::from_u32(0x1f550 + row_index as u32 % 24) + .unwrap() + .to_string() +} diff --git a/egui_extras/src/datepicker.rs b/egui_extras/src/datepicker/mod.rs similarity index 100% rename from egui_extras/src/datepicker.rs rename to egui_extras/src/datepicker/mod.rs diff --git a/egui_extras/src/datepicker/popup.rs b/egui_extras/src/datepicker/popup.rs index 2c552919..0a280100 100644 --- a/egui_extras/src/datepicker/popup.rs +++ b/egui_extras/src/datepicker/popup.rs @@ -1,7 +1,7 @@ use super::{button::DatePickerButtonState, month_data}; use crate::{Size, StripBuilder, TableBuilder}; use chrono::{Date, Datelike, NaiveDate, Utc, Weekday}; -use egui::{Align, Button, Color32, ComboBox, Direction, Id, Label, Layout, RichText, Ui, Vec2}; +use egui::{Align, Button, Color32, ComboBox, Direction, Id, Layout, RichText, Ui, Vec2}; #[derive(Default, Clone)] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] @@ -55,6 +55,7 @@ impl<'a> DatePickerPopup<'a> { let spacing = 2.0; ui.spacing_mut().item_spacing = Vec2::splat(spacing); StripBuilder::new(ui) + .clip(false) .sizes( Size::exact(height), match (self.combo_boxes, self.arrows) { @@ -70,7 +71,7 @@ impl<'a> DatePickerPopup<'a> { .size(Size::exact(height)) .vertical(|mut strip| { if self.combo_boxes { - strip.strip_clip(|builder| { + strip.strip(|builder| { builder.sizes(Size::remainder(), 3).horizontal(|mut strip| { strip.cell(|ui| { ComboBox::from_id_source("date_picker_year") @@ -94,14 +95,14 @@ impl<'a> DatePickerPopup<'a> { }); strip.cell(|ui| { ComboBox::from_id_source("date_picker_month") - .selected_text(popup_state.month.to_string()) + .selected_text(month_name(popup_state.month)) .show_ui(ui, |ui| { for month in 1..=12 { if ui .selectable_value( &mut popup_state.month, month, - month.to_string(), + month_name(month), ) .changed() { @@ -236,6 +237,7 @@ impl<'a> DatePickerPopup<'a> { ui.spacing_mut().item_spacing = Vec2::new(1.0, 2.0); TableBuilder::new(ui) .scroll(false) + .clip(false) .columns(Size::remainder(), if self.calendar_week { 8 } else { 7 }) .header(height, |mut header| { if self.calendar_week { @@ -243,7 +245,7 @@ impl<'a> DatePickerPopup<'a> { ui.with_layout( Layout::centered_and_justified(Direction::TopDown), |ui| { - ui.add(Label::new("Week")); + ui.label("Week"); }, ); }); @@ -255,7 +257,7 @@ impl<'a> DatePickerPopup<'a> { ui.with_layout( Layout::centered_and_justified(Direction::TopDown), |ui| { - ui.add(Label::new(name)); + ui.label(name); }, ); }); @@ -266,7 +268,7 @@ impl<'a> DatePickerPopup<'a> { body.row(height, |mut row| { if self.calendar_week { row.col(|ui| { - ui.add(Label::new(week.number.to_string())); + ui.label(week.number.to_string()); }); } for day in week.days { @@ -274,7 +276,6 @@ impl<'a> DatePickerPopup<'a> { ui.with_layout( Layout::top_down_justified(Align::Center), |ui| { - //TODO: Colors from egui style let fill_color = if popup_state.year == day.year() && popup_state.month == day.month() @@ -284,25 +285,51 @@ impl<'a> DatePickerPopup<'a> { } else if day.weekday() == Weekday::Sat || day.weekday() == Weekday::Sun { - Color32::DARK_RED + if ui.visuals().dark_mode { + Color32::DARK_RED + } else { + Color32::LIGHT_RED + } } else { - Color32::BLACK - }; - let text_color = if day == today { - Color32::RED - } else if day.month() == popup_state.month { - Color32::WHITE - } else { - Color32::from_gray(80) + ui.visuals().extreme_bg_color }; - let button = Button::new( - RichText::new(day.day().to_string()) + let mut text_color = ui + .visuals() + .widgets + .inactive + .text_color(); + + if day.month() != popup_state.month { + text_color = + text_color.linear_multiply(0.5); + }; + + let button_response = ui.add( + Button::new( + RichText::new( + day.day().to_string(), + ) .color(text_color), - ) - .fill(fill_color); + ) + .fill(fill_color), + ); - if ui.add(button).clicked() { + if day == today { + // Encircle today's date + let stroke = ui + .visuals() + .widgets + .inactive + .fg_stroke; + ui.painter().circle_stroke( + button_response.rect.center(), + 8.0, + stroke, + ); + } + + if button_response.clicked() { popup_state.year = day.year(); popup_state.month = day.month(); popup_state.day = day.day(); @@ -361,3 +388,21 @@ impl<'a> DatePickerPopup<'a> { } } } + +fn month_name(i: u32) -> &'static str { + match i { + 1 => "January", + 2 => "February", + 3 => "March", + 4 => "April", + 5 => "May", + 6 => "June", + 7 => "July", + 8 => "August", + 9 => "September", + 10 => "October", + 11 => "November", + 12 => "December", + _ => panic!("Unknown month: {}", i), + } +} diff --git a/egui_extras/src/layout.rs b/egui_extras/src/layout.rs index 36b5fdd6..634518aa 100644 --- a/egui_extras/src/layout.rs +++ b/egui_extras/src/layout.rs @@ -31,10 +31,11 @@ pub struct StripLayout<'l> { rect: Rect, cursor: Pos2, max: Pos2, + pub(crate) clip: bool, } impl<'l> StripLayout<'l> { - pub(crate) fn new(ui: &'l mut Ui, direction: CellDirection) -> Self { + pub(crate) fn new(ui: &'l mut Ui, direction: CellDirection, clip: bool) -> Self { let rect = ui.available_rect_before_wrap(); let pos = rect.left_top(); @@ -44,6 +45,7 @@ impl<'l> StripLayout<'l> { cursor: pos, max: pos, direction, + clip, } } @@ -89,11 +91,10 @@ impl<'l> StripLayout<'l> { &mut self, width: CellSize, height: CellSize, - clip: bool, add_contents: impl FnOnce(&mut Ui), ) -> Response { let rect = self.cell_rect(&width, &height); - let used_rect = self.cell(rect, clip, add_contents); + let used_rect = self.cell(rect, add_contents); self.set_pos(rect); self.ui.allocate_rect(rect.union(used_rect), Sense::hover()) } @@ -102,7 +103,6 @@ impl<'l> StripLayout<'l> { &mut self, width: CellSize, height: CellSize, - clip: bool, add_contents: impl FnOnce(&mut Ui), ) -> Response { let rect = self.cell_rect(&width, &height); @@ -114,7 +114,7 @@ impl<'l> StripLayout<'l> { .painter() .rect_filled(rect, 0.0, self.ui.visuals().faint_bg_color); - self.add(width, height, clip, add_contents) + self.add(width, height, add_contents) } /// only needed for layouts with multiple lines, like [`Table`]. @@ -131,10 +131,10 @@ impl<'l> StripLayout<'l> { } } - fn cell(&mut self, rect: Rect, clip: bool, add_contents: impl FnOnce(&mut Ui)) -> Rect { + fn cell(&mut self, rect: Rect, add_contents: impl FnOnce(&mut Ui)) -> Rect { let mut child_ui = self.ui.child_ui(rect, *self.ui.layout()); - if clip { + if self.clip { let mut clip_rect = child_ui.clip_rect(); clip_rect.min = clip_rect.min.max(rect.min); clip_rect.max = clip_rect.max.min(rect.max); diff --git a/egui_extras/src/strip.rs b/egui_extras/src/strip.rs index 62061d99..2f150877 100644 --- a/egui_extras/src/strip.rs +++ b/egui_extras/src/strip.rs @@ -40,6 +40,7 @@ use egui::{Response, Ui}; pub struct StripBuilder<'a> { ui: &'a mut Ui, sizing: Sizing, + clip: bool, } impl<'a> StripBuilder<'a> { @@ -47,7 +48,17 @@ impl<'a> StripBuilder<'a> { pub fn new(ui: &'a mut Ui) -> Self { let sizing = Sizing::new(); - Self { ui, sizing } + Self { + ui, + sizing, + clip: true, + } + } + + /// Should we clip the contents of each cell? Default: `true`. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self } /// Add size hint for one column/row. @@ -76,7 +87,7 @@ impl<'a> StripBuilder<'a> { self.ui.available_rect_before_wrap().width() - self.ui.spacing().item_spacing.x, self.ui.spacing().item_spacing.x, ); - let mut layout = StripLayout::new(self.ui, CellDirection::Horizontal); + let mut layout = StripLayout::new(self.ui, CellDirection::Horizontal, self.clip); strip(Strip { layout: &mut layout, direction: CellDirection::Horizontal, @@ -97,7 +108,7 @@ impl<'a> StripBuilder<'a> { self.ui.available_rect_before_wrap().height() - self.ui.spacing().item_spacing.y, self.ui.spacing().item_spacing.y, ); - let mut layout = StripLayout::new(self.ui, CellDirection::Vertical); + let mut layout = StripLayout::new(self.ui, CellDirection::Vertical, self.clip); strip(Strip { layout: &mut layout, direction: CellDirection::Vertical, @@ -136,35 +147,18 @@ impl<'a, 'b> Strip<'a, 'b> { self.layout.empty(width, height); } - fn cell_impl(&mut self, clip: bool, add_contents: impl FnOnce(&mut Ui)) { - let (width, height) = self.next_cell_size(); - self.layout.add(width, height, clip, add_contents); - } - - /// Add cell, content is wrapped + /// Add cell contents. pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) { - self.cell_impl(false, add_contents); - } - - /// Add cell, content is clipped - pub fn cell_clip(&mut self, add_contents: impl FnOnce(&mut Ui)) { - self.cell_impl(true, add_contents); - } - - fn strip_impl(&mut self, clip: bool, strip_builder: impl FnOnce(StripBuilder<'_>)) { - self.cell_impl(clip, |ui| { - strip_builder(StripBuilder::new(ui)); - }); + let (width, height) = self.next_cell_size(); + self.layout.add(width, height, add_contents); } /// Add strip as cell pub fn strip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) { - self.strip_impl(false, strip_builder); - } - - /// Add strip as cell, content is clipped - pub fn strip_clip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) { - self.strip_impl(true, strip_builder); + let clip = self.layout.clip; + self.cell(|ui| { + strip_builder(StripBuilder::new(ui).clip(clip)); + }); } } diff --git a/egui_extras/src/table.rs b/egui_extras/src/table.rs index c85df01f..6d7b8ec6 100644 --- a/egui_extras/src/table.rs +++ b/egui_extras/src/table.rs @@ -40,7 +40,7 @@ use egui::{Response, Ui}; /// row.col(|ui| { /// ui.label("first row growing cell"); /// }); -/// row.col_clip(|ui| { +/// row.col(|ui| { /// ui.button("action"); /// }); /// }); @@ -53,6 +53,7 @@ pub struct TableBuilder<'a> { scroll: bool, striped: bool, resizable: bool, + clip: bool, } impl<'a> TableBuilder<'a> { @@ -65,6 +66,7 @@ impl<'a> TableBuilder<'a> { scroll: true, striped: false, resizable: false, + clip: true, } } @@ -94,6 +96,12 @@ impl<'a> TableBuilder<'a> { self } + /// Should we clip the contents of each cell? Default: `true`. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + /// Add size hint for column pub fn column(mut self, width: Size) -> Self { self.sizing.add(width); @@ -127,6 +135,7 @@ impl<'a> TableBuilder<'a> { scroll, striped, resizable, + clip, } = self; let resize_id = resizable.then(|| ui.id().with("__table_resize")); @@ -141,13 +150,12 @@ impl<'a> TableBuilder<'a> { let table_top = ui.cursor().top(); { - let mut layout = StripLayout::new(ui, CellDirection::Horizontal); + let mut layout = StripLayout::new(ui, CellDirection::Horizontal, clip); header(TableRow { layout: &mut layout, widths: &widths, striped: false, height, - clicked: false, }); layout.allocate_rect(); } @@ -161,6 +169,7 @@ impl<'a> TableBuilder<'a> { widths, scroll, striped, + clip, } } @@ -177,6 +186,7 @@ impl<'a> TableBuilder<'a> { scroll, striped, resizable, + clip, } = self; let resize_id = resizable.then(|| ui.id().with("__table_resize")); @@ -199,6 +209,7 @@ impl<'a> TableBuilder<'a> { widths, scroll, striped, + clip, } .body(body); } @@ -216,6 +227,7 @@ pub struct Table<'a> { widths: Vec, scroll: bool, striped: bool, + clip: bool, } impl<'a> Table<'a> { @@ -233,6 +245,7 @@ impl<'a> Table<'a> { widths, scroll, striped, + clip, } = self; let avail_rect = ui.available_rect_before_wrap(); @@ -242,7 +255,7 @@ impl<'a> Table<'a> { egui::ScrollArea::new([false, scroll]) .auto_shrink([true; 2]) .show(ui, move |ui| { - let layout = StripLayout::new(ui, CellDirection::Horizontal); + let layout = StripLayout::new(ui, CellDirection::Horizontal, clip); body(TableBody { layout, @@ -355,7 +368,6 @@ impl<'a> TableBody<'a> { widths: &self.widths, striped: false, height: skip_height, - clicked: false, } .col(|_| ()); // advances the cursor } @@ -372,7 +384,6 @@ impl<'a> TableBody<'a> { widths: &self.widths, striped: self.striped && idx % 2 == 0, height, - clicked: false, }, ); } @@ -385,7 +396,6 @@ impl<'a> TableBody<'a> { widths: &self.widths, striped: false, height: skip_height, - clicked: false, } .col(|_| ()); // advances the cursor } @@ -398,7 +408,6 @@ impl<'a> TableBody<'a> { widths: &self.widths, striped: self.striped && self.row_nr % 2 == 0, height, - clicked: false, }); self.row_nr += 1; @@ -418,26 +427,11 @@ pub struct TableRow<'a, 'b> { widths: &'b [f32], striped: bool, height: f32, - clicked: bool, } impl<'a, 'b> TableRow<'a, 'b> { - /// Check if row was clicked - pub fn clicked(&self) -> bool { - self.clicked - } - - /// Add column, content is wrapped + /// Add the contents of a column. pub fn col(&mut self, add_contents: impl FnOnce(&mut Ui)) -> Response { - self.column(false, add_contents) - } - - /// Add column, content is clipped - pub fn col_clip(&mut self, add_contents: impl FnOnce(&mut Ui)) -> Response { - self.column(true, add_contents) - } - - fn column(&mut self, clip: bool, add_contents: impl FnOnce(&mut Ui)) -> Response { assert!( !self.widths.is_empty(), "Tried using more table columns than available." @@ -448,19 +442,11 @@ impl<'a, 'b> TableRow<'a, 'b> { let width = CellSize::Absolute(width); let height = CellSize::Absolute(self.height); - let response; - if self.striped { - response = self.layout.add_striped(width, height, clip, add_contents); + self.layout.add_striped(width, height, add_contents) } else { - response = self.layout.add(width, height, clip, add_contents); + self.layout.add(width, height, add_contents) } - - if response.clicked() { - self.clicked = true; - } - - response } }