diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 13728413a..ac307b0b5 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -372,7 +372,7 @@ impl ViewportRepaintInfo { // ---------------------------------------------------------------------------- #[derive(Default)] -pub(crate) struct ContextImpl { +struct ContextImpl { fonts: Option, font_definitions: FontDefinitions, @@ -414,7 +414,7 @@ pub(crate) struct ContextImpl { loaders: Arc, - pub(crate) themes: theme::Themes, + themes: theme::Themes, } impl ContextImpl { @@ -767,7 +767,7 @@ impl Context { } /// Do read-write (exclusive access) transaction on Context - pub(crate) fn write(&self, writer: impl FnOnce(&mut ContextImpl) -> R) -> R { + fn write(&self, writer: impl FnOnce(&mut ContextImpl) -> R) -> R { writer(&mut self.0.write()) } @@ -2107,7 +2107,10 @@ impl Context { /// If a theme is already registered for this widget, this is a no-op (useful for `eframe::run_simple_native`). /// /// If you want to add the theme anyway, use [`Self::replace_widget_theme`] instead. - #[cfg(feature = "experimental")] + /// + /// The types you need to call this (e.g. `StyleProvider`) are only public + /// with the `experimental_theme` feature. + #[cfg_attr(not(feature = "experimental"), doc(hidden))] pub fn add_widget_theme( &self, theme: impl theme::StyleProvider + Send + Sync + 'static, @@ -2119,7 +2122,10 @@ impl Context { /// /// Overwrite any theme already registered for the specified widget [`WidgetStyle`]. /// This allow to live edit a theme. - #[cfg(feature = "experimental")] + /// + /// The types you need to call this (e.g. `StyleProvider`) are only public + /// with the `experimental_theme` feature. + #[cfg_attr(not(feature = "experimental"), doc(hidden))] pub fn replace_widget_theme( &self, theme: impl theme::StyleProvider + Send + Sync + 'static, diff --git a/crates/egui/src/theme/default_style.rs b/crates/egui/src/theme/default_style.rs index 571945793..4ae6267ae 100644 --- a/crates/egui/src/theme/default_style.rs +++ b/crates/egui/src/theme/default_style.rs @@ -21,11 +21,9 @@ impl DefaultStyle { /// [`Context::default`] does this. Any theme you register yourself /// replaces the default one for that widget style. pub fn register(ctx: &Context) { - ctx.write(|ctx| { - ctx.themes.register::(Self, false); - ctx.themes.register::(Self, false); - ctx.themes.register::(Self, false); - }); + ctx.add_widget_theme::(Self); + ctx.add_widget_theme::(Self); + ctx.add_widget_theme::(Self); } } diff --git a/crates/egui/src/theme/themes.rs b/crates/egui/src/theme/themes.rs index 27ca343d4..d816f69b3 100644 --- a/crates/egui/src/theme/themes.rs +++ b/crates/egui/src/theme/themes.rs @@ -12,6 +12,8 @@ use crate::{Id, theme::StyleProvider, util::IdTypeMap, widget_style::WidgetStyle /// /// A default provider is registered for every built-in style. Register your /// own with [`crate::Context::add_widget_theme`] or [`crate::Context::replace_widget_theme`]. +/// +/// The [`crate::theme::DefaultStyle`] is registered in [`crate::Context::default`]. #[derive(Default)] pub struct Themes { themes: IdTypeMap, diff --git a/crates/egui/src/widget_style/classes.rs b/crates/egui/src/widget_style/classes.rs index 41aabf3f4..eb05c4426 100644 --- a/crates/egui/src/widget_style/classes.rs +++ b/crates/egui/src/widget_style/classes.rs @@ -1,11 +1,152 @@ -use std::{borrow::Cow, fmt}; - use smallvec::SmallVec; +use std::borrow::{Borrow, Cow}; +use std::fmt; +use std::sync::Arc; use crate::TextBuffer as _; -/// A class is a static string identifier. -pub type ClassName = Cow<'static, str>; +/// A class, used to customize widget styling and behavior. +#[derive(Clone)] +pub struct ClassName(ClassNameInner); + +#[derive(Clone)] +enum ClassNameInner { + Static(&'static str), + Owned(Arc), +} + +impl ClassName { + /// A class from a string known at compile time. This never allocates. + #[inline] + pub const fn from_static(class: &'static str) -> Self { + Self(ClassNameInner::Static(class)) + } + + /// A class from anything that converts into one. + #[inline] + pub fn new(class: impl Into) -> Self { + class.into() + } + + /// The class as a string. + #[inline] + pub fn as_str(&self) -> &str { + match &self.0 { + ClassNameInner::Static(class) => class, + ClassNameInner::Owned(class) => class, + } + } +} + +impl From<&'static str> for ClassName { + #[inline] + fn from(class: &'static str) -> Self { + Self::from_static(class) + } +} + +impl From for ClassName { + #[inline] + fn from(class: String) -> Self { + Self(ClassNameInner::Owned(class.into())) + } +} + +impl From<&String> for ClassName { + #[inline] + fn from(class: &String) -> Self { + Self(ClassNameInner::Owned(class.as_str().into())) + } +} + +impl From> for ClassName { + #[inline] + fn from(class: Arc) -> Self { + Self(ClassNameInner::Owned(class)) + } +} + +impl From> for ClassName { + #[inline] + fn from(class: Cow<'static, str>) -> Self { + match class { + Cow::Borrowed(class) => Self::from_static(class), + Cow::Owned(class) => class.into(), + } + } +} + +impl From<&ClassName> for ClassName { + #[inline] + fn from(class: &ClassName) -> Self { + class.clone() + } +} + +impl Borrow for ClassName { + #[inline] + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl AsRef for ClassName { + #[inline] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl std::ops::Deref for ClassName { + type Target = str; + + #[inline] + fn deref(&self) -> &str { + self.as_str() + } +} + +impl PartialEq for ClassName { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Eq for ClassName {} + +impl PartialEq for ClassName { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl PartialEq<&str> for ClassName { + #[inline] + fn eq(&self, other: &&str) -> bool { + self.as_str() == *other + } +} + +impl std::hash::Hash for ClassName { + #[inline] + fn hash(&self, state: &mut H) { + self.as_str().hash(state); + } +} + +impl fmt::Debug for ClassName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} + +impl fmt::Display for ClassName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} /// Classes are string identifier that can be set on widget/Ui. /// @@ -62,13 +203,13 @@ impl core::fmt::Display for Classes { } } -/// Any widgets supporting [`Classes`] must implement this trait +/// Any widgets supporting [`Classes`] must implement this trait. pub trait HasClasses { fn classes(&self) -> &Classes; fn classes_mut(&mut self) -> &mut Classes; - /// Add the given class by consuming `self` + /// Add the given class by consuming `self`. #[inline] fn with_class(mut self, class: impl Into) -> Self where @@ -78,9 +219,7 @@ pub trait HasClasses { self } - /// Add all the given classes by consuming `self` - /// - /// Useful to forward the classes of a composite widget to the widgets it is built from. + /// Add all the given classes by consuming `self`. #[inline] fn with_classes(mut self, classes: Classes) -> Self where @@ -90,7 +229,7 @@ pub trait HasClasses { self } - /// Add the given class by consuming `self` if the condition is true + /// Add the given class by consuming `self` if the condition is true. #[inline] fn with_class_if(mut self, class: impl Into, condition: bool) -> Self where @@ -100,7 +239,7 @@ pub trait HasClasses { self } - /// Add the given class in-place + /// Add the given class in-place. #[inline] fn add_class(&mut self, class: impl Into) -> &mut Self where @@ -110,13 +249,14 @@ pub trait HasClasses { self } + /// Add all the given classes. #[inline] fn add_classes(&mut self, classes: Classes) -> &mut Self { self.classes_mut().classes.extend(classes.classes); self } - /// Add the given class in-place if the condition is true + /// Add the given class in-place if the condition is true. #[inline] fn add_class_if(&mut self, class: impl Into, condition: bool) -> &mut Self where @@ -126,7 +266,7 @@ pub trait HasClasses { self } - /// Add the given class in-place if `present`, remove it otherwise + /// 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] @@ -138,7 +278,7 @@ pub trait HasClasses { self } - /// Remove the given class in-place + /// Remove the given class in-place. #[inline] fn remove_class(&mut self, class: impl Into) -> &mut Self where @@ -148,13 +288,13 @@ pub trait HasClasses { self } - /// True if the class is present + /// True if the class is present. #[inline] fn has_class(&self, class: impl Into) -> bool { self.classes().classes.contains(&class.into()) } - /// The list of class + /// The list of class. fn as_slice(&self) -> &[ClassName] { &self.classes().classes } diff --git a/crates/egui/src/widget_style/mod.rs b/crates/egui/src/widget_style/mod.rs index 81efe41d8..546c0d523 100644 --- a/crates/egui/src/widget_style/mod.rs +++ b/crates/egui/src/widget_style/mod.rs @@ -8,8 +8,10 @@ pub use self::classes::{ClassName, Classes, HasClasses}; /// Built-in classes shared by all widgets. pub mod class { + use super::ClassName; + /// Present on every top-level [`crate::Ui`]. - pub const ROOT: &str = "egui::root"; + pub const ROOT: ClassName = ClassName::from_static("egui::root"); } use core::fmt::Debug; diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 771f34d65..4e51d2362 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -2,7 +2,7 @@ use crate::{ Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Atoms, Color32, CornerRadius, Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType, - widget_style::{ButtonStyle, Classes, HasClasses}, + widget_style::{ButtonStyle, ClassName, Classes, HasClasses}, }; /// Clickable button with text. @@ -38,20 +38,20 @@ pub struct Button<'a> { impl<'a> Button<'a> { /// Present on a selected button. - pub const CLASS_SELECTED: &'static str = "egui::selected"; + pub const CLASS_SELECTED: ClassName = ClassName::from_static("egui::selected"); /// Present on a small button. - pub const CLASS_SMALL: &'static str = "egui::small"; + pub const CLASS_SMALL: ClassName = ClassName::from_static("egui::small"); /// Present on a button that should have no frame at all. - pub const CLASS_NO_FRAME: &'static str = "egui::no_frame"; + pub const CLASS_NO_FRAME: ClassName = ClassName::from_static("egui::no_frame"); /// Present on a button that should have a frame, even when the global default is frameless. - pub const CLASS_FRAME: &'static str = "egui::frame"; + pub const CLASS_FRAME: ClassName = ClassName::from_static("egui::frame"); /// Present on a button that should have no frame while it is inactive. - pub const CLASS_HIDE_FRAME_WHEN_INACTIVE: &'static str = - "egui::button::hide_frame_when_inactive"; + pub const CLASS_HIDE_FRAME_WHEN_INACTIVE: ClassName = + ClassName::from_static("egui::button::hide_frame_when_inactive"); pub fn new(atoms: impl IntoAtoms<'a>) -> Self { Self { @@ -168,9 +168,8 @@ impl<'a> Button<'a> { /// This adds the built-in [`Self::CLASS_SMALL`], which with the default style removes the top and /// bottom margin. #[inline] - pub fn small(mut self) -> Self { - self.add_class(Self::CLASS_SMALL); - self + pub fn small(self) -> Self { + self.with_class(Self::CLASS_SMALL) } /// Turn off the frame