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,
|
||||
theme::StyleProvider,
|
||||
widget_style::{
|
||||
ButtonStyle, CheckboxStyle, HasClasses as _, SeparatorStyle, StyleArgs, TextVisuals,
|
||||
WidgetState,
|
||||
AtomLayoutStyle, ButtonStyle, CheckboxStyle, HasClasses as _, SeparatorStyle, StyleArgs,
|
||||
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 {
|
||||
atom_layout: AtomLayoutStyle {
|
||||
min_size: if classes.has_class(Button::CLASS_SMALL) {
|
||||
Vec2::ZERO
|
||||
} else {
|
||||
Vec2::new(0.0, spacing.interact_size.y)
|
||||
},
|
||||
gap: spacing.icon_spacing,
|
||||
frame,
|
||||
text_style: text_visuals(style, &widget_visuals),
|
||||
text_style,
|
||||
image_tint,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +109,13 @@ impl StyleProvider<CheckboxStyle> for DefaultStyle {
|
||||
let widget_visuals = *style.visuals.widgets.state(*state);
|
||||
|
||||
CheckboxStyle {
|
||||
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,
|
||||
check_size: spacing.icon_width_inner,
|
||||
checkbox_frame: Frame {
|
||||
@@ -106,7 +124,6 @@ impl StyleProvider<CheckboxStyle> for DefaultStyle {
|
||||
stroke: widget_visuals.bg_stroke,
|
||||
..Default::default()
|
||||
},
|
||||
text_style: text_visuals(style, &widget_visuals),
|
||||
check_stroke: widget_visuals.fg_stroke,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ pub mod class {
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
use epaint::{Color32, FontId, Stroke, Vec2};
|
||||
use emath::{Align2, Vec2};
|
||||
use epaint::{Color32, FontId, Stroke};
|
||||
|
||||
use crate::{
|
||||
Context, Frame, Response, Style, UiStack,
|
||||
AtomLayout, Context, Frame, Response, Style, UiStack,
|
||||
style::{WidgetVisuals, Widgets},
|
||||
};
|
||||
|
||||
@@ -34,14 +35,76 @@ pub struct TextVisuals {
|
||||
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
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ButtonStyle {
|
||||
/// The minimum size of the button before any per-button override.
|
||||
pub min_size: Vec2,
|
||||
|
||||
pub frame: Frame,
|
||||
pub text_style: TextVisuals,
|
||||
pub atom_layout: AtomLayoutStyle,
|
||||
}
|
||||
|
||||
impl WidgetStyle for ButtonStyle {}
|
||||
@@ -49,11 +112,8 @@ impl WidgetStyle for ButtonStyle {}
|
||||
/// Dedicated checkbox style
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CheckboxStyle {
|
||||
/// Frame around
|
||||
pub frame: Frame,
|
||||
|
||||
/// Text next to it
|
||||
pub text_style: TextVisuals,
|
||||
/// Style of the checkbox's atom layout.
|
||||
pub atom_layout: AtomLayoutStyle,
|
||||
|
||||
/// Checkbox size
|
||||
pub checkbox_size: f32,
|
||||
|
||||
@@ -31,7 +31,6 @@ pub struct Button<'a> {
|
||||
min_size: Vec2,
|
||||
corner_radius: Option<CornerRadius>,
|
||||
selected: Option<bool>,
|
||||
image_tint_follows_text_color: bool,
|
||||
limit_image_size: bool,
|
||||
classes: Classes,
|
||||
}
|
||||
@@ -53,6 +52,10 @@ impl<'a> Button<'a> {
|
||||
pub const CLASS_HIDE_FRAME_WHEN_INACTIVE: &'static str =
|
||||
"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 {
|
||||
Self {
|
||||
layout: AtomLayout::new(atoms.into_atoms())
|
||||
@@ -63,7 +66,6 @@ impl<'a> Button<'a> {
|
||||
min_size: Vec2::ZERO,
|
||||
corner_radius: None,
|
||||
selected: None,
|
||||
image_tint_follows_text_color: false,
|
||||
limit_image_size: false,
|
||||
classes: Classes::default(),
|
||||
}
|
||||
@@ -224,15 +226,19 @@ impl<'a> Button<'a> {
|
||||
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 will also make the icon color depend on hover state.
|
||||
/// This makes sense for monochrome images that should have the same color as the text. It also
|
||||
/// 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`.
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
|
||||
@@ -318,10 +324,9 @@ impl<'a> Button<'a> {
|
||||
mut layout,
|
||||
fill,
|
||||
stroke,
|
||||
mut min_size,
|
||||
min_size,
|
||||
corner_radius,
|
||||
selected,
|
||||
image_tint_follows_text_color,
|
||||
limit_image_size,
|
||||
classes,
|
||||
} = self;
|
||||
@@ -340,39 +345,29 @@ impl<'a> Button<'a> {
|
||||
|
||||
let id = ui.next_auto_id();
|
||||
let ButtonStyle {
|
||||
mut frame,
|
||||
text_style,
|
||||
min_size: style_min_size,
|
||||
atom_layout: mut atom_layout_style,
|
||||
} = 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
|
||||
if let Some(stroke) = stroke {
|
||||
atom_layout_style.frame = atom_layout_style.frame.stroke(stroke);
|
||||
}
|
||||
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 {
|
||||
frame = frame.corner_radius(corner_radius);
|
||||
}
|
||||
if let Some(stroke) = stroke {
|
||||
frame = frame.stroke(stroke);
|
||||
atom_layout_style.frame = atom_layout_style.frame.corner_radius(corner_radius);
|
||||
}
|
||||
|
||||
// Apply the style font and color as fallback
|
||||
layout = layout
|
||||
.fallback_font(text_style.font_id.clone())
|
||||
.fallback_text_color(text_style.color);
|
||||
|
||||
let mut prepared = layout.frame(frame).min_size(min_size).allocate(ui);
|
||||
let prepared = atom_layout_style
|
||||
.apply(layout)
|
||||
.min_size(min_size)
|
||||
.allocate(ui);
|
||||
|
||||
// Get AtomLayoutResponse, empty if not visible
|
||||
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)
|
||||
} else {
|
||||
AtomLayoutResponse::empty(prepared.response)
|
||||
|
||||
@@ -71,16 +71,14 @@ impl Widget for Checkbox<'_> {
|
||||
// Get the widget style by reading the response from the previous pass
|
||||
let id = ui.next_auto_id();
|
||||
let CheckboxStyle {
|
||||
atom_layout,
|
||||
check_size,
|
||||
checkbox_frame,
|
||||
checkbox_size,
|
||||
frame,
|
||||
check_stroke,
|
||||
text_style,
|
||||
} = ui.widget_style(id, &classes);
|
||||
|
||||
let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
|
||||
min_size.y = min_size.y.at_least(checkbox_size);
|
||||
let min_size = atom_layout.min_size.at_least(Vec2::new(0.0, checkbox_size));
|
||||
|
||||
// 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);
|
||||
@@ -90,11 +88,8 @@ impl Widget for Checkbox<'_> {
|
||||
|
||||
let text = atoms.text().map(String::from);
|
||||
|
||||
let mut prepared = AtomLayout::new(atoms)
|
||||
.sense(Sense::click())
|
||||
.min_size(min_size)
|
||||
.frame(frame)
|
||||
.allocate(ui);
|
||||
let layout = AtomLayout::new(atoms).sense(Sense::click());
|
||||
let mut prepared = atom_layout.apply(layout).min_size(min_size).allocate(ui);
|
||||
|
||||
if prepared.response.clicked() {
|
||||
*checked = !*checked;
|
||||
@@ -118,7 +113,6 @@ impl Widget for Checkbox<'_> {
|
||||
});
|
||||
|
||||
if ui.is_rect_visible(prepared.response.rect) {
|
||||
prepared.fallback_text_color = text_style.color;
|
||||
let response = prepared.paint(ui);
|
||||
|
||||
if let Some(rect) = response.rect(rect_id) {
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
use eframe::egui::{
|
||||
self, CentralPanel, Color32, Frame, Panel, TextStyle,
|
||||
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.
|
||||
@@ -60,7 +62,9 @@ impl StyleProvider<ButtonStyle> for MyTheme {
|
||||
};
|
||||
|
||||
ButtonStyle {
|
||||
atom_layout: AtomLayoutStyle {
|
||||
min_size: egui::vec2(0.0, style.spacing.interact_size.y),
|
||||
gap: style.spacing.icon_spacing,
|
||||
frame: Frame::new()
|
||||
.fill(fill)
|
||||
.corner_radius(self.corner_radius)
|
||||
@@ -70,6 +74,8 @@ impl StyleProvider<ButtonStyle> for MyTheme {
|
||||
// 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