mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 06:10:06 -04:00
Draft: new eugi StyleEngine and example custom styling system
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use crate::style::WidgetVisuals;
|
||||||
|
use crate::style_trait::{Classes, WidgetName};
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
|
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
|
||||||
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetText, WidgetType,
|
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetText, WidgetType,
|
||||||
@@ -580,7 +582,11 @@ impl CollapsingHeader {
|
|||||||
let openness = state.openness(ui.ctx());
|
let openness = state.openness(ui.ctx());
|
||||||
|
|
||||||
if ui.is_rect_visible(rect) {
|
if ui.is_rect_visible(rect) {
|
||||||
let visuals = ui.style().interact_selectable(&header_response, selected);
|
let visuals: WidgetVisuals = ui.widget_style(
|
||||||
|
WidgetName::Custom("CollapsingHeader".into()),
|
||||||
|
&header_response,
|
||||||
|
&Classes::default().with_if("selected", selected),
|
||||||
|
);
|
||||||
|
|
||||||
if ui.visuals().collapsing_header_frame || show_background {
|
if ui.visuals().collapsing_header_frame || show_background {
|
||||||
ui.painter().add(epaint::RectShape::new(
|
ui.painter().add(epaint::RectShape::new(
|
||||||
|
|||||||
@@ -446,6 +446,7 @@ mod atomics;
|
|||||||
#[cfg(feature = "callstack")]
|
#[cfg(feature = "callstack")]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod callstack;
|
mod callstack;
|
||||||
|
pub mod style_trait;
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub use accesskit;
|
pub use accesskit;
|
||||||
|
|||||||
175
crates/egui/src/style_trait.rs
Normal file
175
crates/egui/src/style_trait.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use crate::style::WidgetVisuals;
|
||||||
|
use crate::{Context, Frame, Id, Response, TextStyle, Ui};
|
||||||
|
use emath::TSTransform;
|
||||||
|
use epaint::text::TextFormat;
|
||||||
|
use epaint::{CornerRadius, Stroke};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum WidgetName {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Slider,
|
||||||
|
TextInput,
|
||||||
|
Custom(Cow<'static, str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct WidgetContext<'c> {
|
||||||
|
pub ui: &'c Ui,
|
||||||
|
pub response: &'c Response,
|
||||||
|
pub classes: &'c Classes,
|
||||||
|
pub name: WidgetName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for WidgetContext<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("WidgetContext")
|
||||||
|
.field("name", &self.name)
|
||||||
|
.field("classes", &self.classes.classes)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLASS_SELECTED: &str = "selected";
|
||||||
|
|
||||||
|
pub trait StyleEngine<T>: Send + Sync {
|
||||||
|
fn get(&self, ctx: &WidgetContext) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DefaultWidgetStyle;
|
||||||
|
|
||||||
|
impl StyleEngine<WidgetVisuals> for DefaultWidgetStyle {
|
||||||
|
fn get(&self, ctx: &WidgetContext) -> WidgetVisuals {
|
||||||
|
ctx.ui
|
||||||
|
.style()
|
||||||
|
.interact_selectable(ctx.response, ctx.classes.has(CLASS_SELECTED))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn widget_style_id() -> Id {
|
||||||
|
Id::new("WidgetStyle")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui {
|
||||||
|
pub fn widget_style<T: 'static>(
|
||||||
|
&self,
|
||||||
|
name: WidgetName,
|
||||||
|
response: &Response,
|
||||||
|
classes: &Classes,
|
||||||
|
) -> T {
|
||||||
|
let style = self.data_mut(|d| {
|
||||||
|
let style: StyleEngineContainer<T> =
|
||||||
|
d.get_temp(widget_style_id()).expect("Widget style not set");
|
||||||
|
style
|
||||||
|
});
|
||||||
|
let ctx = WidgetContext {
|
||||||
|
ui: self,
|
||||||
|
response: &response,
|
||||||
|
classes,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
|
||||||
|
style.0.get(&ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn set_style_engine<T: 'static>(&self, style: impl StyleEngine<T> + 'static) {
|
||||||
|
self.data_mut(|d| {
|
||||||
|
d.insert_temp(widget_style_id(), StyleEngineContainer(Arc::new(style)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StyleEngineContainer<T>(Arc<dyn StyleEngine<T>>);
|
||||||
|
|
||||||
|
impl<T> Clone for StyleEngineContainer<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
StyleEngineContainer(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Classes {
|
||||||
|
pub classes: smallvec::SmallVec<[&'static str; 1]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Classes {
|
||||||
|
pub fn with_if(mut self, class: &'static str, condition: bool) -> Self {
|
||||||
|
if condition {
|
||||||
|
self.classes.push(class);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_if(&mut self, class: &'static str, condition: bool) {
|
||||||
|
if condition {
|
||||||
|
self.classes.push(class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has(&self, class: &str) -> bool {
|
||||||
|
self.classes.contains(&class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasClasses {
|
||||||
|
fn classes(&self) -> &Classes;
|
||||||
|
|
||||||
|
fn classes_mut(&mut self) -> &mut Classes;
|
||||||
|
|
||||||
|
fn add_class(&mut self, class: &'static str) -> &Self {
|
||||||
|
self.classes_mut().add_if(class, true);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_class(mut self, class: &'static str) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.classes_mut().add_if(class, true);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_class_if(mut self, class: &'static str, condition: bool) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.classes_mut().add_if(class, condition);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct WidgetStyle {
|
||||||
|
/// Background color, stroke, margin, and shadow.
|
||||||
|
pub frame: Frame,
|
||||||
|
|
||||||
|
/// What font to use and at what size.
|
||||||
|
pub text: TextFormat,
|
||||||
|
|
||||||
|
/// Color and width of e.g. checkbox checkmark.
|
||||||
|
/// Also text color.
|
||||||
|
///
|
||||||
|
/// Note that this is different from the frame border color.
|
||||||
|
pub stroke: Stroke,
|
||||||
|
|
||||||
|
pub transform: TSTransform,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WidgetStyle> for WidgetVisuals {
|
||||||
|
fn from(value: WidgetStyle) -> Self {
|
||||||
|
Self {
|
||||||
|
bg_stroke: value.frame.stroke,
|
||||||
|
bg_fill: value.frame.fill,
|
||||||
|
fg_stroke: value.stroke,
|
||||||
|
corner_radius: value.frame.corner_radius,
|
||||||
|
weak_bg_fill: value.frame.fill,
|
||||||
|
expansion: value.frame.inner_margin.sum().length(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -192,6 +192,13 @@ impl Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_temp<T: 'static>(self) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
Self::Value { value, .. } => value.downcast().ok().map(|b| *b),
|
||||||
|
Self::Serialized(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_temp_mut_or_insert_with<T: 'static + Any + Clone + Send + Sync>(
|
pub(crate) fn get_temp_mut_or_insert_with<T: 'static + Any + Clone + Send + Sync>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -485,10 +492,10 @@ impl IdTypeMap {
|
|||||||
|
|
||||||
/// Remove and fetch the state of this type and id.
|
/// Remove and fetch the state of this type and id.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_temp<T: 'static + Default>(&mut self, id: Id) -> Option<T> {
|
pub fn remove_temp<T: 'static>(&mut self, id: Id) -> Option<T> {
|
||||||
let hash = hash(TypeId::of::<T>(), id);
|
let hash = hash(TypeId::of::<T>(), id);
|
||||||
let mut element = self.map.remove(&hash)?;
|
let mut element = self.map.remove(&hash)?;
|
||||||
Some(std::mem::take(element.get_mut_temp()?))
|
element.into_temp()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note all state of the given type.
|
/// Note all state of the given type.
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
use crate::style::WidgetVisuals;
|
||||||
|
use crate::style_trait::{Classes, HasClasses, WidgetName, WidgetStyle};
|
||||||
use crate::{
|
use crate::{
|
||||||
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame,
|
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame,
|
||||||
Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextWrapMode, Ui, Vec2, Widget,
|
Image, IntoAtoms, NumExt as _, Response, RichText, Sense, SizedAtomKind, Stroke, TextWrapMode,
|
||||||
WidgetInfo, WidgetText, WidgetType,
|
Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType,
|
||||||
};
|
};
|
||||||
|
use std::mem;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Clickable button with text.
|
/// Clickable button with text.
|
||||||
///
|
///
|
||||||
@@ -35,6 +39,17 @@ pub struct Button<'a> {
|
|||||||
selected: bool,
|
selected: bool,
|
||||||
image_tint_follows_text_color: bool,
|
image_tint_follows_text_color: bool,
|
||||||
limit_image_size: bool,
|
limit_image_size: bool,
|
||||||
|
classes: Classes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasClasses for Button<'_> {
|
||||||
|
fn classes(&self) -> &Classes {
|
||||||
|
&self.classes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classes_mut(&mut self) -> &mut Classes {
|
||||||
|
&mut self.classes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Button<'a> {
|
impl<'a> Button<'a> {
|
||||||
@@ -51,6 +66,7 @@ impl<'a> Button<'a> {
|
|||||||
selected: false,
|
selected: false,
|
||||||
image_tint_follows_text_color: false,
|
image_tint_follows_text_color: false,
|
||||||
limit_image_size: false,
|
limit_image_size: false,
|
||||||
|
classes: Classes::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +277,7 @@ impl<'a> Button<'a> {
|
|||||||
selected,
|
selected,
|
||||||
image_tint_follows_text_color,
|
image_tint_follows_text_color,
|
||||||
limit_image_size,
|
limit_image_size,
|
||||||
|
classes,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if !small {
|
if !small {
|
||||||
@@ -290,44 +307,57 @@ impl<'a> Button<'a> {
|
|||||||
button_padding.y = 0.0;
|
button_padding.y = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut prepared = layout
|
let response = ui.ctx().read_response(ui.next_auto_id());
|
||||||
.frame(Frame::new().inner_margin(button_padding))
|
|
||||||
.min_size(min_size)
|
let style: WidgetStyle = response
|
||||||
.allocate(ui);
|
.map(|r| ui.widget_style(WidgetName::Button, &r, &classes))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
layout.map_texts(|t| match t {
|
||||||
|
WidgetText::RichText(mut text) => {
|
||||||
|
let mut 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 mut 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(style.frame).min_size(min_size).allocate(ui);
|
||||||
|
|
||||||
let response = if ui.is_rect_visible(prepared.response.rect) {
|
let response = if ui.is_rect_visible(prepared.response.rect) {
|
||||||
let visuals = ui.style().interact_selectable(&prepared.response, selected);
|
// let visuals = ui.style().interact_selectable(&prepared.response, selected);
|
||||||
|
let visuals: WidgetStyle = ui.widget_style(
|
||||||
|
WidgetName::Button,
|
||||||
|
&prepared.response,
|
||||||
|
&classes.with_if("selected", selected),
|
||||||
|
);
|
||||||
|
ui.with_visual_transform(visuals.transform, |ui| {
|
||||||
|
let visible_frame = if frame_when_inactive {
|
||||||
|
has_frame_margin
|
||||||
|
} else {
|
||||||
|
has_frame_margin
|
||||||
|
&& (prepared.response.hovered()
|
||||||
|
|| prepared.response.is_pointer_button_down_on()
|
||||||
|
|| prepared.response.has_focus())
|
||||||
|
};
|
||||||
|
|
||||||
let visible_frame = if frame_when_inactive {
|
if image_tint_follows_text_color {
|
||||||
has_frame_margin
|
prepared.map_images(|image| image.tint(visuals.text.color));
|
||||||
} else {
|
}
|
||||||
has_frame_margin
|
|
||||||
&& (prepared.response.hovered()
|
|
||||||
|| prepared.response.is_pointer_button_down_on()
|
|
||||||
|| prepared.response.has_focus())
|
|
||||||
};
|
|
||||||
|
|
||||||
if image_tint_follows_text_color {
|
prepared.fallback_text_color = visuals.text.color;
|
||||||
prepared.map_images(|image| image.tint(visuals.text_color()));
|
|
||||||
}
|
|
||||||
|
|
||||||
prepared.fallback_text_color = visuals.text_color();
|
if visible_frame {
|
||||||
|
prepared.frame = visuals.frame;
|
||||||
|
};
|
||||||
|
|
||||||
if visible_frame {
|
prepared.paint(ui)
|
||||||
let stroke = stroke.unwrap_or(visuals.bg_stroke);
|
})
|
||||||
let fill = fill.unwrap_or(visuals.weak_bg_fill);
|
.inner
|
||||||
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.paint(ui)
|
|
||||||
} else {
|
} else {
|
||||||
AtomLayoutResponse::empty(prepared.response)
|
AtomLayoutResponse::empty(prepared.response)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
|
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
use eframe::egui::style::WidgetVisuals;
|
||||||
|
use eframe::egui::style_trait::{
|
||||||
|
Classes, DefaultWidgetStyle, HasClasses, StyleEngine, WidgetContext, WidgetName, WidgetStyle,
|
||||||
|
};
|
||||||
|
use eframe::egui::{
|
||||||
|
Button, Color32, FontFamily, FontId, Frame, Margin, Response, Stroke, TextFormat,
|
||||||
|
};
|
||||||
|
use eframe::emath::TSTransform;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||||
@@ -16,6 +25,10 @@ fn main() -> eframe::Result {
|
|||||||
let mut age = 42;
|
let mut age = 42;
|
||||||
|
|
||||||
eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
|
eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
|
||||||
|
ctx.set_style_engine(MyCustomWidgetStyle {
|
||||||
|
default: DefaultWidgetStyle,
|
||||||
|
});
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("My egui Application");
|
ui.heading("My egui Application");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -28,6 +41,227 @@ fn main() -> eframe::Result {
|
|||||||
age += 1;
|
age += 1;
|
||||||
}
|
}
|
||||||
ui.label(format!("Hello '{name}', age {age}"));
|
ui.label(format!("Hello '{name}', age {age}"));
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(Button::new("Primary Button").primary());
|
||||||
|
ui.add(Button::new("Secondary Button").secondary());
|
||||||
|
ui.add(Button::new("Normal Button"));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(Button::new("Large Primary").primary().lg());
|
||||||
|
ui.add(Button::new("Large Secondary").secondary().lg());
|
||||||
|
ui.add(Button::new("Small Normal").sm());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MyCustomWidgetStyle {
|
||||||
|
// Can fallback to DefaultWidgetStyle
|
||||||
|
default: DefaultWidgetStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Variant {
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
Normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Variant {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Variant::Primary => write!(f, "primary"),
|
||||||
|
Variant::Secondary => write!(f, "secondary"),
|
||||||
|
Variant::Normal => write!(f, "normal"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Variant {
|
||||||
|
fn from_classes(classes: &Classes) -> Self {
|
||||||
|
if classes.has("primary") {
|
||||||
|
Variant::Primary
|
||||||
|
} else if classes.has("secondary") {
|
||||||
|
Variant::Secondary
|
||||||
|
} else {
|
||||||
|
Variant::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(&self) -> Color32 {
|
||||||
|
match self {
|
||||||
|
Variant::Primary => Color32::LIGHT_RED,
|
||||||
|
Variant::Secondary => Color32::LIGHT_BLUE,
|
||||||
|
Variant::Normal => Color32::LIGHT_GRAY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contrast_color(&self) -> Color32 {
|
||||||
|
match self {
|
||||||
|
Variant::Primary => Color32::WHITE,
|
||||||
|
Variant::Secondary => Color32::BLACK,
|
||||||
|
Variant::Normal => Color32::BLACK,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Size {
|
||||||
|
Sm,
|
||||||
|
Md,
|
||||||
|
Lg,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Size {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Size::Sm => write!(f, "sm"),
|
||||||
|
Size::Md => write!(f, "md"),
|
||||||
|
Size::Lg => write!(f, "lg"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Size {
|
||||||
|
fn from_classes(classes: &Classes) -> Self {
|
||||||
|
if classes.has("sm") {
|
||||||
|
Size::Sm
|
||||||
|
} else if classes.has("md") {
|
||||||
|
Size::Md
|
||||||
|
} else if classes.has("lg") {
|
||||||
|
Size::Lg
|
||||||
|
} else {
|
||||||
|
Size::Md // Default size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_margin(&self) -> Margin {
|
||||||
|
match self {
|
||||||
|
Size::Sm => Margin::symmetric(2, 2),
|
||||||
|
Size::Md => Margin::symmetric(4, 4),
|
||||||
|
Size::Lg => Margin::symmetric(6, 6),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn font_size(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Size::Sm => 12.0,
|
||||||
|
Size::Md => 14.0,
|
||||||
|
Size::Lg => 16.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame_rounding(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Size::Sm => 2.0,
|
||||||
|
Size::Md => 4.0,
|
||||||
|
Size::Lg => 6.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleEngine<WidgetStyle> for MyCustomWidgetStyle {
|
||||||
|
fn get(&self, ctx: &WidgetContext) -> WidgetStyle {
|
||||||
|
// let mut visuals = self.default.get(ctx);
|
||||||
|
|
||||||
|
let variant = Variant::from_classes(&ctx.classes);
|
||||||
|
|
||||||
|
let size = Size::from_classes(&ctx.classes);
|
||||||
|
|
||||||
|
let mut style = WidgetStyle {
|
||||||
|
frame: Frame::new()
|
||||||
|
.fill(variant.color())
|
||||||
|
.inner_margin(size.inner_margin())
|
||||||
|
.corner_radius(size.frame_rounding()),
|
||||||
|
text: TextFormat::simple(
|
||||||
|
FontId::new(size.font_size(), FontFamily::Proportional),
|
||||||
|
variant.contrast_color(),
|
||||||
|
),
|
||||||
|
stroke: Stroke::default(),
|
||||||
|
transform: TSTransform::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = if ctx.response.is_pointer_button_down_on() {
|
||||||
|
-1.0
|
||||||
|
} else if ctx.response.hovered() {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let state_animated =
|
||||||
|
ctx.ui
|
||||||
|
.ctx()
|
||||||
|
.animate_value_with_time(ctx.response.id.with("style_anim"), state, 0.05);
|
||||||
|
|
||||||
|
let lerp_to_darken = Color32::BLACK;
|
||||||
|
let lerp_to_lighten = Color32::WHITE;
|
||||||
|
|
||||||
|
if state_animated < 0.0 {
|
||||||
|
style.frame.fill = variant
|
||||||
|
.color()
|
||||||
|
.lerp_to_gamma(lerp_to_darken, 0.03 * -state_animated);
|
||||||
|
} else if state_animated > 0.0 {
|
||||||
|
style.frame.fill = variant
|
||||||
|
.color()
|
||||||
|
.lerp_to_gamma(lerp_to_lighten, 0.1 * state_animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
match ctx.name {
|
||||||
|
WidgetName::Button => {
|
||||||
|
let scale = 1.0 + state_animated * 0.02;
|
||||||
|
if scale != 1.0 {
|
||||||
|
// style.frame.inner_margin += 4;
|
||||||
|
let center = ctx.response.rect.center().to_vec2();
|
||||||
|
style.transform = TSTransform::from_translation(center)
|
||||||
|
* TSTransform::from_scaling(scale)
|
||||||
|
* TSTransform::from_translation(-center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WidgetName::Checkbox => {}
|
||||||
|
WidgetName::Slider => {}
|
||||||
|
WidgetName::TextInput => {}
|
||||||
|
WidgetName::Custom(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trait ClassExt {
|
||||||
|
// fn primary(self) -> Self;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl<T> ClassExt for T where T: HasClasses {
|
||||||
|
// fn primary(self) -> Self {
|
||||||
|
// self.with_class("primary")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
macro_rules! classes {
|
||||||
|
($trait_name:ident: ($($name:ident, )+)) => {
|
||||||
|
// pub enum $trait_name {
|
||||||
|
// $(
|
||||||
|
// $name,
|
||||||
|
// )*
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub trait $trait_name {
|
||||||
|
$(
|
||||||
|
fn $name(self) -> Self;
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> $trait_name for T
|
||||||
|
where
|
||||||
|
T: HasClasses,
|
||||||
|
{
|
||||||
|
$(fn $name(mut self) -> Self {
|
||||||
|
self.with_class(stringify!($name))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
classes!(CustomStyle: (primary, secondary, normal, sm, md, lg, ));
|
||||||
|
|||||||
Reference in New Issue
Block a user