Skip to main content

max / makeover-immediate

Answer what overlaying means in immediate mode Wave 2 tier A1, the egui half, answered rather than skipped. Palette gains `elevation` and `cast()` returns it as an egui::Shadow. Unlike a bevel, this needed no painter. egui already paints shadows for its own menus and windows, so the honest port is to hand that machinery the theme's tone instead of egui's default. The geometry matches what makeover-webview composes, in points rather than pixels. Breaking: Palette grows a required field, on the same footing as `well` and `sunken`. makeover derives the intent for every theme, so a resolved palette without one is not a thing that exists.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-10 14:41 UTC
Signed with PGP, not checked
Commit: 7736658403fa69d86859a4ec783f8dbca2dcb4ff
Parent: a3c14d7
1 file changed, +58 insertions, -0 deletions
M src/lib.rs +58
@@ -107,6 +107,22 @@
107 107 pub bevel_light: Color32,
108 108 /// `bevel-dark`.
109 109 pub bevel_dark: Color32,
110 + /// `elevation`.
111 + ///
112 + /// What a surface that floats OVER the page is cast onto it with. The one
113 + /// intent here that is about a surface's relationship to the page rather
114 + /// than about the surface, which is why it is a translucent near-black on
115 + /// every theme rather than something read off the palette's own ramp.
116 + ///
117 + /// **Only for a surface that overlays.** A menu, a tooltip, a modal. A
118 + /// surface *in* the layout takes a bevel, and reaching for this on a panel
119 + /// or a card is how a pre-Platinum look survives a conversion under a new
120 + /// name.
121 + ///
122 + /// egui has a real answer for this where a terminal does not: see
123 + /// [`Palette::cast`], which is the shadow to hand an
124 + /// [`egui::Frame`](egui::Frame).
125 + pub elevation: Color32,
110 126 /// `content`.
111 127 ///
112 128 /// Ordinary text. Added 0.5.0 with the field renderer, which is the first
@@ -154,6 +170,35 @@
154 170 }
155 171 }
156 172
173 + /// The cast shadow for a surface that overlays the page.
174 + ///
175 + /// What "overlaying" means in immediate mode, answered rather than skipped.
176 + /// egui already paints shadows for its menus and windows through
177 + /// [`egui::Frame::shadow`], so the honest port is to hand that machinery the
178 + /// theme's tone instead of egui's own default, not to invent a painter here
179 + /// the way [`paint_bevel`] had to.
180 + ///
181 + /// The geometry matches what `makeover-webview` composes, in points rather
182 + /// than pixels: a small downward offset and a wide soft blur. A Platinum-era
183 + /// menu sits just off the page rather than hovering above it.
184 + ///
185 + /// ```no_run
186 + /// # let palette: makeover_immediate::Palette = unimplemented!();
187 + /// # let ui: &mut egui::Ui = unimplemented!();
188 + /// egui::Frame::popup(ui.style())
189 + /// .shadow(palette.cast())
190 + /// .show(ui, |ui| { ui.label("over the page"); });
191 + /// ```
192 + #[must_use]
193 + pub const fn cast(&self) -> egui::Shadow {
194 + egui::Shadow {
195 + offset: [0, 2],
196 + blur: 24,
197 + spread: 0,
198 + color: self.elevation,
199 + }
200 + }
201 +
157 202 /// Resolve a bevel edge intent.
158 203 #[must_use]
159 204 pub const fn edge(&self, edge: Edge) -> Color32 {
@@ -602,12 +647,25 @@
602 647 sunken: Color32::from_rgb(4, 4, 4),
603 648 bevel_light: Color32::WHITE,
604 649 bevel_dark: Color32::BLACK,
650 + elevation: Color32::from_black_alpha(46),
605 651 content: Color32::from_rgb(5, 5, 5),
606 652 content_muted: Color32::from_rgb(6, 6, 6),
607 653 danger: Color32::from_rgb(7, 7, 7),
608 654 }
609 655 }
610 656
657 + /// The cast is egui's own shadow type carrying the theme's tone, which is
658 + /// the whole of what this crate had to decide for it: unlike a bevel, egui
659 + /// already knows how to paint one.
660 + #[test]
661 + fn the_cast_hands_egui_the_themes_tone() {
662 + let p = palette(Color32::from_rgb(9, 9, 9));
663 + let cast = p.cast();
664 + assert_eq!(cast.color, p.elevation);
665 + assert!(cast.blur > 0, "a cast shadow is soft");
666 + assert_eq!(cast.offset, [0, 2], "it falls downward and only a little");
667 + }
668 +
611 669 #[test]
612 670 fn a_well_resolves_to_its_own_token() {
613 671 // No substitution left. The page-filled well was a stand-in for a