diff --git a/CHANGELOG.md b/CHANGELOG.md index c19304ba37e..e30fc98e40d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,15 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG * Add `Plot::clamp_grid` to only show grid where there is data ([#2480](https://github.com/emilk/egui/pull/2480)). * Add `ScrollArea::drag_to_scroll` if you want to turn off that feature. * Add `Response::on_hover_and_drag_cursor`. -* Add `Window::default_open` ([#2539](https://github.com/emilk/egui/pull/2539)) -* Add `ProgressBar::fill` if you want to set the fill color manually. ([#2618](https://github.com/emilk/egui/pull/2618)) -* Add `Button::rounding` to enable round buttons ([#2539](https://github.com/emilk/egui/pull/2539)) +* Add `Window::default_open` ([#2539](https://github.com/emilk/egui/pull/2539)). +* Add `ProgressBar::fill` if you want to set the fill color manually. ([#2618](https://github.com/emilk/egui/pull/2618)). +* Add `Button::rounding` to enable round buttons ([#2616](https://github.com/emilk/egui/pull/2616)). +* Add `WidgetVisuals::optional_bg_color` - set it to `Color32::TRANSPARENT` to hide button backgrounds ([#2621](https://github.com/emilk/egui/pull/2621)). ### Changed 🔧 * Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)). * Improved the algorithm for picking the number of decimals to show when hovering values in the `Plot`. +* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)). ### Fixed 🐛 * Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)). diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 942e06254c4..ea7d52dedeb 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -555,7 +555,7 @@ impl CollapsingHeader { ui.painter().add(epaint::RectShape { rect: header_response.rect.expand(visuals.expansion), rounding: visuals.rounding, - fill: visuals.bg_fill, + fill: visuals.weak_bg_fill, stroke: visuals.bg_stroke, // stroke: Default::default(), }); diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 9942e899acf..911f9424279 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -162,9 +162,6 @@ impl ComboBox { let button_id = ui.make_persistent_id(id_source); ui.horizontal(|ui| { - if let Some(width) = width { - ui.spacing_mut().slider_width = width; // yes, this is ugly. Will remove later. - } let mut ir = combo_box_dyn( ui, button_id, @@ -172,6 +169,7 @@ impl ComboBox { menu_contents, icon, wrap_enabled, + width, ); if let Some(label) = label { ir.response @@ -240,6 +238,7 @@ fn combo_box_dyn<'c, R>( menu_contents: Box R + 'c>, icon: Option, wrap_enabled: bool, + width: Option, ) -> InnerResponse> { let popup_id = button_id.with("popup"); @@ -263,18 +262,20 @@ fn combo_box_dyn<'c, R>( let margin = ui.spacing().button_padding; let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| { + let icon_spacing = ui.spacing().icon_spacing; // We don't want to change width when user selects something new let full_minimum_width = if wrap_enabled { // Currently selected value's text will be wrapped if needed, so occupy the available width. ui.available_width() } else { - // Occupy at least the minimum width assigned to Slider and ComboBox. - ui.spacing().slider_width - 2.0 * margin.x + // Occupy at least the minimum width assigned to ComboBox. + let width = width.unwrap_or_else(|| ui.spacing().combo_width); + width - 2.0 * margin.x }; let icon_size = Vec2::splat(ui.spacing().icon_width); let wrap_width = if wrap_enabled { // Use the available width, currently selected value's text will be wrapped if exceeds this value. - ui.available_width() - ui.spacing().item_spacing.x - icon_size.x + ui.available_width() - icon_spacing - icon_size.x } else { // Use all the width necessary to display the currently selected value's text. f32::INFINITY @@ -288,7 +289,7 @@ fn combo_box_dyn<'c, R>( full_minimum_width } else { // Occupy at least the minimum width needed to contain the widget with the currently selected value's text. - galley.size().x + ui.spacing().item_spacing.x + icon_size.x + galley.size().x + icon_spacing + icon_size.x }; // Case : wrap_enabled : occupy all the available width. @@ -390,7 +391,7 @@ fn button_frame( epaint::RectShape { rect: outer_rect.expand(visuals.expansion), rounding: visuals.rounding, - fill: visuals.bg_fill, + fill: visuals.weak_bg_fill, stroke: visuals.bg_stroke, }, ); diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 4decca3f165..add02b22b51 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -314,6 +314,8 @@ pub fn popup_below_widget( /// /// Useful for drop-down menus (combo boxes) or suggestion menus under text fields. /// +/// The opened popup will have the same width as the parent. +/// /// You must open the popup with [`Memory::open_popup`] or [`Memory::toggle_popup`]. /// /// Returns `None` if the popup is not open. diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 089a290cf3f..46236d6d3f5 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -68,7 +68,7 @@ fn set_menu_style(style: &mut Style) { style.spacing.button_padding = vec2(2.0, 0.0); style.visuals.widgets.active.bg_stroke = Stroke::NONE; style.visuals.widgets.hovered.bg_stroke = Stroke::NONE; - style.visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT; + style.visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT; style.visuals.widgets.inactive.bg_stroke = Stroke::NONE; } @@ -180,7 +180,7 @@ fn stationary_menu_impl<'c, R>( let mut button = Button::new(title); if bar_state.open_menu.is_menu_open(menu_id) { - button = button.fill(ui.visuals().widgets.open.bg_fill); + button = button.fill(ui.visuals().widgets.open.weak_bg_fill); button = button.stroke(ui.visuals().widgets.open.bg_stroke); } @@ -443,7 +443,7 @@ impl SubMenuButton { sub_id: Id, ) -> &'a WidgetVisuals { if menu_state.is_open(sub_id) { - &ui.style().visuals.widgets.hovered + &ui.style().visuals.widgets.open } else { ui.style().interact(response) } @@ -472,7 +472,8 @@ impl SubMenuButton { text_galley.size().x + icon_galley.size().x, text_galley.size().y.max(icon_galley.size().y), ); - let desired_size = text_and_icon_size + 2.0 * button_padding; + let mut desired_size = text_and_icon_size + 2.0 * button_padding; + desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); let (rect, response) = ui.allocate_at_least(desired_size, sense); response.widget_info(|| { @@ -492,7 +493,7 @@ impl SubMenuButton { ui.painter().rect_filled( rect.expand(visuals.expansion), visuals.rounding, - visuals.bg_fill, + visuals.weak_bg_fill, ); } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 963f1b909ae..3078595d5c5 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -216,6 +216,7 @@ impl Style { pub fn interact_selectable(&self, response: &Response, selected: bool) -> WidgetVisuals { let mut visuals = *self.visuals.widgets.style(response); if selected { + visuals.weak_bg_fill = self.visuals.selection.bg_fill; visuals.bg_fill = self.visuals.selection.bg_fill; // visuals.bg_stroke = self.visuals.selection.stroke; visuals.fg_stroke = self.visuals.selection.stroke; @@ -264,8 +265,11 @@ pub struct Spacing { /// Anything clickable should be (at least) this size. pub interact_size: Vec2, // TODO(emilk): rename min_interact_size ? - /// Default width of a [`Slider`] and [`ComboBox`](crate::ComboBox). - pub slider_width: f32, // TODO(emilk): rename big_interact_size ? + /// Default width of a [`Slider`]. + pub slider_width: f32, + + /// Default (minimum) width of a [`ComboBox`](crate::ComboBox). + pub combo_width: f32, /// Default width of a [`TextEdit`]. pub text_edit_width: f32, @@ -533,7 +537,7 @@ impl Visuals { // TODO(emilk): replace with an alpha #[inline(always)] pub fn fade_out_to_color(&self) -> Color32 { - self.widgets.noninteractive.bg_fill + self.widgets.noninteractive.weak_bg_fill } /// Returned a "grayed out" version of the given color. @@ -594,9 +598,17 @@ impl Widgets { #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct WidgetVisuals { - /// Background color of widget. + /// Background color of widgets that must have a background fill, + /// such as the slider background, a checkbox background, or a radio button background. + /// + /// Must never be [`Color32::TRANSPARENT`]. pub bg_fill: Color32, + /// Background color of widgets that can _optionally_ have a background fill, such as buttons. + /// + /// May be [`Color32::TRANSPARENT`]. + pub weak_bg_fill: Color32, + /// For surrounding rectangle of things that need it, /// like buttons, the box of the checkbox, etc. /// Should maybe be called `frame_stroke`. @@ -684,6 +696,7 @@ impl Default for Spacing { indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing` interact_size: vec2(40.0, 18.0), slider_width: 100.0, + combo_width: 100.0, text_edit_width: 280.0, icon_width: 14.0, icon_width_inner: 8.0, @@ -717,8 +730,8 @@ impl Visuals { widgets: Widgets::default(), selection: Selection::default(), hyperlink_color: Color32::from_rgb(90, 170, 255), - faint_bg_color: Color32::from_gray(35), - extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background + faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so + extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background 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 @@ -751,8 +764,8 @@ impl Visuals { widgets: Widgets::light(), selection: Selection::light(), hyperlink_color: Color32::from_rgb(0, 155, 255), - faint_bg_color: Color32::from_gray(242), - extreme_bg_color: Color32::from_gray(255), // e.g. TextEdit background + faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so + extreme_bg_color: Color32::from_gray(255), // e.g. TextEdit background 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 @@ -801,6 +814,7 @@ impl Widgets { pub fn dark() -> Self { Self { noninteractive: WidgetVisuals { + weak_bg_fill: Color32::from_gray(27), 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 @@ -808,13 +822,15 @@ impl Widgets { expansion: 0.0, }, inactive: WidgetVisuals { - bg_fill: Color32::from_gray(60), // button background + weak_bg_fill: Color32::from_gray(60), // button background + bg_fill: Color32::from_gray(60), // checkbox background bg_stroke: Default::default(), fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text rounding: Rounding::same(2.0), expansion: 0.0, }, hovered: WidgetVisuals { + weak_bg_fill: Color32::from_gray(70), bg_fill: Color32::from_gray(70), bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button fg_stroke: Stroke::new(1.5, Color32::from_gray(240)), @@ -822,6 +838,7 @@ impl Widgets { expansion: 1.0, }, active: WidgetVisuals { + weak_bg_fill: Color32::from_gray(55), bg_fill: Color32::from_gray(55), bg_stroke: Stroke::new(1.0, Color32::WHITE), fg_stroke: Stroke::new(2.0, Color32::WHITE), @@ -829,6 +846,7 @@ impl Widgets { expansion: 1.0, }, open: WidgetVisuals { + weak_bg_fill: Color32::from_gray(27), bg_fill: Color32::from_gray(27), bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), fg_stroke: Stroke::new(1.0, Color32::from_gray(210)), @@ -841,6 +859,7 @@ impl Widgets { pub fn light() -> Self { Self { noninteractive: WidgetVisuals { + weak_bg_fill: Color32::from_gray(248), 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 @@ -848,13 +867,15 @@ impl Widgets { expansion: 0.0, }, inactive: WidgetVisuals { - bg_fill: Color32::from_gray(230), // button background + weak_bg_fill: Color32::from_gray(230), // button background + bg_fill: Color32::from_gray(230), // checkbox background bg_stroke: Default::default(), fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text rounding: Rounding::same(2.0), expansion: 0.0, }, hovered: WidgetVisuals { + weak_bg_fill: Color32::from_gray(220), bg_fill: Color32::from_gray(220), bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button fg_stroke: Stroke::new(1.5, Color32::BLACK), @@ -862,6 +883,7 @@ impl Widgets { expansion: 1.0, }, active: WidgetVisuals { + weak_bg_fill: Color32::from_gray(165), bg_fill: Color32::from_gray(165), bg_stroke: Stroke::new(1.0, Color32::BLACK), fg_stroke: Stroke::new(2.0, Color32::BLACK), @@ -869,6 +891,7 @@ impl Widgets { expansion: 1.0, }, open: WidgetVisuals { + weak_bg_fill: Color32::from_gray(220), bg_fill: Color32::from_gray(220), bg_stroke: Stroke::new(1.0, Color32::from_gray(160)), fg_stroke: Stroke::new(1.0, Color32::BLACK), @@ -984,6 +1007,7 @@ impl Spacing { indent, interact_size, slider_width, + combo_width, text_edit_width, icon_width, icon_width_inner, @@ -1012,6 +1036,10 @@ impl Spacing { ui.add(DragValue::new(slider_width).clamp_range(0.0..=1000.0)); ui.label("Slider width"); }); + ui.horizontal(|ui| { + ui.add(DragValue::new(combo_width).clamp_range(0.0..=1000.0)); + ui.label("ComboBox width"); + }); ui.horizontal(|ui| { ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); ui.label("TextEdit width"); @@ -1185,13 +1213,17 @@ impl Selection { impl WidgetVisuals { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { - bg_fill, + weak_bg_fill, + bg_fill: mandatory_bg_fill, bg_stroke, rounding, fg_stroke, expansion, } = self; - ui_color(ui, bg_fill, "background fill"); + ui_color(ui, weak_bg_fill, "optional background fill") + .on_hover_text("For buttons, combo-boxes, etc"); + ui_color(ui, mandatory_bg_fill, "mandatory background fill") + .on_hover_text("For checkboxes, sliders, etc"); stroke_ui(ui, bg_stroke, "background stroke"); rounding_ui(ui, rounding); @@ -1270,7 +1302,7 @@ impl Visuals { } = self; ui.collapsing("Background Colors", |ui| { - ui_color(ui, &mut widgets.inactive.bg_fill, "Buttons"); + ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons"); ui_color(ui, window_fill, "Windows"); ui_color(ui, panel_fill, "Panels"); ui_color(ui, faint_bg_color, "Faint accent").on_hover_text( diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index e485dbb9337..8f4ad6bbe93 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -180,10 +180,10 @@ impl Widget for Button { desired_size.x += ui.spacing().item_spacing.x + shortcut_text.size().x; desired_size.y = desired_size.y.max(shortcut_text.size().y); } + desired_size += 2.0 * button_padding; if !small { desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); } - desired_size += 2.0 * button_padding; desired_size = desired_size.at_least(min_size); let (rect, response) = ui.allocate_at_least(desired_size, sense); @@ -193,7 +193,7 @@ impl Widget for Button { let visuals = ui.style().interact(&response); if frame { - let fill = fill.unwrap_or(visuals.bg_fill); + let fill = fill.unwrap_or(visuals.weak_bg_fill); let stroke = stroke.unwrap_or(visuals.bg_stroke); let rounding = rounding.unwrap_or(visuals.rounding); ui.painter() @@ -540,7 +540,7 @@ impl Widget for ImageButton { ( expansion, visuals.rounding, - visuals.bg_fill, + visuals.weak_bg_fill, visuals.bg_stroke, ) } else { diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index 6762a2dd209..89f4b0bf28e 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -127,10 +127,8 @@ impl Widget for ProgressBar { + vec2(-rounding, 0.0) }) .collect(); - ui.painter().add(Shape::line( - points, - Stroke::new(2.0, visuals.faint_bg_color), - )); + ui.painter() + .add(Shape::line(points, Stroke::new(2.0, visuals.text_color()))); } if let Some(text_kind) = text { diff --git a/crates/egui/src/widgets/selected_label.rs b/crates/egui/src/widgets/selected_label.rs index 2f2d49d23d7..86024ae3538 100644 --- a/crates/egui/src/widgets/selected_label.rs +++ b/crates/egui/src/widgets/selected_label.rs @@ -64,8 +64,12 @@ impl Widget for SelectableLabel { if selected || response.hovered() || response.has_focus() { let rect = rect.expand(visuals.expansion); - ui.painter() - .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); + ui.painter().rect( + rect, + visuals.rounding, + visuals.weak_bg_fill, + visuals.bg_stroke, + ); } text.paint_with_visuals(ui.painter(), text_pos, &visuals); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 0142e2e3f63..9d03fa06d20 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -624,11 +624,7 @@ impl<'a> Slider<'a> { rect: rail_rect, rounding: ui.visuals().widgets.inactive.rounding, fill: ui.visuals().widgets.inactive.bg_fill, - // fill: visuals.bg_fill, - // fill: ui.visuals().extreme_bg_color, stroke: Default::default(), - // stroke: visuals.bg_stroke, - // stroke: ui.visuals().widgets.inactive.bg_stroke, }); let center = self.marker_center(position_1d, &rail_rect); diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 22d4ac06d37..d91ae395e74 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -175,6 +175,8 @@ impl WidgetGallery { egui::ComboBox::from_label("Take your pick") .selected_text(format!("{:?}", radio)) .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_min_width(60.0); ui.selectable_value(radio, Enum::First, "First"); ui.selectable_value(radio, Enum::Second, "Second"); ui.selectable_value(radio, Enum::Third, "Third"); diff --git a/crates/egui_extras/src/datepicker/button.rs b/crates/egui_extras/src/datepicker/button.rs index 9c5cb11188f..2a07ba9c784 100644 --- a/crates/egui_extras/src/datepicker/button.rs +++ b/crates/egui_extras/src/datepicker/button.rs @@ -76,7 +76,7 @@ impl<'a> Widget for DatePickerButton<'a> { } let mut button = Button::new(text); if button_state.picker_visible { - button = button.fill(visuals.bg_fill).stroke(visuals.bg_stroke); + button = button.fill(visuals.weak_bg_fill).stroke(visuals.bg_stroke); } let mut button_response = ui.add(button); if button_response.clicked() {