mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Add AtomLayoutStyle
This commit is contained in:
@@ -6,8 +6,8 @@ use crate::{
|
|||||||
style::WidgetVisuals,
|
style::WidgetVisuals,
|
||||||
theme::StyleProvider,
|
theme::StyleProvider,
|
||||||
widget_style::{
|
widget_style::{
|
||||||
ButtonStyle, CheckboxStyle, HasClasses as _, SeparatorStyle, StyleArgs, TextVisuals,
|
AtomLayoutStyle, ButtonStyle, CheckboxStyle, HasClasses as _, SeparatorStyle, StyleArgs,
|
||||||
WidgetState,
|
TextVisuals, WidgetState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,14 +78,26 @@ impl StyleProvider<ButtonStyle> for DefaultStyle {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let text_style = text_visuals(style, &widget_visuals);
|
||||||
|
let image_tint = if classes.has_class(Button::CLASS_IMAGE_TINT_FOLLOWS_TEXT_COLOR) {
|
||||||
|
text_style.color
|
||||||
|
} else {
|
||||||
|
crate::Color32::WHITE
|
||||||
|
};
|
||||||
|
|
||||||
ButtonStyle {
|
ButtonStyle {
|
||||||
min_size: if classes.has_class(Button::CLASS_SMALL) {
|
atom_layout: AtomLayoutStyle {
|
||||||
Vec2::ZERO
|
min_size: if classes.has_class(Button::CLASS_SMALL) {
|
||||||
} else {
|
Vec2::ZERO
|
||||||
Vec2::new(0.0, spacing.interact_size.y)
|
} else {
|
||||||
|
Vec2::new(0.0, spacing.interact_size.y)
|
||||||
|
},
|
||||||
|
gap: spacing.icon_spacing,
|
||||||
|
frame,
|
||||||
|
text_style,
|
||||||
|
image_tint,
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
frame,
|
|
||||||
text_style: text_visuals(style, &widget_visuals),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +109,13 @@ impl StyleProvider<CheckboxStyle> for DefaultStyle {
|
|||||||
let widget_visuals = *style.visuals.widgets.state(*state);
|
let widget_visuals = *style.visuals.widgets.state(*state);
|
||||||
|
|
||||||
CheckboxStyle {
|
CheckboxStyle {
|
||||||
frame: Frame::new(),
|
atom_layout: AtomLayoutStyle {
|
||||||
|
min_size: Vec2::splat(spacing.interact_size.y),
|
||||||
|
gap: spacing.icon_spacing,
|
||||||
|
frame: Frame::new(),
|
||||||
|
text_style: text_visuals(style, &widget_visuals),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
checkbox_size: spacing.icon_width,
|
checkbox_size: spacing.icon_width,
|
||||||
check_size: spacing.icon_width_inner,
|
check_size: spacing.icon_width_inner,
|
||||||
checkbox_frame: Frame {
|
checkbox_frame: Frame {
|
||||||
@@ -106,7 +124,6 @@ impl StyleProvider<CheckboxStyle> for DefaultStyle {
|
|||||||
stroke: widget_visuals.bg_stroke,
|
stroke: widget_visuals.bg_stroke,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
text_style: text_visuals(style, &widget_visuals),
|
|
||||||
check_stroke: widget_visuals.fg_stroke,
|
check_stroke: widget_visuals.fg_stroke,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ pub mod class {
|
|||||||
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
|
||||||
use epaint::{Color32, FontId, Stroke, Vec2};
|
use emath::{Align2, Vec2};
|
||||||
|
use epaint::{Color32, FontId, Stroke};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, Frame, Response, Style, UiStack,
|
AtomLayout, Context, Frame, Response, Style, UiStack,
|
||||||
style::{WidgetVisuals, Widgets},
|
style::{WidgetVisuals, Widgets},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,14 +35,76 @@ pub struct TextVisuals {
|
|||||||
pub color: Color32,
|
pub color: Color32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Visual and layout style shared by widgets built from an [`AtomLayout`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AtomLayoutStyle {
|
||||||
|
/// Alignment of the atoms within the allocated rectangle.
|
||||||
|
///
|
||||||
|
/// `None` uses the alignment of the surrounding [`crate::Ui`].
|
||||||
|
pub align2: Option<Align2>,
|
||||||
|
|
||||||
|
/// Minimum size of the atom layout.
|
||||||
|
pub min_size: Vec2,
|
||||||
|
|
||||||
|
/// Space between adjacent atoms.
|
||||||
|
pub gap: f32,
|
||||||
|
|
||||||
|
/// Frame around the atoms.
|
||||||
|
pub frame: Frame,
|
||||||
|
|
||||||
|
/// Fallback visuals for text atoms.
|
||||||
|
pub text_style: TextVisuals,
|
||||||
|
|
||||||
|
/// Fallback tint for images whose tint is [`Color32::WHITE`] (untinted).
|
||||||
|
pub image_tint: Color32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AtomLayoutStyle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
align2: None,
|
||||||
|
min_size: Vec2::ZERO,
|
||||||
|
gap: 0.0,
|
||||||
|
frame: Frame::default(),
|
||||||
|
text_style: TextVisuals {
|
||||||
|
font_id: FontId::default(),
|
||||||
|
color: Color32::WHITE,
|
||||||
|
},
|
||||||
|
image_tint: Color32::WHITE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomLayoutStyle {
|
||||||
|
/// Apply this style to an [`AtomLayout`].
|
||||||
|
pub fn apply<'a>(&self, mut layout: AtomLayout<'a>) -> AtomLayout<'a> {
|
||||||
|
layout.map_images(|image| {
|
||||||
|
if image.image_options().tint == Color32::WHITE {
|
||||||
|
image.tint(self.image_tint)
|
||||||
|
} else {
|
||||||
|
image
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let layout = layout
|
||||||
|
.min_size(self.min_size)
|
||||||
|
.gap(self.gap)
|
||||||
|
.frame(self.frame)
|
||||||
|
.fallback_font(self.text_style.font_id.clone())
|
||||||
|
.fallback_text_color(self.text_style.color);
|
||||||
|
|
||||||
|
if let Some(align2) = self.align2 {
|
||||||
|
layout.align2(align2)
|
||||||
|
} else {
|
||||||
|
layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Dedicated button style
|
/// Dedicated button style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ButtonStyle {
|
pub struct ButtonStyle {
|
||||||
/// The minimum size of the button before any per-button override.
|
pub atom_layout: AtomLayoutStyle,
|
||||||
pub min_size: Vec2,
|
|
||||||
|
|
||||||
pub frame: Frame,
|
|
||||||
pub text_style: TextVisuals,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetStyle for ButtonStyle {}
|
impl WidgetStyle for ButtonStyle {}
|
||||||
@@ -49,11 +112,8 @@ impl WidgetStyle for ButtonStyle {}
|
|||||||
/// Dedicated checkbox style
|
/// Dedicated checkbox style
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CheckboxStyle {
|
pub struct CheckboxStyle {
|
||||||
/// Frame around
|
/// Style of the checkbox's atom layout.
|
||||||
pub frame: Frame,
|
pub atom_layout: AtomLayoutStyle,
|
||||||
|
|
||||||
/// Text next to it
|
|
||||||
pub text_style: TextVisuals,
|
|
||||||
|
|
||||||
/// Checkbox size
|
/// Checkbox size
|
||||||
pub checkbox_size: f32,
|
pub checkbox_size: f32,
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ pub struct Button<'a> {
|
|||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
corner_radius: Option<CornerRadius>,
|
corner_radius: Option<CornerRadius>,
|
||||||
selected: Option<bool>,
|
selected: Option<bool>,
|
||||||
image_tint_follows_text_color: bool,
|
|
||||||
limit_image_size: bool,
|
limit_image_size: bool,
|
||||||
classes: Classes,
|
classes: Classes,
|
||||||
}
|
}
|
||||||
@@ -53,6 +52,10 @@ impl<'a> Button<'a> {
|
|||||||
pub const CLASS_HIDE_FRAME_WHEN_INACTIVE: &'static str =
|
pub const CLASS_HIDE_FRAME_WHEN_INACTIVE: &'static str =
|
||||||
"egui::button::hide_frame_when_inactive";
|
"egui::button::hide_frame_when_inactive";
|
||||||
|
|
||||||
|
/// Present when untinted images should follow the button text color.
|
||||||
|
pub const CLASS_IMAGE_TINT_FOLLOWS_TEXT_COLOR: &'static str =
|
||||||
|
"egui::button::image_tint_follows_text_color";
|
||||||
|
|
||||||
pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
|
pub fn new(atoms: impl IntoAtoms<'a>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
layout: AtomLayout::new(atoms.into_atoms())
|
layout: AtomLayout::new(atoms.into_atoms())
|
||||||
@@ -63,7 +66,6 @@ impl<'a> Button<'a> {
|
|||||||
min_size: Vec2::ZERO,
|
min_size: Vec2::ZERO,
|
||||||
corner_radius: None,
|
corner_radius: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
image_tint_follows_text_color: false,
|
|
||||||
limit_image_size: false,
|
limit_image_size: false,
|
||||||
classes: Classes::default(),
|
classes: Classes::default(),
|
||||||
}
|
}
|
||||||
@@ -224,15 +226,19 @@ impl<'a> Button<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If true, the tint of the image is multiplied by the widget text color.
|
/// If true, use the widget text color as the fallback tint for images.
|
||||||
///
|
///
|
||||||
/// This makes sense for images that are white, that should have the same color as the text color.
|
/// This makes sense for monochrome images that should have the same color as the text. It also
|
||||||
/// This will also make the icon color depend on hover state.
|
/// makes the image color depend on hover state. A non-white tint set on an image takes
|
||||||
|
/// precedence over this fallback; [`Color32::WHITE`] means untinted.
|
||||||
///
|
///
|
||||||
/// Default: `false`.
|
/// Default: `false`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn image_tint_follows_text_color(mut self, image_tint_follows_text_color: bool) -> Self {
|
pub fn image_tint_follows_text_color(mut self, image_tint_follows_text_color: bool) -> Self {
|
||||||
self.image_tint_follows_text_color = image_tint_follows_text_color;
|
self.set_class(
|
||||||
|
Self::CLASS_IMAGE_TINT_FOLLOWS_TEXT_COLOR,
|
||||||
|
image_tint_follows_text_color,
|
||||||
|
);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,10 +324,9 @@ impl<'a> Button<'a> {
|
|||||||
mut layout,
|
mut layout,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
mut min_size,
|
min_size,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
selected,
|
selected,
|
||||||
image_tint_follows_text_color,
|
|
||||||
limit_image_size,
|
limit_image_size,
|
||||||
classes,
|
classes,
|
||||||
} = self;
|
} = self;
|
||||||
@@ -340,39 +345,29 @@ impl<'a> Button<'a> {
|
|||||||
|
|
||||||
let id = ui.next_auto_id();
|
let id = ui.next_auto_id();
|
||||||
let ButtonStyle {
|
let ButtonStyle {
|
||||||
mut frame,
|
atom_layout: mut atom_layout_style,
|
||||||
text_style,
|
|
||||||
min_size: style_min_size,
|
|
||||||
} = ui.widget_style(id, &classes);
|
} = ui.widget_style(id, &classes);
|
||||||
|
|
||||||
min_size = min_size.at_least(style_min_size);
|
let min_size = min_size.at_least(atom_layout_style.min_size);
|
||||||
|
|
||||||
// Override global style by local style
|
// Override global style by local style
|
||||||
|
if let Some(stroke) = stroke {
|
||||||
|
atom_layout_style.frame = atom_layout_style.frame.stroke(stroke);
|
||||||
|
}
|
||||||
if let Some(fill) = fill {
|
if let Some(fill) = fill {
|
||||||
frame = frame.fill(fill);
|
atom_layout_style.frame = atom_layout_style.frame.fill(fill);
|
||||||
}
|
}
|
||||||
if let Some(corner_radius) = corner_radius {
|
if let Some(corner_radius) = corner_radius {
|
||||||
frame = frame.corner_radius(corner_radius);
|
atom_layout_style.frame = atom_layout_style.frame.corner_radius(corner_radius);
|
||||||
}
|
|
||||||
if let Some(stroke) = stroke {
|
|
||||||
frame = frame.stroke(stroke);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the style font and color as fallback
|
let prepared = atom_layout_style
|
||||||
layout = layout
|
.apply(layout)
|
||||||
.fallback_font(text_style.font_id.clone())
|
.min_size(min_size)
|
||||||
.fallback_text_color(text_style.color);
|
.allocate(ui);
|
||||||
|
|
||||||
let mut prepared = layout.frame(frame).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) {
|
||||||
if image_tint_follows_text_color {
|
|
||||||
prepared.map_images(|image| image.tint(text_style.color));
|
|
||||||
}
|
|
||||||
|
|
||||||
prepared.fallback_text_color = text_style.color;
|
|
||||||
|
|
||||||
prepared.paint(ui)
|
prepared.paint(ui)
|
||||||
} else {
|
} else {
|
||||||
AtomLayoutResponse::empty(prepared.response)
|
AtomLayoutResponse::empty(prepared.response)
|
||||||
|
|||||||
@@ -71,16 +71,14 @@ impl Widget for Checkbox<'_> {
|
|||||||
// Get the widget style by reading the response from the previous pass
|
// Get the widget style by reading the response from the previous pass
|
||||||
let id = ui.next_auto_id();
|
let id = ui.next_auto_id();
|
||||||
let CheckboxStyle {
|
let CheckboxStyle {
|
||||||
|
atom_layout,
|
||||||
check_size,
|
check_size,
|
||||||
checkbox_frame,
|
checkbox_frame,
|
||||||
checkbox_size,
|
checkbox_size,
|
||||||
frame,
|
|
||||||
check_stroke,
|
check_stroke,
|
||||||
text_style,
|
|
||||||
} = ui.widget_style(id, &classes);
|
} = ui.widget_style(id, &classes);
|
||||||
|
|
||||||
let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
|
let min_size = atom_layout.min_size.at_least(Vec2::new(0.0, checkbox_size));
|
||||||
min_size.y = min_size.y.at_least(checkbox_size);
|
|
||||||
|
|
||||||
// In order to center the checkbox based on min_size we set the icon height to at least min_size.y
|
// In order to center the checkbox based on min_size we set the icon height to at least min_size.y
|
||||||
let mut icon_size = Vec2::splat(checkbox_size);
|
let mut icon_size = Vec2::splat(checkbox_size);
|
||||||
@@ -90,11 +88,8 @@ impl Widget for Checkbox<'_> {
|
|||||||
|
|
||||||
let text = atoms.text().map(String::from);
|
let text = atoms.text().map(String::from);
|
||||||
|
|
||||||
let mut prepared = AtomLayout::new(atoms)
|
let layout = AtomLayout::new(atoms).sense(Sense::click());
|
||||||
.sense(Sense::click())
|
let mut prepared = atom_layout.apply(layout).min_size(min_size).allocate(ui);
|
||||||
.min_size(min_size)
|
|
||||||
.frame(frame)
|
|
||||||
.allocate(ui);
|
|
||||||
|
|
||||||
if prepared.response.clicked() {
|
if prepared.response.clicked() {
|
||||||
*checked = !*checked;
|
*checked = !*checked;
|
||||||
@@ -118,7 +113,6 @@ impl Widget for Checkbox<'_> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ui.is_rect_visible(prepared.response.rect) {
|
if ui.is_rect_visible(prepared.response.rect) {
|
||||||
prepared.fallback_text_color = text_style.color;
|
|
||||||
let response = prepared.paint(ui);
|
let response = prepared.paint(ui);
|
||||||
|
|
||||||
if let Some(rect) = response.rect(rect_id) {
|
if let Some(rect) = response.rect(rect_id) {
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
use eframe::egui::{
|
use eframe::egui::{
|
||||||
self, CentralPanel, Color32, Frame, Panel, TextStyle,
|
self, CentralPanel, Color32, Frame, Panel, TextStyle,
|
||||||
theme::StyleProvider,
|
theme::StyleProvider,
|
||||||
widget_style::{ButtonStyle, HasClasses as _, StyleArgs, TextVisuals, WidgetState},
|
widget_style::{
|
||||||
|
AtomLayoutStyle, 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.
|
||||||
@@ -60,15 +62,19 @@ impl StyleProvider<ButtonStyle> for MyTheme {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ButtonStyle {
|
ButtonStyle {
|
||||||
min_size: egui::vec2(0.0, style.spacing.interact_size.y),
|
atom_layout: AtomLayoutStyle {
|
||||||
frame: Frame::new()
|
min_size: egui::vec2(0.0, style.spacing.interact_size.y),
|
||||||
.fill(fill)
|
gap: style.spacing.icon_spacing,
|
||||||
.corner_radius(self.corner_radius)
|
frame: Frame::new()
|
||||||
.inner_margin(8),
|
.fill(fill)
|
||||||
text_style: TextVisuals {
|
.corner_radius(self.corner_radius)
|
||||||
color: Color32::WHITE,
|
.inner_margin(8),
|
||||||
// Resolve the font from the style, so we follow the user's font sizes:
|
text_style: TextVisuals {
|
||||||
font_id: TextStyle::Button.resolve(style),
|
color: Color32::WHITE,
|
||||||
|
// Resolve the font from the style, so we follow the user's font sizes:
|
||||||
|
font_id: TextStyle::Button.resolve(style),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user