#![forbid(unsafe_code)]
use egui::{Color32, CornerRadius, Margin, Painter, Rect, Shape, Stroke, Ui};
use makeover_layout::{Bevel, Depth, Edge, Fill};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Palette {
pub page: Color32,
pub raised: Color32,
pub overlay: Color32,
pub well: Option<Color32>,
pub bevel_light: Color32,
pub bevel_dark: Color32,
}
impl Palette {
#[must_use]
pub fn fill(&self, fill: Fill) -> Color32 {
match fill {
Fill::Page => self.page,
Fill::Raised => self.raised,
Fill::Overlay => self.overlay,
Fill::Well => self.well.unwrap_or(self.page),
}
}
#[must_use]
pub const fn edge(&self, edge: Edge) -> Color32 {
match edge {
Edge::Light => self.bevel_light,
Edge::Dark => self.bevel_dark,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FrameStyle {
pub radius: CornerRadius,
pub margin: Margin,
pub stroke: f32,
}
impl Default for FrameStyle {
fn default() -> Self {
Self {
radius: CornerRadius::ZERO,
margin: Margin::ZERO,
stroke: 1.0,
}
}
}
pub fn paint_bevel(painter: &Painter, rect: Rect, bevel: Bevel, palette: &Palette, stroke: f32) {
let (top_left, bottom_right) = bevel.edges();
let r = rect.shrink(stroke / 2.0);
painter.add(Shape::line(
vec![r.left_bottom(), r.left_top(), r.right_top()],
Stroke::new(stroke, palette.edge(top_left)),
));
painter.add(Shape::line(
vec![r.right_top(), r.right_bottom(), r.left_bottom()],
Stroke::new(stroke, palette.edge(bottom_right)),
));
}
pub fn frame<R>(
ui: &mut Ui,
depth: Depth,
palette: &Palette,
style: FrameStyle,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> R {
let mut f = egui::Frame::new()
.corner_radius(style.radius)
.inner_margin(style.margin);
if let Some(fill) = depth.fill() {
f = f.fill(palette.fill(fill));
}
let framed = f.show(ui, add_contents);
if let Some(bevel) = depth.bevel() {
paint_bevel(
ui.painter(),
framed.response.rect,
bevel,
palette,
style.stroke,
);
}
framed.inner
}
#[cfg(test)]
mod tests {
use super::*;
fn palette(well: Option<Color32>) -> Palette {
Palette {
page: Color32::from_rgb(1, 1, 1),
raised: Color32::from_rgb(2, 2, 2),
overlay: Color32::from_rgb(3, 3, 3),
well,
bevel_light: Color32::WHITE,
bevel_dark: Color32::BLACK,
}
}
#[test]
fn a_well_falls_back_to_the_page_before_makeover_2_3() {
let p = palette(None);
assert_eq!(p.fill(Fill::Well), p.page);
let w = Color32::from_rgb(9, 9, 9);
assert_eq!(palette(Some(w)).fill(Fill::Well), w);
}
#[test]
fn every_other_intent_resolves_without_a_fallback() {
let p = palette(None);
assert_eq!(p.fill(Fill::Page), p.page);
assert_eq!(p.fill(Fill::Raised), p.raised);
assert_eq!(p.fill(Fill::Overlay), p.overlay);
}
#[test]
fn a_raised_region_never_resolves_to_the_well_fill() {
let p = palette(Some(Color32::from_rgb(9, 9, 9)));
let raised = Depth::Raised.fill().map(|f| p.fill(f));
let well = Depth::Well.fill().map(|f| p.fill(f));
assert_eq!(raised, Some(p.raised));
assert_ne!(raised, well);
}
#[test]
fn the_lit_edge_swaps_when_a_card_is_pressed() {
let p = palette(None);
let (tl, _) = Depth::Raised.bevel().unwrap().edges();
let (ptl, _) = Depth::Raised.pressed().bevel().unwrap().edges();
assert_eq!(p.edge(tl), p.bevel_light);
assert_eq!(p.edge(ptl), p.bevel_dark);
}
#[test]
fn flat_asks_for_neither_fill_nor_edge() {
assert!(Depth::Flat.fill().is_none());
assert!(Depth::Flat.bevel().is_none());
}
#[test]
fn the_default_frame_is_square_and_one_point() {
let d = FrameStyle::default();
assert_eq!(d.radius, CornerRadius::ZERO);
assert_eq!(d.margin, Margin::ZERO);
assert!((d.stroke - 1.0).abs() < f32::EPSILON);
}
}