1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

final draft

This commit is contained in:
adrien
2026-01-13 13:38:07 +01:00
parent fd2b45940c
commit 8627de6808
5 changed files with 154 additions and 97 deletions

View File

@@ -7,6 +7,7 @@ use emath::GuiRounding as _;
use epaint::mutex::RwLock;
use crate::containers::menu;
use crate::widget_style::{HasModifiers, StyleModifiers};
use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
// ----------------------------------------------------------------------------
@@ -89,6 +90,9 @@ pub struct Ui {
/// This is an optimization, so we don't call [`Ui::remember_min_rect`] multiple times at the
/// end of a [`Ui::scope`].
min_rect_already_remembered: bool,
/// Modifiers for style purpose
modifiers: StyleModifiers,
}
/// Allow using [`Ui`] like a [`Context`].
@@ -161,6 +165,7 @@ impl Ui {
stack: Arc::new(ui_stack),
sense,
min_rect_already_remembered: false,
modifiers: StyleModifiers::default(),
};
if let Some(accessibility_parent) = accessibility_parent {
@@ -323,6 +328,7 @@ impl Ui {
stack: Arc::new(ui_stack),
sense,
min_rect_already_remembered: false,
modifiers: StyleModifiers::default(),
};
if disabled {
@@ -3145,6 +3151,16 @@ impl Drop for Ui {
}
}
impl HasModifiers for Ui {
fn modifiers(&self) -> &StyleModifiers {
&self.modifiers
}
fn modifiers_mut(&mut self) -> &mut StyleModifiers {
&mut self.modifiers
}
}
/// Show this rectangle to the user if certain debug options are set.
#[cfg(debug_assertions)]
fn register_rect(ui: &Ui, rect: Rect) {

View File

@@ -2,7 +2,7 @@ use emath::Vec2;
use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode};
use crate::{
Frame, Id, Response, Style, TextStyle, Theme, Ui,
Frame, Response, Style, TextStyle, Theme,
style::{WidgetVisuals, Widgets},
};
@@ -107,8 +107,8 @@ impl Response {
}
impl Style {
pub fn widget_style(&self, state: WidgetState) -> WidgetStyle {
let visuals = self.visuals.widgets.state(state);
pub fn widget_style(&self, modifier: &StyleModifiers) -> WidgetStyle {
let visuals = self.visuals.widgets.state(modifier.state);
let font_id = self.override_font_id.clone();
WidgetStyle {
frame: Frame {
@@ -123,19 +123,19 @@ impl Style {
color: self
.visuals
.override_text_color
.unwrap_or(visuals.text_color()),
font_id: font_id.unwrap_or(TextStyle::Body.resolve(self)),
.unwrap_or_else(|| visuals.text_color()),
font_id: font_id.unwrap_or_else(|| TextStyle::Body.resolve(self)),
strikethrough: Stroke::NONE,
underline: Stroke::NONE,
},
}
}
pub fn button_style(&self, state: WidgetState, selected: bool) -> ButtonStyle {
let mut visuals = *self.visuals.widgets.state(state);
let mut ws = self.widget_style(state);
pub fn button_style(&self, modifier: &StyleModifiers) -> ButtonStyle {
let mut visuals = *self.visuals.widgets.state(modifier.state);
let mut ws = self.widget_style(modifier);
if selected {
if modifier.has("selected") {
visuals.weak_bg_fill = self.visuals.selection.bg_fill;
visuals.bg_fill = self.visuals.selection.bg_fill;
visuals.fg_stroke = self.visuals.selection.stroke;
@@ -157,9 +157,9 @@ impl Style {
}
}
pub fn checkbox_style(&self, state: WidgetState) -> CheckboxStyle {
let visuals = self.visuals.widgets.state(state);
let ws = self.widget_style(state);
pub fn checkbox_style(&self, modifier: &StyleModifiers) -> CheckboxStyle {
let visuals = self.visuals.widgets.state(modifier.state);
let ws = self.widget_style(modifier);
CheckboxStyle {
frame: Frame::new(),
checkbox_size: self.spacing.icon_width,
@@ -175,8 +175,8 @@ impl Style {
}
}
pub fn label_style(&self, state: WidgetState) -> LabelStyle {
let ws = self.widget_style(state);
pub fn label_style(&self, modifier: &StyleModifiers) -> LabelStyle {
let ws = self.widget_style(modifier);
LabelStyle {
frame: Frame {
fill: ws.frame.fill,
@@ -191,7 +191,7 @@ impl Style {
}
}
pub fn separator_style(&self, _state: WidgetState) -> SeparatorStyle {
pub fn separator_style(&self, _modifier: &StyleModifiers) -> SeparatorStyle {
let visuals = self.visuals.noninteractive();
SeparatorStyle {
spacing: 6.0,
@@ -200,116 +200,109 @@ impl Style {
}
}
#[derive(PartialEq, Eq, Clone)]
pub enum StyleModifier {
/// Widget type should be a modifier or a separate information ?
/// Could have the trait [`HasClasses`] force to implement a method "name"
/// or "`widget_type`"
Button,
Label,
Separator,
Checkbox,
/// Classes and Id are string
Class(String),
Id(String),
/// Theme can be useful
Theme(Theme),
}
pub type WidgetStyleModifier = String;
/// Text modifiers affect only the text of the widgets
pub enum TextModifier {
Header,
Small,
Weak,
Strong,
Code,
}
impl From<&str> for StyleModifier {
fn from(class: &str) -> Self {
match class {
"dark" => Self::Theme(Theme::Dark),
"light" => Self::Theme(Theme::Light),
"button" => Self::Button,
"label" => Self::Label,
"separator" => Self::Separator,
"checkbox" => Self::Checkbox,
// Maybe add a prefix for class and ID ?
_ => Self::Class(class.to_owned()),
}
}
}
pub(crate) const CLASSES_SMALL_VEC_SIZE: usize = 5;
/// Small vec for performance
/// For now we use [`Vec`] but later we could use [`SmallVec`] for performance
#[derive(Default)]
pub struct Modifiers {
pub modifiers: Vec<StyleModifier>,
text: Option<TextModifier>,
parent: Option<Id>,
pub struct StyleModifiers {
modifiers: Vec<WidgetStyleModifier>,
theme: Option<Theme>,
state: WidgetState,
}
impl Modifiers {
pub fn with_classes(mut self, classes: &[StyleModifier]) -> Self {
// debug_assert!(
// classes.len() <= CLASSES_SMALL_VEC_SIZE - self.modifiers.len(),
// "Too many modifiers !"
// );
impl StyleModifiers {
/// Add multiples modifiers in one method and return the list for method chaining
pub fn with_modifiers(mut self, classes: &[WidgetStyleModifier]) -> Self {
self.modifiers.append(&mut classes.to_vec());
self
}
pub fn with_class(mut self, class: impl Into<StyleModifier>) -> Self {
/// Add a single modifier and return the list for method chaining
pub fn with_modifier(mut self, class: impl Into<WidgetStyleModifier>) -> Self {
self.modifiers.push(class.into());
self
}
pub fn with_parent(mut self, parent: &Ui) -> Self {
self.parent = Some(parent.id());
self
}
/// Add a class to the list
pub fn add_if(&mut self, class: impl Into<StyleModifier>, condition: bool) {
/// Add a class to the list if the condition is true
pub fn add_if(&mut self, modifier: impl Into<WidgetStyleModifier>, condition: bool) {
if condition {
self.modifiers.push(class.into());
self.modifiers.push(modifier.into());
}
}
/// Add a class to the list and return the list, for method chaining
pub fn with_if(mut self, class: impl Into<StyleModifier>, condition: bool) -> Self {
self.add_if(class.into(), condition);
/// Add a class to the list and return the list for method chaining
pub fn with_if(mut self, modifier: impl Into<WidgetStyleModifier>, condition: bool) -> Self {
self.add_if(modifier.into(), condition);
self
}
pub fn has(&self, class: impl Into<StyleModifier>) -> bool {
self.modifiers.contains(&class.into())
/// Return true if the modifier is present in the list
pub fn has(&self, modifier: impl Into<WidgetStyleModifier>) -> bool {
self.modifiers.contains(&modifier.into())
}
pub fn with_theme(&mut self, theme: Theme) {
self.theme = Some(theme);
}
pub fn with_state(&mut self, state: WidgetState) {
self.state = state;
}
}
/// Any widgets supporting classes must implement this trait
pub trait HasModifier {
fn classes(&self) -> &Modifiers;
/// Any widgets supporting [`StyleModifiers`] must implement this trait
pub trait HasModifiers {
fn modifiers(&self) -> &StyleModifiers;
fn classes_mut(&mut self) -> &mut Modifiers;
fn modifiers_mut(&mut self) -> &mut StyleModifiers;
fn add_class(&mut self, class: impl Into<StyleModifier>) -> &Self {
self.classes_mut().add_if(class.into(), true);
fn add_class(&mut self, modifier: impl Into<WidgetStyleModifier>) -> &Self {
self.modifiers_mut().add_if(modifier.into(), true);
self
}
fn with_class(mut self, class: impl Into<StyleModifier>) -> Self
fn with_modifier(mut self, modifier: impl Into<WidgetStyleModifier>) -> Self
where
Self: Sized,
{
self.classes_mut().add_if(class.into(), true);
self.modifiers_mut().add_if(modifier.into(), true);
self
}
fn with_class_if(mut self, class: impl Into<StyleModifier>, condition: bool) -> Self
fn with_modifier_if(mut self, modifier: impl Into<WidgetStyleModifier>, condition: bool) -> Self
where
Self: Sized,
{
self.classes_mut().add_if(class.into(), condition);
self.modifiers_mut().add_if(modifier.into(), condition);
self
}
fn theme(mut self, theme: Theme) -> Self
where
Self: Sized,
{
self.modifiers_mut().with_theme(theme);
self
}
}
/// Add a shortcut to add modifiers. The syntax is add_modifiers!([Name]: (modifier1, modifier2, modifier3,)) for any number of modifiers
#[macro_export]
macro_rules! add_modifiers {
($trait_name:ident: ($( $name:ident )+),?) => {
pub trait $trait_name {
$(
fn $name(self) -> Self;
)*
}
impl<T> $trait_name for T
where
T: HasModifiers,
{
$(fn $name(mut self) -> Self {
self.with_modifier(stringify!($name))
})?
}
};
}

View File

@@ -4,7 +4,7 @@ use crate::{
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame,
Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2,
Widget, WidgetInfo, WidgetText, WidgetType,
widget_style::{ButtonStyle, WidgetState},
widget_style::{ButtonStyle, HasModifiers, StyleModifiers, WidgetState},
};
/// Clickable button with text.
@@ -38,6 +38,7 @@ pub struct Button<'a> {
selected: bool,
image_tint_follows_text_color: bool,
limit_image_size: bool,
modifiers: StyleModifiers,
}
impl<'a> Button<'a> {
@@ -56,6 +57,7 @@ impl<'a> Button<'a> {
selected: false,
image_tint_follows_text_color: false,
limit_image_size: false,
modifiers: StyleModifiers::default(),
}
}
@@ -273,6 +275,7 @@ impl<'a> Button<'a> {
selected,
image_tint_follows_text_color,
limit_image_size,
mut modifiers,
} = self;
// Min size height always equal or greater than interact size if not small
@@ -297,8 +300,11 @@ impl<'a> Button<'a> {
let id = ui.next_auto_id();
let response: Option<Response> = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
modifiers.with_state(state);
let ButtonStyle { frame, text_style } = ui.style().button_style(state, selected);
modifiers.add_if("selected", selected);
let ButtonStyle { frame, text_style } = ui.style().button_style(&modifiers);
let mut button_padding = if has_frame_margin {
frame.inner_margin
@@ -369,3 +375,13 @@ impl Widget for Button<'_> {
self.atom_ui(ui).response
}
}
impl HasModifiers for Button<'_> {
fn modifiers(&self) -> &StyleModifiers {
&self.modifiers
}
fn modifiers_mut(&mut self) -> &mut StyleModifiers {
&mut self.modifiers
}
}

View File

@@ -2,7 +2,8 @@ use emath::Rect;
use crate::{
Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget,
WidgetInfo, WidgetType, epaint, pos2, widget_style::CheckboxStyle,
WidgetInfo, WidgetType, epaint, pos2,
widget_style::{CheckboxStyle, HasModifiers, StyleModifiers},
};
// TODO(emilk): allow checkbox without a text label
@@ -23,6 +24,7 @@ pub struct Checkbox<'a> {
checked: &'a mut bool,
atoms: Atoms<'a>,
indeterminate: bool,
modifier: StyleModifiers,
}
impl<'a> Checkbox<'a> {
@@ -31,6 +33,7 @@ impl<'a> Checkbox<'a> {
checked,
atoms: atoms.into_atoms(),
indeterminate: false,
modifier: StyleModifiers::default(),
}
}
@@ -55,12 +58,14 @@ impl Widget for Checkbox<'_> {
checked,
mut atoms,
indeterminate,
mut modifier,
} = self;
// Get the widget style by reading the response from the previous pass
let id = ui.next_auto_id();
let response: Option<Response> = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
modifier.with_state(state);
let CheckboxStyle {
check_size,
@@ -69,7 +74,7 @@ impl Widget for Checkbox<'_> {
frame,
check_stroke,
text_style,
} = ui.style().checkbox_style(state);
} = ui.style().checkbox_style(&modifier);
let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
min_size.y = min_size.y.at_least(checkbox_size);
@@ -153,3 +158,13 @@ impl Widget for Checkbox<'_> {
}
}
}
impl HasModifiers for Checkbox<'_> {
fn modifiers(&self) -> &crate::widget_style::StyleModifiers {
&self.modifier
}
fn modifiers_mut(&mut self) -> &mut crate::widget_style::StyleModifiers {
&mut self.modifier
}
}

View File

@@ -1,4 +1,7 @@
use crate::{Response, Sense, Ui, Vec2, Widget, vec2, widget_style::SeparatorStyle};
use crate::{
Response, Sense, Ui, Vec2, Widget, vec2,
widget_style::{HasModifiers, SeparatorStyle, StyleModifiers},
};
/// A visual separator. A horizontal or vertical line (depending on [`crate::Layout`]).
///
@@ -16,6 +19,7 @@ pub struct Separator {
spacing: Option<f32>,
grow: f32,
is_horizontal_line: Option<bool>,
modifier: StyleModifiers,
}
impl Default for Separator {
@@ -24,6 +28,7 @@ impl Default for Separator {
spacing: None,
grow: 0.0,
is_horizontal_line: None,
modifier: StyleModifiers::default(),
}
}
}
@@ -91,16 +96,18 @@ impl Widget for Separator {
spacing,
grow,
is_horizontal_line,
mut modifier,
} = self;
// Get the widget style by reading the response from the previous pass
let id = ui.next_auto_id();
let response: Option<Response> = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
modifier.with_state(state);
let SeparatorStyle {
spacing: spacing_style,
stroke,
} = ui.style().separator_style(state);
} = ui.style().separator_style(&modifier);
// override the spacing if not set
let spacing = spacing.unwrap_or(spacing_style);
@@ -142,3 +149,13 @@ impl Widget for Separator {
response
}
}
impl HasModifiers for Separator {
fn modifiers(&self) -> &crate::widget_style::StyleModifiers {
&self.modifier
}
fn modifiers_mut(&mut self) -> &mut crate::widget_style::StyleModifiers {
&mut self.modifier
}
}