1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -04:00

Move more button features to classes and rename classes

This commit is contained in:
Lucas Meurer
2026-08-28 12:34:07 +02:00
parent 5d3e958ecf
commit 7a18bc9cf3
7 changed files with 183 additions and 190 deletions

View File

@@ -31,7 +31,6 @@ pub enum AtomKind<'a> {
/// Text atom. /// Text atom.
/// ///
/// Truncation within [`crate::AtomLayout`] works like this: /// Truncation within [`crate::AtomLayout`] works like this:
/// -
/// - if `wrap_mode` is not Extend /// - if `wrap_mode` is not Extend
/// - if no atom is `shrink` /// - if no atom is `shrink`
/// - the first text atom is selected and will be marked as `shrink` /// - the first text atom is selected and will be marked as `shrink`

View File

@@ -1,12 +1,13 @@
use emath::Vec2; use emath::Vec2;
use epaint::{Shadow, Stroke, text::TextWrapMode}; use epaint::Margin;
use crate::{ use crate::{
Frame, TextStyle, Frame, Style, TextStyle,
style::WidgetVisuals,
theme::StyleProvider, theme::StyleProvider,
widget_style::{ widget_style::{
BaseStyle, ButtonStyle, CheckboxStyle, HasClasses as _, LabelStyle, SELECTED_CLASS, ButtonStyle, CheckboxStyle, HasClasses as _, NO_FRAME_CLASS, BUTTON_NO_FRAME_WHEN_INACTIVE_CLASS,
SeparatorStyle, StyleArgs, TextVisuals, WidgetState, SELECTED_CLASS, SMALL_CLASS, SeparatorStyle, StyleArgs, TextVisuals, WidgetState,
}, },
}; };
@@ -15,97 +16,75 @@ use crate::{
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DefaultStyle; pub struct DefaultStyle;
impl StyleProvider<BaseStyle> for DefaultStyle { /// The text of a widget, based on the [`WidgetVisuals`] of its current state.
fn style(&mut self, modifiers: &StyleArgs<'_>) -> BaseStyle { fn text_visuals(style: &Style, widget_visuals: &WidgetVisuals) -> TextVisuals {
let StyleArgs { style, state, .. } = modifiers; TextVisuals {
let spacing = &style.spacing; color: widget_visuals.text_color(),
let widget_visuals = match state { font_id: style
WidgetState::Noninteractive => style.visuals.widgets.noninteractive, .override_font_id
WidgetState::Inactive => style.visuals.widgets.inactive, .clone()
WidgetState::Hovered => style.visuals.widgets.hovered, .unwrap_or_else(|| TextStyle::Body.resolve(style)),
WidgetState::Active => style.visuals.widgets.active,
};
BaseStyle {
frame: Frame {
fill: widget_visuals.bg_fill,
stroke: widget_visuals.bg_stroke,
corner_radius: widget_visuals.corner_radius,
inner_margin: spacing.button_padding.into(),
..Default::default()
},
stroke: widget_visuals.fg_stroke,
text: TextVisuals {
color: widget_visuals.text_color(),
font_id: modifiers
.style
.override_font_id
.clone()
.unwrap_or_else(|| TextStyle::Body.resolve(style)),
strikethrough: Stroke::NONE,
underline: Stroke::NONE,
},
}
} }
} }
impl StyleProvider<ButtonStyle> for DefaultStyle { impl StyleProvider<ButtonStyle> for DefaultStyle {
fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> ButtonStyle {
let StyleArgs { let StyleArgs {
ctx,
classes, classes,
style, style,
state, state,
.. ..
} = modifiers; } = modifiers;
let spacing = &style.spacing; let spacing = &style.spacing;
let mut widget_visuals = match state { let mut widget_visuals = *style.visuals.widgets.state(*state);
WidgetState::Noninteractive => style.visuals.widgets.noninteractive,
WidgetState::Inactive => style.visuals.widgets.inactive,
WidgetState::Hovered => style.visuals.widgets.hovered,
WidgetState::Active => style.visuals.widgets.active,
};
let mut ws: BaseStyle = ctx.get_widget_style(modifiers); if classes.has_class(SELECTED_CLASS) {
if classes.has(SELECTED_CLASS) {
let visuals = &style.visuals; let visuals = &style.visuals;
widget_visuals.weak_bg_fill = visuals.selection.bg_fill; widget_visuals.weak_bg_fill = visuals.selection.bg_fill;
widget_visuals.bg_fill = visuals.selection.bg_fill; widget_visuals.bg_fill = visuals.selection.bg_fill;
widget_visuals.fg_stroke = visuals.selection.stroke; widget_visuals.fg_stroke = visuals.selection.stroke;
ws.text.color = visuals.selection.stroke.color;
} }
ButtonStyle { let mut inner_margin: Margin = (spacing.button_padding
frame: Frame { + Vec2::splat(widget_visuals.expansion)
- Vec2::splat(widget_visuals.bg_stroke.width))
.into();
// A small button is meant to be embedded into text, so it must not add any height.
if classes.has_class(SMALL_CLASS) {
inner_margin.top = 0;
inner_margin.bottom = 0;
}
let frame = if classes.has_class(NO_FRAME_CLASS) {
// No frame at all: the button takes up no more room than its contents.
Frame::new()
} else if classes.has_class(BUTTON_NO_FRAME_WHEN_INACTIVE_CLASS) && *state == WidgetState::Inactive {
// Invisible, but as big as it will be once the user interacts with it.
Frame::new().inner_margin(inner_margin)
} else {
Frame {
fill: widget_visuals.weak_bg_fill, fill: widget_visuals.weak_bg_fill,
stroke: widget_visuals.bg_stroke, stroke: widget_visuals.bg_stroke,
corner_radius: widget_visuals.corner_radius, corner_radius: widget_visuals.corner_radius,
outer_margin: (-Vec2::splat(widget_visuals.expansion)).into(), outer_margin: (-Vec2::splat(widget_visuals.expansion)).into(),
inner_margin: (spacing.button_padding + Vec2::splat(widget_visuals.expansion) inner_margin,
- Vec2::splat(widget_visuals.bg_stroke.width))
.into(),
..Default::default() ..Default::default()
}, }
text_style: ws.text, };
ButtonStyle {
frame,
text_style: text_visuals(style, &widget_visuals),
} }
} }
} }
impl StyleProvider<CheckboxStyle> for DefaultStyle { impl StyleProvider<CheckboxStyle> for DefaultStyle {
fn style(&mut self, modifiers: &StyleArgs<'_>) -> CheckboxStyle { fn style(&mut self, modifiers: &StyleArgs<'_>) -> CheckboxStyle {
let StyleArgs { let StyleArgs { style, state, .. } = modifiers;
ctx, style, state, ..
} = modifiers;
let spacing = &style.spacing; let spacing = &style.spacing;
let widget_visuals = match state { let widget_visuals = *style.visuals.widgets.state(*state);
WidgetState::Noninteractive => style.visuals.widgets.noninteractive,
WidgetState::Inactive => style.visuals.widgets.inactive,
WidgetState::Hovered => style.visuals.widgets.hovered,
WidgetState::Active => style.visuals.widgets.active,
};
let ws: BaseStyle = ctx.get_widget_style(modifiers);
CheckboxStyle { CheckboxStyle {
frame: Frame::new(), frame: Frame::new(),
@@ -117,28 +96,8 @@ impl StyleProvider<CheckboxStyle> for DefaultStyle {
stroke: widget_visuals.bg_stroke, stroke: widget_visuals.bg_stroke,
..Default::default() ..Default::default()
}, },
text_style: ws.text, text_style: text_visuals(style, &widget_visuals),
check_stroke: ws.stroke, check_stroke: widget_visuals.fg_stroke,
}
}
}
impl StyleProvider<LabelStyle> for DefaultStyle {
fn style(&mut self, modifiers: &StyleArgs<'_>) -> LabelStyle {
let StyleArgs { ctx, .. } = modifiers;
let ws: BaseStyle = ctx.get_widget_style(modifiers);
LabelStyle {
frame: Frame {
fill: ws.frame.fill,
inner_margin: 0.0.into(),
outer_margin: 0.0.into(),
stroke: Stroke::NONE,
shadow: Shadow::NONE,
corner_radius: 0.into(),
},
text: ws.text,
wrap_mode: TextWrapMode::Wrap,
} }
} }
} }

