Merge pull request #3650 from insideTheFlux/fix/3381-title-bar

LR-BEHAV-TITLEBAR: the Windows title bar takes LightCraft's theme at launch (#3381)
This commit is contained in:
FredApps
2026-11-05 10:22:34 +01:00
committed by GitHub
3 changed files with 72 additions and 0 deletions
+1
View File
@@ -265,6 +265,7 @@ impl LightcraftApp {
self.import_dropped_files(ctx);
}
self.check_external_editor(ctx);
theme::refresh_window_on_first_focus(ctx);
// Alt (Option on macOS) while a slider is dragged shows its temporary preview
self.ui.alt_held = ctx.input(|i| i.modifiers.alt);
// Recent-library checks and launch-list keys read focus/time/keys: they belong to the UI
+64
View File
@@ -412,6 +412,25 @@ pub fn current(ctx: &egui::Context) -> Option<ThemeKind> {
ctx.data(|d| d.get_temp::<ThemeKind>(theme_id()))
}
/// Windows only: tell the window its theme again, the first time it has focus. A first theme that
/// reaches a focused window as it comes up changes the title's colour but not the caption's, so
/// with LightCraft dark on a light system the title is white on a light caption (it looks blank),
/// and light on dark is dark on dark, until focus next moves (#3381). The same command repaints
/// the caption once the window is up. A window that opens without focus shows the right colours
/// from the start.
pub fn refresh_window_on_first_focus(ctx: &egui::Context) {
let done = egui::Id::unique("lightcraft-window-theme-refreshed");
if !cfg!(target_os = "windows") || !ctx.input(|i| i.focused) || ctx.data(|d| d.get_temp::<bool>(done)).is_some() {
return;
}
ctx.data_mut(|d| d.insert_temp(done, true));
let theme = match ctx.theme() {
egui::Theme::Dark => egui::SystemTheme::Dark,
egui::Theme::Light => egui::SystemTheme::Light,
};
ctx.send_viewport_cmd(egui::ViewportCommand::SetTheme(theme));
}
/// Style `ctx` with the default theme.
pub fn apply(ctx: &egui::Context) {
apply_kind(ctx, ThemeKind::default());
@@ -506,6 +525,51 @@ mod tests {
}
}
/// The `SetTheme(Light)` commands one frame of `ctx` sends the window, with or without
/// [`refresh_window_on_first_focus`] in it.
#[cfg(target_os = "windows")]
fn light_theme_commands(ctx: &egui::Context, focused: bool, refresh: bool) -> usize {
let raw = egui::RawInput { focused, ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| {
if refresh {
refresh_window_on_first_focus(ui.ctx());
}
});
out.textures_delta.clear();
let commands = out.viewport_output.remove(&egui::ViewportId::ROOT).map(|v| v.commands).unwrap_or_default();
commands.iter().filter(|c| matches!(c, egui::ViewportCommand::SetTheme(egui::SystemTheme::Light))).count()
}
/// #3381: egui sends the theme with the first frame, and the window hears it once more the
/// first time it has focus, not on every later focus.
#[cfg(target_os = "windows")]
#[test]
fn the_window_hears_its_theme_again_the_first_time_it_has_focus() {
let ctx = egui::Context::default();
apply_kind(&ctx, ThemeKind::Silver);
let sent = |focused: bool| light_theme_commands(&ctx, focused, true);
assert_eq!(sent(false), 1, "egui's own, with the first frame");
assert_eq!(sent(false), 0, "no focus yet");
assert_eq!(sent(true), 1, "the first frame with focus");
assert_eq!(sent(true), 0, "still focused");
assert_eq!(sent(false), 0, "focus lost");
assert_eq!(sent(true), 0, "focus back: Windows repaints this by itself");
}
/// #3381: a window that has focus from its very first frame hears the theme once more in that
/// frame, on top of egui's own command. (In the app the first frame never gets here: `ui`
/// returns before the fonts are ready, so egui's command always goes a frame earlier.)
#[cfg(target_os = "windows")]
#[test]
fn a_window_focused_from_its_first_frame_hears_its_theme_again_in_that_frame() {
let first_frame = |refresh: bool| {
let ctx = egui::Context::default();
apply_kind(&ctx, ThemeKind::Silver);
light_theme_commands(&ctx, true, refresh)
};
assert_eq!(first_frame(true), first_frame(false) + 1);
}
/// Every theme scrolls 100 points a wheel notch, and a zoom notch stays egui's stock ~1.22x
/// (`exp(40 / 200)`) instead of growing with the longer scroll.
#[test]
+7
View File
@@ -12,6 +12,13 @@
hot pixels on a sensor. Stars and other points of light that are kept don't count, so a dense star field still
loses its hot pixels. Settings ▸ Performance ▸ Hot pixel limit sets the share (0.05 % to 1 %).
## 2026-11-02
### The Windows title bar is readable from the moment the window opens
- When LightCraft's appearance differs from the one set in Windows (the dark default on a light Windows, for one), the
title bar kept the system's colour with the title in LightCraft's, so the title looked blank until focus moved to
another window. It now takes LightCraft's colours as soon as the window has focus.
## 2026-11-01
### Korean and Vietnamese text in exports draws whole