Merge pull request #5164 from dotCooCoo/fix/issue-3818

Fix #3818: the chosen brightness holds when the system theme differs
This commit is contained in:
Brandon Thomas
2026-11-01 23:45:09 -05:00
committed by GitHub
+91
View File
@@ -587,6 +587,15 @@ pub fn apply(ctx: &egui::Context, b: Brightness) {
pub fn apply_with(ctx: &egui::Context, b: Brightness, accent: Accent, scheme: Scheme) {
let t = Tokens::for_scheme(scheme, b, accent);
ctx.data_mut(|d| d.insert_temp(egui::Id::NULL, t));
// egui keeps a style for each system theme and by default follows the system's setting. The
// chosen brightness sets egui's theme, so these colors stay in force when the system's setting
// differs or changes. A change of theme starts from the current style, which keeps settings
// such as the tool tip delay.
let theme = if t.dark { egui::Theme::Dark } else { egui::Theme::Light };
if ctx.theme() != theme {
ctx.set_style_of(theme, ctx.global_style());
}
ctx.set_theme(theme);
let mut v = if t.dark { Visuals::dark() } else { Visuals::light() };
v.panel_fill = t.panel;
v.window_fill = t.panel;
@@ -669,4 +678,86 @@ mod tests {
assert_eq!(Scheme::parse(Scheme::Classic.id()), Some(Scheme::Classic));
}
}
/// #3818: the brightness chosen in Preferences stays in force when the system's light or dark
/// setting differs from it or changes while the app runs. Tool tips keep the theme's panel fill
/// and text color instead of egui's own white box and gray text.
#[test]
fn the_theme_holds_whatever_the_system_theme() {
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(400.0, 400.0));
for b in Brightness::ALL {
for scheme in Scheme::ALL {
for (first, then) in [(egui::Theme::Dark, egui::Theme::Light), (egui::Theme::Light, egui::Theme::Dark)] {
let t = Tokens::for_scheme(scheme, b, Accent::default());
let ctx = egui::Context::default();
let mut time = 0.0;
let mut tip = None;
for i in 0..5 {
time += 0.5;
let system_theme = Some(if i == 0 { first } else { then });
let events = if i == 1 { vec![egui::Event::PointerMoved(egui::pos2(30.0, 15.0))] } else { vec![] };
let input = egui::RawInput { time: Some(time), screen_rect: Some(screen), events, system_theme, ..Default::default() };
let mut out = ctx.run_ui(input, |ui| {
if i == 0 {
apply_with(ui.ctx(), b, Accent::default(), scheme);
ui.ctx().global_style_mut(|s| s.interaction.tooltip_delay = 0.0);
}
ui.button("Hover me").on_hover_text("Tip text");
});
out.textures_delta.clear();
// The tool tip's text color, and the fill of the box drawn behind it.
let mut shapes: Vec<&egui::Shape> = Vec::new();
let mut open: Vec<&egui::Shape> = out.shapes.iter().rev().map(|c| &c.shape).collect();
while let Some(shape) = open.pop() {
match shape {
egui::Shape::Vec(inner) => open.extend(inner.iter().rev()),
shape => shapes.push(shape),
}
}
tip = shapes.iter().enumerate().find_map(|(n, shape)| match shape {
egui::Shape::Text(s) if s.galley.text() == "Tip text" => {
let fill = shapes[..n].iter().rev().find_map(|behind| match behind {
egui::Shape::Rect(r) if r.rect.contains(s.pos) && r.fill.a() > 0 => Some(r.fill),
_ => None,
});
Some((s.fallback_color, fill))
}
_ => None,
});
}
let v = ctx.global_style().visuals.clone();
let what = format!("{scheme:?} {b:?}, system {first:?} then {then:?}");
assert_eq!((v.window_fill, v.override_text_color), (t.panel, Some(t.text)), "{what}");
assert_eq!(tip, Some((t.text, Some(t.panel))), "{what}: the tool tip's text and box");
assert!(contrast(t.text, t.panel) >= 4.5, "{what}: the tool tip's text against its box, {}", contrast(t.text, t.panel));
}
}
}
}
/// The contrast ratio of two opaque colors, from 1 (the same) to 21 (black on white), as WCAG 2
/// defines it.
fn contrast(a: egui::Color32, b: egui::Color32) -> f32 {
let luminance = |c: egui::Color32| {
let channel = |v: u8| {
let v = f32::from(v) / 255.0;
if v <= 0.040_45 { v / 12.92 } else { ((v + 0.055) / 1.055).powf(2.4) }
};
0.2126 * channel(c.r()) + 0.7152 * channel(c.g()) + 0.0722 * channel(c.b())
};
let (a, b) = (luminance(a), luminance(b));
(a.max(b) + 0.05) / (a.min(b) + 0.05)
}
/// Changing brightness keeps the interface settings made after the theme was applied, such as
/// General › Show Tool Tips' delay.
#[test]
fn changing_brightness_keeps_other_interface_settings() {
let ctx = egui::Context::default();
apply(&ctx, Brightness::Dark);
ctx.global_style_mut(|s| s.interaction.tooltip_delay = f32::INFINITY);
apply(&ctx, Brightness::Light);
assert_eq!(ctx.global_style().interaction.tooltip_delay, f32::INFINITY);
assert_eq!(ctx.global_style().visuals.window_fill, Tokens::for_brightness(Brightness::Light).panel);
}
}