mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 13:50:04 -04:00
Move more button features to classes and rename classes
This commit is contained in:
@@ -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`
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user