View File

@@ -6,9 +6,7 @@ use crate::{
Id, Id,
theme::{StyleProvider, default_style::DefaultStyle}, theme::{StyleProvider, default_style::DefaultStyle},
util::IdTypeMap, util::IdTypeMap,
widget_style::{ widget_style::{ButtonStyle, CheckboxStyle, SeparatorStyle, WidgetStyle},
BaseStyle, ButtonStyle, CheckboxStyle, LabelStyle, SeparatorStyle, WidgetStyle,
},
}; };
/// The registry of [`StyleProvider`]s, one per [`WidgetStyle`] type. /// The registry of [`StyleProvider`]s, one per [`WidgetStyle`] type.
@@ -31,11 +29,6 @@ impl Default for Themes {
fn default() -> Self { fn default() -> Self {
let mut themes = IdTypeMap::default(); let mut themes = IdTypeMap::default();
themes.insert_temp::<ThemeWrap<BaseStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
themes.insert_temp::<ThemeWrap<ButtonStyle>>( themes.insert_temp::<ThemeWrap<ButtonStyle>>(
Id::NULL, Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))), Arc::new(Mutex::new(Box::new(DefaultStyle))),
@@ -51,11 +44,6 @@ impl Default for Themes {
Arc::new(Mutex::new(Box::new(DefaultStyle))), Arc::new(Mutex::new(Box::new(DefaultStyle))),
); );
themes.insert_temp::<ThemeWrap<LabelStyle>>(
Id::NULL,
Arc::new(Mutex::new(Box::new(DefaultStyle))),
);
Self { themes } Self { themes }
} }
} }

