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

style by widget

This commit is contained in:
adrien
2025-10-13 00:01:34 +02:00
parent 96470fabee
commit 835db5cba6
4 changed files with 224 additions and 33 deletions

View File

@@ -434,6 +434,7 @@ mod plugin;
pub mod response;
mod sense;
pub mod style;
pub mod style_trait;
pub mod text_selection;
mod ui;
mod ui_builder;

View File

@@ -0,0 +1,188 @@
use emath::Vec2;
use epaint::{Color32, FontId, Stroke};
use crate::{
Frame, Response, Style,
style::{WidgetVisuals, Widgets},
};
/// General text style
pub struct TextVisuals {
/// Font used
pub font_id: FontId,
/// Font color
pub color: Color32,
}
/// General widget style
pub struct WidgetStyle {
pub frame: Frame,
pub text: TextVisuals,
pub stroke: Stroke,
}
pub struct ButtonStyle {
pub frame: Frame,
pub text: TextVisuals,
}
pub struct CheckboxStyle {
/// Frame around
pub frame: Frame,
/// Text next to it
pub text: TextVisuals,
/// Size
pub size: Vec2,
/// Frame of the checkbox itself
pub checkbox_frame: Frame,
/// Checkmark stroke
pub stroke: Stroke,
}
pub struct DragValueStyle {
/// Frame around
pub frame: Frame,
/// Text of the value
pub text: TextVisuals,
pub min_size: Vec2,
}
pub struct HyperlinkStyle {
pub frame: Frame,
pub text: TextVisuals,
pub size: Vec2,
pub checkbox_frame: Frame,
pub stroke: Stroke,
}
pub struct ImageStyle {
pub frame: Frame,
pub text: TextVisuals,
pub size: Vec2,
pub checkbox_frame: Frame,
pub stroke: Stroke,
}
pub struct LabelStyle {
pub frame: Frame,
pub text: TextVisuals,
pub size: Vec2,
pub checkbox_frame: Frame,
pub stroke: Stroke,
}
pub struct RadioButtonStyle {
pub frame: Frame,
pub text: TextVisuals,
pub size: Vec2,
pub checkbox_frame: Frame,
pub stroke: Stroke,
}
pub struct SeparatorStyle {
pub size: f32,
pub stroke: Stroke,
}
pub struct SliderStyle {
pub frame: Frame,
pub text: TextVisuals,
pub size: Vec2,
pub checkbox_frame: Frame,
pub stroke: Stroke,
}
pub struct SpinnerStyle {
pub frame: Frame,
pub text: TextVisuals,
pub size: Vec2,
pub checkbox_frame: Frame,
pub stroke: Stroke,
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum WidgetState {
Noninteractive,
#[default]
Inactive,
Hovered,
Active,
}
impl Widgets {
pub fn state(&self, state: WidgetState) -> &WidgetVisuals {
match state {
WidgetState::Noninteractive => &self.noninteractive,
WidgetState::Inactive => &self.inactive,
WidgetState::Hovered => &self.hovered,
WidgetState::Active => &self.active,
}
}
}
impl Response {
pub fn widget_state(&self) -> WidgetState {
if !self.sense.interactive() {
WidgetState::Noninteractive
} else if self.is_pointer_button_down_on() || self.has_focus() || self.clicked() {
WidgetState::Active
} else if self.hovered() || self.highlighted() {
WidgetState::Hovered
} else {
WidgetState::Inactive
}
}
}
impl Style {
pub fn widget_style(&self, state: WidgetState) -> WidgetStyle {
let visuals = self.visuals.widgets.state(state);
let font_id = self.override_font_id.clone();
WidgetStyle {
frame: Frame {
fill: visuals.bg_fill,
stroke: visuals.bg_stroke,
corner_radius: visuals.corner_radius,
..Default::default()
},
stroke: visuals.fg_stroke,
text: TextVisuals {
color: visuals.fg_stroke.color,
font_id: font_id.unwrap_or(FontId::new(13.0, epaint::FontFamily::Proportional)),
},
}
}
pub fn button_style(&self, state: WidgetState) -> ButtonStyle {
let ws = self.widget_style(state);
ButtonStyle {
frame: ws.frame.inner_margin(self.spacing.button_padding),
text: ws.text,
}
}
// pub fn checkbox_style(&self, state: WidgetState) -> CheckboxStylee {}
// pub fn label_style(&self, state: WidgetState) -> LabelStyle {}
pub fn drag_value_style(&self, state: WidgetState) -> DragValueStyle {
let ws = self.widget_style(state);
DragValueStyle {
frame: ws.frame.inner_margin(self.spacing.button_padding),
min_size: self.spacing.interact_size,
text: ws.text,
}
}
// pub fn hyperlink_style(&self, state: WidgetState) -> HyperlinkStyle {}
// pub fn image_style(&self, state: WidgetState) -> ImageStyle {}
// pub fn slider_style(&self, state: WidgetState) -> SliderStyle {}
// pub fn separator_style(&self, state: WidgetState) -> SeparatorStyle {}
// pub fn spinner_style(&self, state: WidgetState) -> SpinnerStyle {}
}

View File

@@ -1,7 +1,9 @@
use std::{mem, sync::Arc};
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,
Image, IntoAtoms, NumExt as _, Response, RichText, Sense, Stroke, TextStyle, TextWrapMode, Ui,
Vec2, Widget, WidgetInfo, WidgetText, WidgetType,
};
/// Clickable button with text.
@@ -281,25 +283,30 @@ impl<'a> Button<'a> {
let text = layout.text().map(String::from);
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();
let style = ui.style().button_style(state);
let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame);
let mut button_padding = if has_frame_margin {
ui.spacing().button_padding
} else {
Vec2::ZERO
};
if small {
button_padding.y = 0.0;
}
layout.map_texts(|t| match t {
WidgetText::RichText(mut text) => {
let text_mut = Arc::make_mut(&mut text);
*text_mut = mem::take(text_mut).font(style.text.font_id.clone());
WidgetText::RichText(text)
}
WidgetText::Text(text) => {
let rich_text = RichText::new(text.clone()).font(style.text.font_id.clone());
WidgetText::RichText(Arc::new(rich_text))
}
w => w,
});
let mut prepared = layout
.frame(Frame::new().inner_margin(button_padding))
.min_size(min_size)
.allocate(ui);
let mut prepared = layout.frame(style.frame).min_size(min_size).allocate(ui);
let response = if ui.is_rect_visible(prepared.response.rect) {
let visuals = ui.style().interact_selectable(&prepared.response, selected);
let visible_frame = if frame_when_inactive {
has_frame_margin
} else {
@@ -310,23 +317,13 @@ impl<'a> Button<'a> {
};
if image_tint_follows_text_color {
prepared.map_images(|image| image.tint(visuals.text_color()));
prepared.map_images(|image| image.tint(style.text.color));
}
prepared.fallback_text_color = visuals.text_color();
prepared.fallback_text_color = style.text.color;
if visible_frame {
let stroke = stroke.unwrap_or(visuals.bg_stroke);
let fill = fill.unwrap_or(visuals.weak_bg_fill);
prepared.frame = prepared
.frame
.inner_margin(
button_padding + Vec2::splat(visuals.expansion) - Vec2::splat(stroke.width),
)
.outer_margin(-Vec2::splat(visuals.expansion))
.fill(fill)
.stroke(stroke)
.corner_radius(corner_radius.unwrap_or(visuals.corner_radius));
prepared.frame = style.frame;
}
prepared.paint(ui)

View File

@@ -4,7 +4,7 @@ use std::{cmp::Ordering, ops::RangeInclusive};
use crate::{
Button, CursorIcon, Id, Key, MINUS_CHAR_STR, Modifiers, NumExt as _, Response, RichText, Sense,
TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, emath, text,
TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, emath, grid::State, text,
};
// ----------------------------------------------------------------------------
@@ -447,6 +447,10 @@ impl Widget for DragValue<'_> {
let id = ui.next_auto_id();
let is_slow_speed = shift && ui.ctx().is_being_dragged(id);
let response = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
let style = ui.style().drag_value_style(state);
// The following ensures that when a `DragValue` receives focus,
// it is immediately rendered in edit mode, rather than being rendered
// in button mode for just one frame. This is important for
@@ -560,13 +564,14 @@ impl Widget for DragValue<'_> {
.clip_text(false)
.horizontal_align(ui.layout().horizontal_align())
.vertical_align(ui.layout().vertical_align())
.margin(ui.spacing().button_padding)
.min_size(ui.spacing().interact_size)
.margin(style.frame.inner_margin)
.min_size(style.min_size)
.id(id)
.desired_width(
ui.spacing().interact_size.x - 2.0 * ui.spacing().button_padding.x,
)
.font(text_style),
.text_color(style.text.color)
.font(style.text.font_id),
);
// Select all text when the edit gains focus.