use egui::{
Color32, CornerRadius, Painter, Rect, Response, Shadow, Shape, Stroke, StrokeKind,
epaint::RectShape,
};
pub type LabelVisuals = ButtonVisuals;
#[derive(Copy, Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ButtonVisuals {
pub text: Color32,
pub bg: Color32,
pub bg_hover: Color32,
pub bg_click: Color32,
pub bg_selected: Color32,
pub border: Stroke,
pub border_hover: Stroke,
pub border_click: Stroke,
pub corner_radius: CornerRadius,
pub shadow: Shadow,
}
impl PartialEq for ButtonVisuals {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
&& self.bg == other.bg
&& self.bg_hover == other.bg_hover
&& self.bg_click == other.bg_click
&& self.bg_selected == other.bg_selected
&& self.border == other.border
&& self.border_hover == other.border_hover
&& self.border_click == other.border_click
&& self.corner_radius == other.corner_radius
&& self.shadow == other.shadow
}
}
impl Eq for ButtonVisuals {}
impl ButtonVisuals {
pub fn bg_from_res(&self, res: &Response) -> Color32 {
if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
self.bg_click
} else if res.hovered() || res.highlighted() {
self.bg_hover
} else {
self.bg
}
}
pub fn border_from_res(&self, res: &Response) -> Stroke {
if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
self.border_click
} else if res.hovered() || res.highlighted() {
self.border_hover
} else {
self.border
}
}
}
#[derive(Copy, Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextEditVisuals {
pub text: Color32,
pub bg: Color32,
pub border: Stroke,
pub border_hover: Stroke,
pub border_open: Stroke,
pub corner_radius: CornerRadius,
pub shadow: Shadow,
}
impl PartialEq for TextEditVisuals {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
&& self.bg == other.bg
&& self.border == other.border
&& self.border_hover == other.border_hover
&& self.border_open == other.border_open
&& self.corner_radius == other.corner_radius
&& self.shadow == other.shadow
}
}
impl Eq for TextEditVisuals {}
impl TextEditVisuals {
pub fn border_from_res(&self, res: &Response) -> Stroke {
if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
self.border_open
} else if res.hovered() || res.highlighted() {
self.border_hover
} else {
self.border
}
}
}
#[derive(Copy, Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ComboBoxVisuals {
pub bg: Color32,
pub icon: Color32,
pub bg_hover: Color32,
pub bg_open: Color32,
pub border: Stroke,
pub border_hover: Stroke,
pub border_open: Stroke,
pub corner_radius: CornerRadius,
pub shadow: Shadow,
}
impl PartialEq for ComboBoxVisuals {
fn eq(&self, other: &Self) -> bool {
self.bg == other.bg
&& self.icon == other.icon
&& self.bg_hover == other.bg_hover
&& self.bg_open == other.bg_open
&& self.border == other.border
&& self.border_hover == other.border_hover
&& self.border_open == other.border_open
&& self.corner_radius == other.corner_radius
&& self.shadow == other.shadow
}
}
impl Eq for ComboBoxVisuals {}
impl ComboBoxVisuals {
pub fn bg_from_res(&self, res: &Response) -> Color32 {
if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
self.bg_open
} else if res.hovered() || res.highlighted() {
self.bg_hover
} else {
self.bg
}
}
pub fn border_from_res(&self, res: &Response) -> Stroke {
if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
self.border_open
} else if res.hovered() || res.highlighted() {
self.border_hover
} else {
self.border
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, Default)]
pub struct FrameVisuals {
pub bg: Color32,
pub bg_hover: Color32,
pub bg_click: Color32,
pub bg_selected: Color32,
pub border: Stroke,
pub border_hover: Stroke,
pub border_click: Stroke,
pub corner_radius: CornerRadius,
pub shadow: Shadow,
}
impl PartialEq for FrameVisuals {
fn eq(&self, other: &Self) -> bool {
self.bg == other.bg
&& self.bg_hover == other.bg_hover
&& self.bg_click == other.bg_click
&& self.bg_selected == other.bg_selected
&& self.border == other.border
&& self.border_hover == other.border_hover
&& self.border_click == other.border_click
&& self.corner_radius == other.corner_radius
&& self.shadow == other.shadow
}
}
impl Eq for FrameVisuals {}
impl FrameVisuals {
pub fn bg_from_res(&self, res: &Response) -> Color32 {
if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
self.bg_click
} else if res.contains_pointer() || res.hovered() || res.highlighted() {
self.bg_hover
} else {
self.bg
}
}
pub fn border_from_res(&self, res: &Response) -> Stroke {
if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
self.border_click
} else if res.contains_pointer() || res.hovered() || res.highlighted() {
self.border_hover
} else {
self.border
}
}
pub fn paint_at(&self, painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke) {
painter.add(paint_shape(
rect,
fill,
stroke,
self.corner_radius,
self.shadow,
));
}
}
pub(crate) fn paint_shape(
rect: Rect,
fill: Color32,
stroke: Stroke,
corner_radius: CornerRadius,
shadow: Shadow,
) -> Shape {
if rect.width() <= 0.0 || rect.height() <= 0.0 {
return Shape::Noop;
}
let has_fill = fill != Color32::TRANSPARENT;
let has_shadow = shadow != Shadow::NONE && shadow.color != Color32::TRANSPARENT;
if !has_fill && stroke.is_empty() && !has_shadow {
return Shape::Noop;
}
let mut shapes = Vec::new();
if has_shadow {
shapes.push(Shape::from(shadow.as_shape(rect, corner_radius)));
}
if has_fill || !stroke.is_empty() {
shapes.push(Shape::Rect(RectShape::new(
rect,
corner_radius,
fill,
stroke,
StrokeKind::Inside,
)));
}
match shapes.len() {
0 => Shape::Noop,
1 => shapes.remove(0),
_ => Shape::Vec(shapes),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paint_shape_draws_fill_without_stroke() {
let shape = paint_shape(
Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(10.0, 10.0)),
Color32::RED,
Stroke::NONE,
CornerRadius::ZERO,
Shadow::NONE,
);
match shape {
Shape::Rect(rect) => assert_eq!(rect.fill, Color32::RED),
other => panic!("expected filled rect, got {other:?}"),
}
}
}