View File

@@ -5,10 +5,20 @@ use smallvec::SmallVec;
use crate::TextBuffer as _; use crate::TextBuffer as _;
/// The root class is a special class present on every top-level [`crate::Ui`]. /// The root class is a special class present on every top-level [`crate::Ui`].
pub const ROOT_CLASS: &str = "root"; pub const ROOT_CLASS: &str = "egui::root";
/// The selected class is a special class present on selected [`crate::Button`]. /// The selected class is a special class present on selected [`crate::Button`].
pub const SELECTED_CLASS: &str = "selected"; pub const SELECTED_CLASS: &str = "egui::selected";
/// The small class is a special class present on small [`crate::Button`].
pub const SMALL_CLASS: &str = "egui::small";
/// Present on a [`crate::Button`] that should have no frame at all.
pub const NO_FRAME_CLASS: &str = "egui::no_frame";
/// Present on a [`crate::Button`] that should have no frame while it is
/// [`crate::widget_style::WidgetState::Inactive`].
pub const BUTTON_NO_FRAME_WHEN_INACTIVE_CLASS: &str = "egui::button::no_frame_when_inactive";
/// A class is a static string identifier. /// A class is a static string identifier.
pub type ClassName = Cow<'static, str>; pub type ClassName = Cow<'static, str>;
@@ -17,17 +27,34 @@ pub type ClassName = Cow<'static, str>;
/// ///
/// This can be used by styling engine to compute a different style /// This can be used by styling engine to compute a different style
/// based on the set of classes present on the widget/Ui. /// based on the set of classes present on the widget/Ui.
/// Class order is preserved and may be used by a style provider for precedence.
#[derive(Debug, Default, Clone, Hash)] #[derive(Debug, Default, Clone, Hash)]
pub struct Classes { pub struct Classes {
classes: SmallVec<[ClassName; 5]>, classes: SmallVec<[ClassName; 5]>,
} }
impl Classes { impl Classes {
/// Add a class to the list if the condition is true /// Add a class to the list if the condition is true.
///
/// A class is never added twice. This never removes a class: use [`Self::set`] for that.
#[inline] #[inline]
fn add_if(&mut self, class: impl Into<ClassName>, condition: bool) { fn add_if(&mut self, class: impl Into<ClassName>, condition: bool) {
if condition { if condition {
self.classes.push(class.into()); let class = class.into();
// Always retain and push again, since order of classes can matter.
self.classes.retain(|existing| existing != &class);
self.classes.push(class);
}
}
/// Add the class if `present`, remove it otherwise.
#[inline]
fn set(&mut self, class: impl Into<ClassName>, present: bool) {
let class = class.into();
// Always retain and push again, since order of classes can matter.
self.classes.retain(|existing| existing != &class);
if present {
self.classes.push(class);
} }
} }
} }
@@ -97,8 +124,31 @@ pub trait HasClasses {
self self
} }
/// Add the given class in-place if `present`, remove it otherwise
///
/// Use this for a setter that takes a `bool`, so that the last call wins.
#[inline]
fn set_class(&mut self, class: impl Into<ClassName>, present: bool) -> &mut Self
where
Self: Sized,
{
self.classes_mut().set(class.into(), present);
self
}
/// Remove the given class in-place
#[inline]
fn remove_class(&mut self, class: impl Into<ClassName>) -> &mut Self
where
Self: Sized,
{
self.classes_mut().set(class.into(), false);
self
}
/// True if the class is present /// True if the class is present
fn has(&self, class: impl Into<ClassName>) -> bool { #[inline]
fn has_class(&self, class: impl Into<ClassName>) -> bool {
self.classes().classes.contains(&class.into()) self.classes().classes.contains(&class.into())
} }
@@ -107,3 +157,32 @@ pub trait HasClasses {
&self.classes().classes &self.classes().classes
} }
} }
#[cfg(test)]
mod tests {
use super::{Classes, HasClasses as _};
#[test]
fn setting_a_class_moves_it_to_end() {
let mut classes = Classes::default();
classes.add_class("first");
classes.add_class("updated");
classes.add_class("second");
classes.set_class("updated", true);
assert_eq!(classes.as_slice(), ["first", "second", "updated"]);
}
#[test]
fn adding_a_class_twice_moves_it_to_end() {
let mut classes = Classes::default();
classes.add_class("first");
classes.add_class("updated");
classes.add_class("second");
classes.add_class("updated");
assert_eq!(classes.as_slice(), ["first", "second", "updated"]);
}
}

