diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 3738e6e5..c24ca211 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -27,7 +27,7 @@ use crate::{ vec2, widgets, widgets::{ Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, RadioButton, - SelectableLabel, Separator, Spinner, TextEdit, Widget, color_picker, + Separator, Spinner, TextEdit, Widget, color_picker, }, }; // ---------------------------------------------------------------------------- @@ -2077,13 +2077,13 @@ impl Ui { Checkbox::new(checked, atoms).ui(self) } - /// Acts like a checkbox, but looks like a [`SelectableLabel`]. + /// Acts like a checkbox, but looks like a [`Button::selectable`]. /// /// Click to toggle to bool. /// /// See also [`Self::checkbox`]. - pub fn toggle_value(&mut self, selected: &mut bool, text: impl Into) -> Response { - let mut response = self.selectable_label(*selected, text); + pub fn toggle_value<'a>(&mut self, selected: &mut bool, atoms: impl IntoAtoms<'a>) -> Response { + let mut response = self.selectable_label(*selected, atoms); if response.clicked() { *selected = !*selected; response.mark_changed(); @@ -2134,10 +2134,10 @@ impl Ui { /// Show a label which can be selected or not. /// - /// See also [`SelectableLabel`] and [`Self::toggle_value`]. + /// See also [`Button::selectable`] and [`Self::toggle_value`]. #[must_use = "You should check if the user clicked this with `if ui.selectable_label(…).clicked() { … } "] - pub fn selectable_label(&mut self, checked: bool, text: impl Into) -> Response { - SelectableLabel::new(checked, text).ui(self) + pub fn selectable_label<'a>(&mut self, checked: bool, text: impl IntoAtoms<'a>) -> Response { + Button::selectable(checked, text).ui(self) } /// Show selectable text. It is selected if `*current_value == selected_value`. @@ -2145,12 +2145,12 @@ impl Ui { /// /// Example: `ui.selectable_value(&mut my_enum, Enum::Alternative, "Alternative")`. /// - /// See also [`SelectableLabel`] and [`Self::toggle_value`]. - pub fn selectable_value( + /// See also [`Button::selectable`] and [`Self::toggle_value`]. + pub fn selectable_value<'a, Value: PartialEq>( &mut self, current_value: &mut Value, selected_value: Value, - text: impl Into, + text: impl IntoAtoms<'a>, ) -> Response { let mut response = self.selectable_label(*current_value == selected_value, text); if response.clicked() && *current_value != selected_value { diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index aa75eabd..d836c070 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -29,6 +29,7 @@ pub struct Button<'a> { stroke: Option, small: bool, frame: Option, + frame_when_inactive: bool, min_size: Vec2, corner_radius: Option, selected: bool, @@ -44,6 +45,7 @@ impl<'a> Button<'a> { stroke: None, small: false, frame: None, + frame_when_inactive: true, min_size: Vec2::ZERO, corner_radius: None, selected: false, @@ -52,6 +54,27 @@ impl<'a> Button<'a> { } } + /// Show a selectable button. + /// + /// Equivalent to: + /// ```rust + /// # use egui::{Button, IntoAtoms, __run_test_ui}; + /// # __run_test_ui(|ui| { + /// let selected = true; + /// ui.add(Button::new("toggle me").selected(selected).frame_when_inactive(!selected).frame(true)); + /// # }); + /// ``` + /// + /// See also: + /// - [`Ui::selectable_value`] + /// - [`Ui::selectable_label`] + pub fn selectable(selected: bool, atoms: impl IntoAtoms<'a>) -> Self { + Self::new(atoms) + .selected(selected) + .frame_when_inactive(selected) + .frame(true) + } + /// Creates a button with an image. The size of the image as displayed is defined by the provided size. /// /// Note: In contrast to [`Button::new`], this limits the image size to the default font height @@ -138,6 +161,18 @@ impl<'a> Button<'a> { self } + /// If `false`, the button will not have a frame when inactive. + /// + /// Default: `true`. + /// + /// Note: When [`Self::frame`] (or `ui.visuals().button_frame`) is `false`, this setting + /// has no effect. + #[inline] + pub fn frame_when_inactive(mut self, frame_when_inactive: bool) -> Self { + self.frame_when_inactive = frame_when_inactive; + self + } + /// By default, buttons senses clicks. /// Change this to a drag-button with `Sense::drag()`. #[inline] @@ -220,6 +255,7 @@ impl<'a> Button<'a> { stroke, small, frame, + frame_when_inactive, mut min_size, corner_radius, selected, @@ -243,9 +279,9 @@ impl<'a> Button<'a> { let text = layout.text().map(String::from); - let has_frame = frame.unwrap_or_else(|| ui.visuals().button_frame); + let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame); - let mut button_padding = if has_frame { + let mut button_padding = if has_frame_margin { ui.spacing().button_padding } else { Vec2::ZERO @@ -262,13 +298,22 @@ impl<'a> Button<'a> { let response = if ui.is_rect_visible(prepared.response.rect) { let visuals = ui.style().interact_selectable(&prepared.response, selected); + let visible_frame = if frame_when_inactive { + has_frame_margin + } else { + has_frame_margin + && (prepared.response.hovered() + || prepared.response.is_pointer_button_down_on() + || prepared.response.has_focus()) + }; + if image_tint_follows_text_color { prepared.map_images(|image| image.tint(visuals.text_color())); } prepared.fallback_text_color = visuals.text_color(); - if has_frame { + if visible_frame { let stroke = stroke.unwrap_or(visuals.bg_stroke); let fill = fill.unwrap_or(visuals.weak_bg_fill); prepared.frame = prepared diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index d303b181..9cf003c9 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -22,6 +22,8 @@ mod slider; mod spinner; pub mod text_edit; +#[expect(deprecated)] +pub use self::selected_label::SelectableLabel; pub use self::{ button::Button, checkbox::Checkbox, @@ -35,7 +37,6 @@ pub use self::{ label::Label, progress_bar::ProgressBar, radio_button::RadioButton, - selected_label::SelectableLabel, separator::Separator, slider::{Slider, SliderClamping, SliderOrientation}, spinner::Spinner, diff --git a/crates/egui/src/widgets/selected_label.rs b/crates/egui/src/widgets/selected_label.rs index 4b2ee9ae..da18b5fe 100644 --- a/crates/egui/src/widgets/selected_label.rs +++ b/crates/egui/src/widgets/selected_label.rs @@ -1,88 +1,2 @@ -use crate::{ - NumExt as _, Response, Sense, TextStyle, Ui, Widget, WidgetInfo, WidgetText, WidgetType, -}; - -/// One out of several alternatives, either selected or not. -/// Will mark selected items with a different background color. -/// An alternative to [`crate::RadioButton`] and [`crate::Checkbox`]. -/// -/// Usually you'd use [`Ui::selectable_value`] or [`Ui::selectable_label`] instead. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// #[derive(PartialEq)] -/// enum Enum { First, Second, Third } -/// let mut my_enum = Enum::First; -/// -/// ui.selectable_value(&mut my_enum, Enum::First, "First"); -/// -/// // is equivalent to: -/// -/// if ui.add(egui::SelectableLabel::new(my_enum == Enum::First, "First")).clicked() { -/// my_enum = Enum::First -/// } -/// # }); -/// ``` -#[must_use = "You should put this widget in a ui with `ui.add(widget);`"] -pub struct SelectableLabel { - selected: bool, - text: WidgetText, -} - -impl SelectableLabel { - pub fn new(selected: bool, text: impl Into) -> Self { - Self { - selected, - text: text.into(), - } - } -} - -impl Widget for SelectableLabel { - fn ui(self, ui: &mut Ui) -> Response { - let Self { selected, text } = self; - - let button_padding = ui.spacing().button_padding; - let total_extra = button_padding + button_padding; - - let wrap_width = ui.available_width() - total_extra.x; - let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - - let mut desired_size = total_extra + galley.size(); - desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); - let (rect, response) = ui.allocate_at_least(desired_size, Sense::click()); - response.widget_info(|| { - WidgetInfo::selected( - WidgetType::SelectableLabel, - ui.is_enabled(), - selected, - galley.text(), - ) - }); - - if ui.is_rect_visible(response.rect) { - let text_pos = ui - .layout() - .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) - .min; - - let visuals = ui.style().interact_selectable(&response, selected); - - if selected || response.hovered() || response.highlighted() || response.has_focus() { - let rect = rect.expand(visuals.expansion); - - ui.painter().rect( - rect, - visuals.corner_radius, - visuals.weak_bg_fill, - visuals.bg_stroke, - epaint::StrokeKind::Inside, - ); - } - - ui.painter().galley(text_pos, galley, visuals.text_color()); - } - - response - } -} +#[deprecated = "SelectableLabel has been removed. Use Button::selectable() instead"] +pub struct SelectableLabel {} diff --git a/crates/egui_demo_lib/src/demo/password.rs b/crates/egui_demo_lib/src/demo/password.rs index f22b5aa8..04b3c6f3 100644 --- a/crates/egui_demo_lib/src/demo/password.rs +++ b/crates/egui_demo_lib/src/demo/password.rs @@ -27,7 +27,7 @@ pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response { let result = ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // Toggle the `show_plaintext` bool with a button: let response = ui - .add(egui::SelectableLabel::new(show_plaintext, "👁")) + .selectable_label(show_plaintext, "👁") .on_hover_text("Show/hide password"); if response.clicked() {