mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
final draft
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
})?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user