Remove `SelectableLabel` (#7277)

* part of https://github.com/emilk/egui/issues/7264
* removes SelectableLabel (Use `Button::selectable` instead)
* updates `Ui::selectable_value/label` with IntoAtoms support

Had to make some changes to `Button` since the SelecatbleLabel had no
frame unless selected.
This commit is contained in:
Lucas Meurer 2025-07-03 16:34:47 +02:00 committed by GitHub
parent 2b62c68598
commit 47a2bb10b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 63 additions and 103 deletions

View File

@ -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<WidgetText>) -> 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<WidgetText>) -> 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<Value: PartialEq>(
/// 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<WidgetText>,
text: impl IntoAtoms<'a>,
) -> Response {
let mut response = self.selectable_label(*current_value == selected_value, text);
if response.clicked() && *current_value != selected_value {

View File

@ -29,6 +29,7 @@ pub struct Button<'a> {
stroke: Option<Stroke>,
small: bool,
frame: Option<bool>,
frame_when_inactive: bool,
min_size: Vec2,
corner_radius: Option<CornerRadius>,
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

View File

@ -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,

View File

@ -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<WidgetText>) -> 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 {}

View File

@ -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() {