View File

@@ -4,11 +4,14 @@
mod classes; mod classes;
pub use self::classes::{ClassName, Classes, HasClasses, ROOT_CLASS, SELECTED_CLASS}; pub use self::classes::{
ClassName, Classes, HasClasses, NO_FRAME_CLASS, BUTTON_NO_FRAME_WHEN_INACTIVE_CLASS, ROOT_CLASS,
SELECTED_CLASS, SMALL_CLASS,
};
use core::fmt::Debug; use core::fmt::Debug;
use epaint::{Color32, FontId, Stroke, text::TextWrapMode}; use epaint::{Color32, FontId, Stroke};
use crate::{ use crate::{
Context, Frame, Response, Style, UiStack, Context, Frame, Response, Style, UiStack,
@@ -26,24 +29,8 @@ pub struct TextVisuals {
/// Font color /// Font color
pub color: Color32, pub color: Color32,
/// Text decoration
pub underline: Stroke,
pub strikethrough: Stroke,
} }
/// General widget style
#[derive(Debug, Clone)]
pub struct BaseStyle {
pub frame: Frame,
pub text: TextVisuals,
pub stroke: Stroke,
}
impl WidgetStyle for BaseStyle {}
/// Dedicated button style /// Dedicated button style
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ButtonStyle { pub struct ButtonStyle {
@@ -77,21 +64,6 @@ pub struct CheckboxStyle {
impl WidgetStyle for CheckboxStyle {} impl WidgetStyle for CheckboxStyle {}
/// Dedicated label style
#[derive(Debug, Clone)]
pub struct LabelStyle {
/// Frame around
pub frame: Frame,
/// Text style
pub text: TextVisuals,
/// Wrap mode used
pub wrap_mode: TextWrapMode,
}
impl WidgetStyle for LabelStyle {}
/// Dedicated separator style /// Dedicated separator style
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SeparatorStyle { pub struct SeparatorStyle {

View File

@@ -1,10 +1,11 @@
use epaint::Margin;
use crate::{ use crate::{
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Atoms, Color32, CornerRadius, Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Atoms, Color32, CornerRadius,
Frame, Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2,
Vec2, Widget, WidgetInfo, WidgetText, WidgetType, Widget, WidgetInfo, WidgetText, WidgetType,
widget_style::{ButtonStyle, Classes, HasClasses, SELECTED_CLASS, WidgetState}, widget_style::{
ButtonStyle, Classes, HasClasses, NO_FRAME_CLASS, BUTTON_NO_FRAME_WHEN_INACTIVE_CLASS,
SELECTED_CLASS, SMALL_CLASS,
},
}; };
/// Clickable button with text. /// Clickable button with text.
@@ -30,9 +31,7 @@ pub struct Button<'a> {
layout: AtomLayout<'a>, layout: AtomLayout<'a>,
fill: Option<Color32>, fill: Option<Color32>,
stroke: Option<Stroke>, stroke: Option<Stroke>,
small: bool,
frame: Option<bool>, frame: Option<bool>,
frame_when_inactive: bool,
min_size: Vec2, min_size: Vec2,
corner_radius: Option<CornerRadius>, corner_radius: Option<CornerRadius>,
selected: Option<bool>, selected: Option<bool>,
@@ -49,9 +48,7 @@ impl<'a> Button<'a> {
.fallback_font(TextStyle::Button), .fallback_font(TextStyle::Button),
fill: None, fill: None,
stroke: None, stroke: None,
small: false,
frame: None, frame: None,
frame_when_inactive: true,
min_size: Vec2::ZERO, min_size: Vec2::ZERO,
corner_radius: None, corner_radius: None,
selected: None, selected: None,
@@ -72,6 +69,8 @@ impl<'a> Button<'a> {
/// # }); /// # });
/// ``` /// ```
/// ///
/// When selected, [`SELECTED_CLASS`] is added.
///
/// See also: /// See also:
/// - [`Ui::selectable_value`] /// - [`Ui::selectable_value`]
/// - [`Ui::selectable_label`] /// - [`Ui::selectable_label`]
@@ -155,28 +154,41 @@ impl<'a> Button<'a> {
} }
/// Make this a small button, suitable for embedding into text. /// Make this a small button, suitable for embedding into text.
///
/// This adds the built-in [`SMALL_CLASS`], which with the default style removes the top and
/// bottom margin.
#[inline] #[inline]
pub fn small(mut self) -> Self { pub fn small(mut self) -> Self {
self.small = true; self.add_class(SMALL_CLASS);
self self
} }
/// Turn off the frame /// Turn off the frame
///
/// If `false`, this adds the built-in [`NO_FRAME_CLASS`], which with the default style
/// removes the fill, the stroke and the margin.
///
/// Default: `ui.visuals().button_frame`.
#[inline] #[inline]
pub fn frame(mut self, frame: bool) -> Self { pub fn frame(mut self, frame: bool) -> Self {
self.frame = Some(frame); self.frame = Some(frame);
self.set_class(NO_FRAME_CLASS, !frame);
self self
} }
/// If `false`, the button will not have a frame when inactive. /// If `false`, the button will not have a frame when inactive.
/// ///
/// This adds the built-in [`BUTTON_NO_FRAME_WHEN_INACTIVE_CLASS`], which with the
/// default style removes the fill and the stroke, but keeps the margin, so the button does
/// not change size once the user interacts with it.
///
/// Default: `true`. /// Default: `true`.
/// ///
/// Note: When [`Self::frame`] (or `ui.visuals().button_frame`) is `false`, this setting /// Note: When [`Self::frame`] (or `ui.visuals().button_frame`) is `false`, this setting
/// has no effect. /// has no effect.
#[inline] #[inline]
pub fn frame_when_inactive(mut self, frame_when_inactive: bool) -> Self { pub fn frame_when_inactive(mut self, frame_when_inactive: bool) -> Self {
self.frame_when_inactive = frame_when_inactive; self.set_class(BUTTON_NO_FRAME_WHEN_INACTIVE_CLASS, !frame_when_inactive);
self self
} }
@@ -266,9 +278,13 @@ impl<'a> Button<'a> {
/// current pressed/not-pressed state will be reported to assistive /// current pressed/not-pressed state will be reported to assistive
/// technologies (e.g. screen readers). Plain buttons that never call /// technologies (e.g. screen readers). Plain buttons that never call
/// `selected` are not announced as toggles. /// `selected` are not announced as toggles.
///
/// When selected, [`SELECTED_CLASS`] is added. You should prefer calling this though over
/// just adding [`SELECTED_CLASS`] manually, since this also exposes accessibility information.
#[inline] #[inline]
pub fn selected(mut self, selected: bool) -> Self { pub fn selected(mut self, selected: bool) -> Self {
self.selected = Some(selected); self.selected = Some(selected);
self.set_class(SELECTED_CLASS, selected);
self self
} }
@@ -292,9 +308,7 @@ impl<'a> Button<'a> {
mut layout, mut layout,
fill, fill,
stroke, stroke,
small,
frame, frame,
frame_when_inactive,
mut min_size, mut min_size,
corner_radius, corner_radius,
selected, selected,
@@ -304,7 +318,7 @@ impl<'a> Button<'a> {
} = self; } = self;
// Min size height always equal or greater than interact size if not small // Min size height always equal or greater than interact size if not small
if !small { if !classes.has_class(SMALL_CLASS) {
min_size.y = min_size.y.at_least(ui.spacing().interact_size.y); min_size.y = min_size.y.at_least(ui.spacing().interact_size.y);
} }
@@ -320,29 +334,19 @@ impl<'a> Button<'a> {
let text = layout.text().map(String::from); let text = layout.text().map(String::from);
let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame); // An explicit `frame` call already updated the classes at the call site, preserving
// its order relative to user classes. Only apply the global default here.
let id = ui.next_auto_id(); if frame.is_none() {
let response: Option<Response> = ui.ctx().read_response(id); classes.add_class_if(NO_FRAME_CLASS, !ui.visuals().button_frame);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
classes.add_class_if(SELECTED_CLASS, selected.unwrap_or(false));
let ButtonStyle { frame, text_style } = ui.widget_style(id, &classes);
let mut button_padding = if has_frame_margin {
frame.inner_margin
} else {
Margin::ZERO
};
if small {
button_padding.bottom = 0;
button_padding.top = 0;
} }
let id = ui.next_auto_id();
let ButtonStyle {
mut frame,
text_style,
} = ui.widget_style(id, &classes);
// Override global style by local style // Override global style by local style
let mut frame = frame;
if let Some(fill) = fill { if let Some(fill) = fill {
frame = frame.fill(fill); frame = frame.fill(fill);
} }
@@ -353,21 +357,12 @@ impl<'a> Button<'a> {
frame = frame.stroke(stroke); frame = frame.stroke(stroke);
} }
frame = frame.inner_margin(button_padding);
// Apply the style font and color as fallback // Apply the style font and color as fallback
layout = layout layout = layout
.fallback_font(text_style.font_id.clone()) .fallback_font(text_style.font_id.clone())
.fallback_text_color(text_style.color); .fallback_text_color(text_style.color);
// Retrocompatibility with button settings let mut prepared = layout.frame(frame).min_size(min_size).allocate(ui);
layout = if has_frame_margin && (state != WidgetState::Inactive || frame_when_inactive) {
layout.frame(frame)
} else {
layout.frame(Frame::new().inner_margin(frame.inner_margin))
};
let mut prepared = layout.min_size(min_size).allocate(ui);
// Get AtomLayoutResponse, empty if not visible // Get AtomLayoutResponse, empty if not visible
let response = if ui.is_rect_visible(prepared.response.rect) { let response = if ui.is_rect_visible(prepared.response.rect) {

View File

@@ -4,9 +4,9 @@
//! based on the _classes_ set on it, and that can be edited live. //! based on the _classes_ set on it, and that can be edited live.
use eframe::egui::{ use eframe::egui::{
self, CentralPanel, Color32, Frame, Panel, self, CentralPanel, Color32, Frame, Panel, TextStyle,
theme::StyleProvider, theme::StyleProvider,
widget_style::{BaseStyle, ButtonStyle, HasClasses as _, StyleArgs, WidgetState}, widget_style::{ButtonStyle, HasClasses as _, StyleArgs, TextVisuals, WidgetState},
}; };
/// Buttons with this class are styled as a destructive action. /// Buttons with this class are styled as a destructive action.
@@ -42,14 +42,11 @@ impl StyleProvider<ButtonStyle> for MyTheme {
let StyleArgs { let StyleArgs {
classes, classes,
state, state,
ctx, style,
.. ..
} = args; } = args;
// Start from the style egui computed for a generic widget, so we inherit e.g. the font: let fill = if classes.has_class(DANGER) {
let base: BaseStyle = ctx.get_widget_style(args);
let fill = if classes.has(DANGER) {
self.danger self.danger
} else { } else {
self.normal self.normal
@@ -67,7 +64,11 @@ impl StyleProvider<ButtonStyle> for MyTheme {
.fill(fill) .fill(fill)
.corner_radius(self.corner_radius) .corner_radius(self.corner_radius)
.inner_margin(8), .inner_margin(8),
text_style: base.text, text_style: TextVisuals {
color: Color32::WHITE,
// Resolve the font from the style, so we follow the user's font sizes:
font_id: TextStyle::Button.resolve(style),
},
} }
} }
} }