Apply selected style to window elements on macOS

Qt doesn't apply the proper style to elements such as the title bar or text shadows, so this must be done in native code.

Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
This commit is contained in:
Kenneth Chew 2025-07-16 00:28:28 -04:00
parent 0dbaeef8a6
commit 3e65d3a9b5
No known key found for this signature in database
4 changed files with 91 additions and 0 deletions

View file

@ -1178,6 +1178,13 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h ui/instanceview/VisualGroup.h
) )
if (APPLE)
set(LAUNCHER_SOURCES
${LAUNCHER_SOURCES}
ui/themes/ThemeManager.mm
)
endif()
if (NOT Apple) if (NOT Apple)
set(LAUNCHER_SOURCES set(LAUNCHER_SOURCES
${LAUNCHER_SOURCES} ${LAUNCHER_SOURCES}

View file

@ -174,6 +174,13 @@ void ThemeManager::initializeWidgets()
themeDebugLog() << "<> Widget themes initialized."; themeDebugLog() << "<> Widget themes initialized.";
} }
#ifndef Q_OS_MACOS
void ThemeManager::setTitlebarColorOnMac(WId windowId, QColor color)
{}
void ThemeManager::setTitlebarColorOfAllWindowsOnMac(QColor color)
{}
#endif
QList<IconTheme*> ThemeManager::getValidIconThemes() QList<IconTheme*> ThemeManager::getValidIconThemes()
{ {
QList<IconTheme*> ret; QList<IconTheme*> ret;
@ -247,6 +254,7 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial)
auto& theme = themeIter->second; auto& theme = themeIter->second;
themeDebugLog() << "applying theme" << theme->name(); themeDebugLog() << "applying theme" << theme->name();
theme->apply(initial); theme->apply(initial);
setTitlebarColorOfAllWindowsOnMac(qApp->palette().window().color());
m_logColors = theme->logColorScheme(); m_logColors = theme->logColorScheme();
} else { } else {

View file

@ -81,6 +81,15 @@ class ThemeManager {
void initializeIcons(); void initializeIcons();
void initializeWidgets(); void initializeWidgets();
// On non-Mac systems, this is a no-op.
void setTitlebarColorOnMac(WId windowId, QColor color);
// This also will set the titlebar color of newly opened windows after this method is called.
// On non-Mac systems, this is a no-op.
void setTitlebarColorOfAllWindowsOnMac(QColor color);
#ifdef Q_OS_MACOS
NSObject* m_windowTitlebarObserver = nullptr;
#endif
const QStringList builtinIcons{ "pe_colored", "pe_light", "pe_dark", "pe_blue", "breeze_light", "breeze_dark", const QStringList builtinIcons{ "pe_colored", "pe_light", "pe_dark", "pe_blue", "breeze_light", "breeze_dark",
"OSX", "iOS", "flat", "flat_white", "multimc" }; "OSX", "iOS", "flat", "flat_white", "multimc" };
}; };

View file

@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2025 Kenneth Chew <79120643+kthchew@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ThemeManager.h"
#include <AppKit/AppKit.h>
void ThemeManager::setTitlebarColorOnMac(WId windowId, QColor color)
{
if (windowId == 0) {
return;
}
NSView* view = (NSView*)windowId;
NSWindow* window = [view window];
window.titlebarAppearsTransparent = YES;
window.backgroundColor = [NSColor colorWithRed:color.redF() green:color.greenF() blue:color.blueF() alpha:color.alphaF()];
// Unfortunately there seems to be no easy way to set the titlebar text color.
// The closest we can do without dubious hacks is set the dark/light mode state based on the brightness of the
// background color, which should at least make the text readable even if we can't use the theme's text color.
// It's a good idea to set this anyway since it also affects some other UI elements like text shadows (PrismLauncher#3825).
if (color.lightnessF() < 0.5) {
window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
} else {
window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
}
}
void ThemeManager::setTitlebarColorOfAllWindowsOnMac(QColor color)
{
NSArray<NSWindow*>* windows = [NSApp windows];
for (NSWindow* window : windows) {
setTitlebarColorOnMac((WId)window.contentView, color);
}
// We want to change the titlebar color of newly opened windows as well.
// There's no notification for when a new window is opened, but we can set the color when a window switches
// from occluded to visible, which also fires on open.
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
if (m_windowTitlebarObserver) {
[center removeObserver:m_windowTitlebarObserver];
m_windowTitlebarObserver = nil;
}
m_windowTitlebarObserver = [center addObserverForName:NSWindowDidChangeOcclusionStateNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* notification) {
NSWindow* window = notification.object;
setTitlebarColorOnMac((WId)window.contentView, color);
}];
}