diff --git a/CHANGELOG.md b/CHANGELOG.md index a95001781e0..74ed7691ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG * Added `Area::pivot` and `Window::pivot` which controls what part of the window to position ([#2303](https://github.com/emilk/egui/pull/2303)). * Added support for [thin space](https://en.wikipedia.org/wiki/Thin_space). * Added optional integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)). +* Added `panel_fill`, `window_fill` and `window_stroke` to `Visuals` for your theming pleasure ([#2406](https://github.com/emilk/egui/pull/2406)). * Plots: * Allow linking plot cursors ([#1722](https://github.com/emilk/egui/pull/1722)). * Added `Plot::auto_bounds_x/y` and `Plot::reset` ([#2029](https://github.com/emilk/egui/pull/2029)). @@ -36,6 +37,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG * Panels always have a separator line, but no stroke on other sides. Their spacing has also changed slightly ([#2261](https://github.com/emilk/egui/pull/2261)). * Tooltips are only shown when mouse pointer is still ([#2263](https://github.com/emilk/egui/pull/2263)). * Make it slightly easier to click buttons ([#2304](https://github.com/emilk/egui/pull/2304)). +* `egui::color` has been renamed `egui::ecolor` ([#2399](https://github.com/emilk/egui/pull/2399)). ### Fixed 🐛 * ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)). @@ -52,6 +54,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG * Fix bug in `plot::Line::fill` ([#2275](https://github.com/emilk/egui/pull/2275)). * Only emit `changed` events in `radio_value` and `selectable_value` if the value actually changed ([#2343](https://github.com/emilk/egui/pull/2343)). * Fixed sizing bug in `Grid` ([#2384](https://github.com/emilk/egui/pull/2384)). +* `ComboBox::width` now correctly sets the outer width ([#2406](https://github.com/emilk/egui/pull/2406)). ## 0.19.0 - 2022-08-20 diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index febf0d1b8d3..942e06254c4 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -311,7 +311,6 @@ impl<'ui, HeaderRet> HeaderResponse<'ui, HeaderRet> { /// Paint the arrow icon that indicated if the region is open or not pub fn paint_default_icon(ui: &mut Ui, openness: f32, response: &Response) { let visuals = ui.style().interact(response); - let stroke = visuals.fg_stroke; let rect = response.rect; @@ -325,7 +324,11 @@ pub fn paint_default_icon(ui: &mut Ui, openness: f32, response: &Response) { *p = rect.center() + rotation * (*p - rect.center()); } - ui.painter().add(Shape::closed_line(points, stroke)); + ui.painter().add(Shape::convex_polygon( + points, + visuals.fg_stroke.color, + Stroke::NONE, + )); } /// A function that paints an icon indicating if the region is open or not diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index cb2665d5ede..38e9f8afb89 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -77,7 +77,7 @@ impl ComboBox { } } - /// Set the width of the button and menu + /// Set the outer width of the button and menu. pub fn width(mut self, width: f32) -> Self { self.width = Some(width); self @@ -261,15 +261,20 @@ fn combo_box_dyn<'c, R>( AboveOrBelow::Above }; + let margin = ui.spacing().button_padding; let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| { // We don't want to change width when user selects something new - let full_minimum_width = wrap_enabled - .then(|| ui.available_width() - ui.spacing().item_spacing.x * 2.0) - .unwrap_or_else(|| ui.spacing().slider_width); + let full_minimum_width = if wrap_enabled { + ui.available_width() - ui.spacing().item_spacing.x * 2.0 + } else { + ui.spacing().slider_width - 2.0 * margin.x + }; let icon_size = Vec2::splat(ui.spacing().icon_width); - let wrap_width = wrap_enabled - .then(|| ui.available_width() - ui.spacing().item_spacing.x - icon_size.x) - .unwrap_or(f32::INFINITY); + let wrap_width = if wrap_enabled { + ui.available_width() - ui.spacing().item_spacing.x - icon_size.x + } else { + f32::INFINITY + }; let galley = selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button); @@ -396,16 +401,18 @@ fn paint_default_icon( match above_or_below { AboveOrBelow::Above => { // Upward pointing triangle - painter.add(Shape::closed_line( + painter.add(Shape::convex_polygon( vec![rect.left_bottom(), rect.right_bottom(), rect.center_top()], - visuals.fg_stroke, + visuals.fg_stroke.color, + Stroke::NONE, )); } AboveOrBelow::Below => { // Downward pointing triangle - painter.add(Shape::closed_line( + painter.add(Shape::convex_polygon( vec![rect.left_top(), rect.right_top(), rect.center_bottom()], - visuals.fg_stroke, + visuals.fg_stroke.color, + Stroke::NONE, )); } } diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 769aa8cd1b1..b41e75a7dcf 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -45,7 +45,7 @@ impl Frame { pub fn side_top_panel(style: &Style) -> Self { Self { inner_margin: Margin::symmetric(8.0, 2.0), - fill: style.visuals.window_fill(), + fill: style.visuals.panel_fill, ..Default::default() } } @@ -53,7 +53,7 @@ impl Frame { pub fn central_panel(style: &Style) -> Self { Self { inner_margin: Margin::same(8.0), - fill: style.visuals.window_fill(), + fill: style.visuals.panel_fill, ..Default::default() } } diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index bdea5e5ef38..e4947db39a5 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -306,7 +306,10 @@ impl SidePanel { Stroke::NONE }; // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done - let resize_x = side.opposite().side_x(rect); + // In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel + // (hence the shrink). + let resize_x = side.opposite().side_x(rect.shrink(1.0)); + let resize_x = ui.painter().round_to_pixel(resize_x); ui.painter().vline(resize_x, rect.y_range(), stroke); } @@ -755,7 +758,10 @@ impl TopBottomPanel { Stroke::NONE }; // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done - let resize_y = side.opposite().side_y(rect); + // In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel + // (hence the shrink). + let resize_y = side.opposite().side_y(rect.shrink(1.0)); + let resize_y = ui.painter().round_to_pixel(resize_y); ui.painter().hline(rect.x_range(), resize_y, stroke); } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index f6bd2c4f980..09271fe1ae6 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -360,6 +360,10 @@ impl Margin { pub fn right_bottom(&self) -> Vec2 { vec2(self.right, self.bottom) } + + pub fn is_same(&self) -> bool { + self.left == self.right && self.left == self.top && self.left == self.bottom + } } impl From for Margin { @@ -464,6 +468,11 @@ pub struct Visuals { pub window_rounding: Rounding, pub window_shadow: Shadow, + pub window_fill: Color32, + pub window_stroke: Stroke, + + /// Panel background color + pub panel_fill: Color32, pub popup_shadow: Shadow, @@ -495,7 +504,7 @@ impl Visuals { } pub fn weak_text_color(&self) -> Color32 { - crate::ecolor::tint_color_towards(self.text_color(), self.window_fill()) + self.gray_out(self.text_color()) } #[inline(always)] @@ -506,12 +515,25 @@ impl Visuals { /// Window background color. #[inline(always)] pub fn window_fill(&self) -> Color32 { - self.widgets.noninteractive.bg_fill + self.window_fill } #[inline(always)] pub fn window_stroke(&self) -> Stroke { - self.widgets.noninteractive.bg_stroke + self.window_stroke + } + + /// When fading out things, we fade the colors towards this. + // TODO(emilk): replace with an alpha + #[inline(always)] + pub fn fade_out_to_color(&self) -> Color32 { + self.widgets.noninteractive.bg_fill + } + + /// Returned a "grayed out" version of the given color. + #[inline(always)] + pub fn gray_out(&self, color: Color32) -> Color32 { + crate::ecolor::tint_color_towards(color, self.fade_out_to_color()) } } @@ -651,7 +673,7 @@ impl Default for Spacing { Self { item_spacing: vec2(8.0, 3.0), window_margin: Margin::same(6.0), - menu_margin: Margin::same(1.0), + menu_margin: Margin::same(6.0), button_padding: vec2(4.0, 1.0), indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing` interact_size: vec2(40.0, 18.0), @@ -694,8 +716,14 @@ impl Visuals { code_bg_color: Color32::from_gray(64), warn_fg_color: Color32::from_rgb(255, 143, 0), // orange error_fg_color: Color32::from_rgb(255, 0, 0), // red + window_rounding: Rounding::same(6.0), window_shadow: Shadow::big_dark(), + window_fill: Color32::from_gray(27), + window_stroke: Stroke::new(1.0, Color32::from_gray(60)), + + panel_fill: Color32::from_gray(27), + popup_shadow: Shadow::small_dark(), resize_corner_size: 12.0, text_cursor_width: 2.0, @@ -718,7 +746,13 @@ impl Visuals { code_bg_color: Color32::from_gray(230), warn_fg_color: Color32::from_rgb(255, 100, 0), // slightly orange red. it's difficult to find a warning color that pops on bright background. error_fg_color: Color32::from_rgb(255, 0, 0), // red + window_shadow: Shadow::big_light(), + window_fill: Color32::from_gray(248), + window_stroke: Stroke::new(1.0, Color32::from_gray(190)), + + panel_fill: Color32::from_gray(248), + popup_shadow: Shadow::small_light(), ..Self::dark() } @@ -757,8 +791,8 @@ impl Widgets { pub fn dark() -> Self { Self { noninteractive: WidgetVisuals { - bg_fill: Color32::from_gray(27), // window background - bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines, windows outlines + bg_fill: Color32::from_gray(27), + bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color rounding: Rounding::same(2.0), expansion: 0.0, @@ -797,8 +831,8 @@ impl Widgets { pub fn light() -> Self { Self { noninteractive: WidgetVisuals { - bg_fill: Color32::from_gray(248), // window background - should be distinct from TextEdit background - bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines, windows outlines + bg_fill: Color32::from_gray(248), + bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // normal text color rounding: Rounding::same(2.0), expansion: 0.0, @@ -954,64 +988,8 @@ impl Spacing { ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing")); - let margin_range = 0.0..=20.0; - ui.horizontal(|ui| { - ui.add( - DragValue::new(&mut window_margin.left) - .clamp_range(margin_range.clone()) - .prefix("left: "), - ); - ui.add( - DragValue::new(&mut window_margin.right) - .clamp_range(margin_range.clone()) - .prefix("right: "), - ); - - ui.label("Window margins x"); - }); - - ui.horizontal(|ui| { - ui.add( - DragValue::new(&mut window_margin.top) - .clamp_range(margin_range.clone()) - .prefix("top: "), - ); - ui.add( - DragValue::new(&mut window_margin.bottom) - .clamp_range(margin_range.clone()) - .prefix("bottom: "), - ); - ui.label("Window margins y"); - }); - - ui.horizontal(|ui| { - ui.add( - DragValue::new(&mut menu_margin.left) - .clamp_range(margin_range.clone()) - .prefix("left: "), - ); - ui.add( - DragValue::new(&mut menu_margin.right) - .clamp_range(margin_range.clone()) - .prefix("right: "), - ); - - ui.label("Menu margins x"); - }); - - ui.horizontal(|ui| { - ui.add( - DragValue::new(&mut menu_margin.top) - .clamp_range(margin_range.clone()) - .prefix("top: "), - ); - ui.add( - DragValue::new(&mut menu_margin.bottom) - .clamp_range(margin_range) - .prefix("bottom: "), - ); - ui.label("Menu margins y"); - }); + margin_ui(ui, "Window margin:", window_margin); + margin_ui(ui, "Menu margin:", menu_margin); ui.add(slider_vec2(button_padding, 0.0..=20.0, "Button padding")); ui.add(slider_vec2(interact_size, 4.0..=60.0, "Interact size")) @@ -1079,6 +1057,55 @@ impl Spacing { } } +fn margin_ui(ui: &mut Ui, text: &str, margin: &mut Margin) { + let margin_range = 0.0..=20.0; + + ui.horizontal(|ui| { + ui.label(text); + + let mut same = margin.is_same(); + ui.checkbox(&mut same, "Same"); + + if same { + let mut value = margin.left; + ui.add(DragValue::new(&mut value).clamp_range(margin_range.clone())); + *margin = Margin::same(value); + } else { + if margin.is_same() { + // HACK: prevent collapse: + margin.right = margin.left + 1.0; + margin.bottom = margin.left + 2.0; + margin.top = margin.left + 3.0; + } + + ui.add( + DragValue::new(&mut margin.left) + .clamp_range(margin_range.clone()) + .prefix("L: "), + ) + .on_hover_text("Left margin"); + ui.add( + DragValue::new(&mut margin.right) + .clamp_range(margin_range.clone()) + .prefix("R: "), + ) + .on_hover_text("Right margin"); + ui.add( + DragValue::new(&mut margin.top) + .clamp_range(margin_range.clone()) + .prefix("T: "), + ) + .on_hover_text("Top margin"); + ui.add( + DragValue::new(&mut margin.bottom) + .clamp_range(margin_range) + .prefix("B: "), + ) + .on_hover_text("Bottom margin"); + } + }); +} + impl Interaction { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { @@ -1210,9 +1237,16 @@ impl Visuals { code_bg_color, warn_fg_color, error_fg_color, + window_rounding, window_shadow, + window_fill, + window_stroke, + + panel_fill, + popup_shadow, + resize_corner_size, text_cursor_width, text_cursor_preview, @@ -1223,7 +1257,8 @@ impl Visuals { ui.collapsing("Background Colors", |ui| { ui_color(ui, &mut widgets.inactive.bg_fill, "Buttons"); - ui_color(ui, &mut widgets.noninteractive.bg_fill, "Windows"); + ui_color(ui, window_fill, "Windows"); + ui_color(ui, panel_fill, "Panels"); ui_color(ui, faint_bg_color, "Faint accent").on_hover_text( "Used for faint accentuation of interactive things, like striped grids.", ); @@ -1233,8 +1268,8 @@ impl Visuals { ui.collapsing("Window", |ui| { // Common shortcuts - ui_color(ui, &mut widgets.noninteractive.bg_fill, "Fill"); - stroke_ui(ui, &mut widgets.noninteractive.bg_stroke, "Outline"); + ui_color(ui, window_fill, "Fill"); + stroke_ui(ui, window_stroke, "Outline"); rounding_ui(ui, window_rounding); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 1e7cc1486c8..f8a685637bb 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -239,7 +239,7 @@ impl Ui { self.enabled &= enabled; if !self.enabled && self.is_visible() { self.painter - .set_fade_to_color(Some(self.visuals().window_fill())); + .set_fade_to_color(Some(self.visuals().fade_out_to_color())); } } diff --git a/crates/egui/src/widgets/plot/legend.rs b/crates/egui/src/widgets/plot/legend.rs index 22ca3540d32..f626e569b0d 100644 --- a/crates/egui/src/widgets/plot/legend.rs +++ b/crates/egui/src/widgets/plot/legend.rs @@ -239,7 +239,7 @@ impl Widget for &mut LegendWidget { let background_frame = Frame { inner_margin: vec2(8.0, 4.0).into(), rounding: ui.style().visuals.window_rounding, - shadow: epaint::Shadow::default(), + shadow: epaint::Shadow::NONE, fill: ui.style().visuals.extreme_bg_color, stroke: ui.style().visuals.window_stroke(), ..Default::default() diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index dfe5e784913..0e89c893137 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -176,7 +176,7 @@ impl eframe::App for WrapApp { } fn clear_color(&self, visuals: &egui::Visuals) -> egui::Rgba { - visuals.window_fill().into() + visuals.panel_fill.into() } fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { diff --git a/crates/egui_demo_lib/src/demo/drag_and_drop.rs b/crates/egui_demo_lib/src/demo/drag_and_drop.rs index c45e1aa5fdc..019ace90271 100644 --- a/crates/egui_demo_lib/src/demo/drag_and_drop.rs +++ b/crates/egui_demo_lib/src/demo/drag_and_drop.rs @@ -58,9 +58,8 @@ pub fn drop_target( let mut fill = style.bg_fill; let mut stroke = style.bg_stroke; if is_being_dragged && !can_accept_what_is_being_dragged { - // gray out: - fill = ecolor::tint_color_towards(fill, ui.visuals().window_fill()); - stroke.color = ecolor::tint_color_towards(stroke.color, ui.visuals().window_fill()); + fill = ui.visuals().gray_out(fill); + stroke.color = ui.visuals().gray_out(stroke.color); } ui.painter().set( diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index aea2a2901ee..ced9e696488 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -516,6 +516,13 @@ pub struct Table<'a> { } impl<'a> Table<'a> { + /// Access the contained [`egui::Ui`]. + /// + /// You can use this to e.g. modify the [`egui::Style`] with [`egui::Ui::style_mut`]. + pub fn ui_mut(&mut self) -> &mut egui::Ui { + self.ui + } + /// Create table body after adding a header row pub fn body(self, add_body_contents: F) where @@ -724,6 +731,13 @@ pub struct TableBody<'a> { } impl<'a> TableBody<'a> { + /// Access the contained [`egui::Ui`]. + /// + /// You can use this to e.g. modify the [`egui::Style`] with [`egui::Ui::style_mut`]. + pub fn ui_mut(&mut self) -> &mut egui::Ui { + self.layout.ui + } + /// Where in screen-space is the table body? pub fn max_rect(&self) -> Rect { self.layout diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index 22eccd5afe9..ad13037aeb4 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -14,6 +14,11 @@ pub struct Shadow { } impl Shadow { + pub const NONE: Self = Self { + extrusion: 0.0, + color: Color32::TRANSPARENT, + }; + /// Tooltips, menus, …, for dark mode. pub fn small_dark() -> Self { Self {