mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 00:47:22 +00:00
Add wallpaper-driven Shift dynamic theming
Expose theme and accent controls in MobileShellSettings and the\nAppearance KCM. Generate ShiftWallpaperDark/Light from Shift base\nschemes, apply them through KDE color tools, and debounce wallpaper\ncolor updates. Register a dedicated dynamic-theming regression test.
This commit is contained in:
parent
4af811c97c
commit
08264e5759
9 changed files with 938 additions and 17 deletions
|
|
@ -12,6 +12,7 @@ target_link_libraries(shellsettingsplugin PRIVATE
|
||||||
Qt::DBus
|
Qt::DBus
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
|
KF6::ConfigCore
|
||||||
Plasma::KWaylandClient
|
Plasma::KWaylandClient
|
||||||
KF6::Service
|
KF6::Service
|
||||||
KF6::Package
|
KF6::Package
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,163 @@
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#include <QDBusPendingCall>
|
#include <QDBusPendingCall>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QtMath>
|
||||||
|
|
||||||
const QString CONFIG_FILE = QStringLiteral("plasmamobilerc");
|
const QString CONFIG_FILE = QStringLiteral("plasmamobilerc");
|
||||||
const QString GENERAL_CONFIG_GROUP = QStringLiteral("General");
|
const QString GENERAL_CONFIG_GROUP = QStringLiteral("General");
|
||||||
|
const QString KDE_GLOBALS_CONFIG_GROUP = QStringLiteral("General");
|
||||||
const QString LOCKSCREEN_CONFIG_GROUP = QStringLiteral("Lockscreen");
|
const QString LOCKSCREEN_CONFIG_GROUP = QStringLiteral("Lockscreen");
|
||||||
const QString QUICKSETTINGS_CONFIG_GROUP = QStringLiteral("QuickSettings");
|
const QString QUICKSETTINGS_CONFIG_GROUP = QStringLiteral("QuickSettings");
|
||||||
|
const QString WALLPAPER_THEME_MANUAL_DARK_KEY = QStringLiteral("wallpaperThemeManualDarkTheme");
|
||||||
|
const QString SHIFT_DARK_COLOR_SCHEME = QStringLiteral("ShiftDark");
|
||||||
|
const QString SHIFT_LIGHT_COLOR_SCHEME = QStringLiteral("ShiftLight");
|
||||||
|
const QString SHIFT_WALLPAPER_DARK_COLOR_SCHEME = QStringLiteral("ShiftWallpaperDark");
|
||||||
|
const QString SHIFT_WALLPAPER_LIGHT_COLOR_SCHEME = QStringLiteral("ShiftWallpaperLight");
|
||||||
|
const QString SHIFT_DARK_PLASMA_THEME = QStringLiteral("shift-dark");
|
||||||
|
const QString SHIFT_LIGHT_PLASMA_THEME = QStringLiteral("shift-light");
|
||||||
|
constexpr qreal MIN_NORMAL_CONTRAST = 4.5;
|
||||||
|
constexpr qreal MIN_INACTIVE_CONTRAST = 3.0;
|
||||||
|
constexpr qreal MIN_LINK_CONTRAST = 3.5;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
QString baseColorSchemeName(bool dark)
|
||||||
|
{
|
||||||
|
return dark ? SHIFT_DARK_COLOR_SCHEME : SHIFT_LIGHT_COLOR_SCHEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString wallpaperColorSchemeName(bool dark)
|
||||||
|
{
|
||||||
|
return dark ? SHIFT_WALLPAPER_DARK_COLOR_SCHEME : SHIFT_WALLPAPER_LIGHT_COLOR_SCHEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString wallpaperColorSchemeDisplayName(bool dark)
|
||||||
|
{
|
||||||
|
return dark ? QStringLiteral("SHIFT Wallpaper Dark") : QStringLiteral("SHIFT Wallpaper Light");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString rgbString(const QColor &color)
|
||||||
|
{
|
||||||
|
return QStringLiteral("%1,%2,%3").arg(color.red()).arg(color.green()).arg(color.blue());
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal relativeLuminance(const QColor &color)
|
||||||
|
{
|
||||||
|
return 0.2126 * color.redF() + 0.7152 * color.greenF() + 0.0722 * color.blueF();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal linearizedChannel(qreal channel)
|
||||||
|
{
|
||||||
|
return channel <= 0.04045 ? channel / 12.92 : qPow((channel + 0.055) / 1.055, 2.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal wcagLuminance(const QColor &color)
|
||||||
|
{
|
||||||
|
return 0.2126 * linearizedChannel(color.redF()) + 0.7152 * linearizedChannel(color.greenF()) + 0.0722 * linearizedChannel(color.blueF());
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal contrastRatio(const QColor &left, const QColor &right)
|
||||||
|
{
|
||||||
|
const qreal leftLum = wcagLuminance(left);
|
||||||
|
const qreal rightLum = wcagLuminance(right);
|
||||||
|
const qreal lighter = qMax(leftLum, rightLum);
|
||||||
|
const qreal darker = qMin(leftLum, rightLum);
|
||||||
|
return (lighter + 0.05) / (darker + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor contrastingTextColor(const QColor &color);
|
||||||
|
QColor blendColors(const QColor &base, const QColor &overlay, qreal overlayAmount);
|
||||||
|
|
||||||
|
QColor ensureContrast(const QColor &foreground, const QColor &background, qreal minimumContrast)
|
||||||
|
{
|
||||||
|
if (contrastRatio(foreground, background) >= minimumContrast) {
|
||||||
|
return foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QColor highContrastTarget = contrastingTextColor(background);
|
||||||
|
for (int i = 1; i <= 12; ++i) {
|
||||||
|
const qreal amount = i / 12.0;
|
||||||
|
const QColor candidate = blendColors(foreground, highContrastTarget, amount);
|
||||||
|
if (contrastRatio(candidate, background) >= minimumContrast) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return highContrastTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor contrastingTextColor(const QColor &color)
|
||||||
|
{
|
||||||
|
const QColor white{Qt::white};
|
||||||
|
const QColor black{Qt::black};
|
||||||
|
return contrastRatio(color, white) >= contrastRatio(color, black) ? white : black;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor blendColors(const QColor &base, const QColor &overlay, qreal overlayAmount)
|
||||||
|
{
|
||||||
|
const qreal clampedAmount = qBound(0.0, overlayAmount, 1.0);
|
||||||
|
const qreal baseAmount = 1.0 - clampedAmount;
|
||||||
|
return QColor::fromRgbF(base.redF() * baseAmount + overlay.redF() * clampedAmount,
|
||||||
|
base.greenF() * baseAmount + overlay.greenF() * clampedAmount,
|
||||||
|
base.blueF() * baseAmount + overlay.blueF() * clampedAmount,
|
||||||
|
1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeAccentEntries(const KSharedConfig::Ptr &config, const QString &groupName, const QColor &accentColor)
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{config, groupName};
|
||||||
|
group.writeEntry("DecorationFocus", rgbString(accentColor));
|
||||||
|
group.writeEntry("DecorationHover", rgbString(accentColor));
|
||||||
|
group.writeEntry("ForegroundActive", rgbString(accentColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tintBackgroundEntries(const KSharedConfig::Ptr &config, const QString &groupName, const QColor &accentColor, bool dark)
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{config, groupName};
|
||||||
|
const QColor defaultBase = dark ? QColor(38, 41, 47) : QColor(244, 246, 251);
|
||||||
|
const QColor baseNormal = group.readEntry("BackgroundNormal", defaultBase);
|
||||||
|
const QColor baseAlternate = group.readEntry("BackgroundAlternate", baseNormal);
|
||||||
|
const qreal normalBlend = dark ? 0.22 : 0.12;
|
||||||
|
const qreal alternateBlend = dark ? 0.16 : 0.08;
|
||||||
|
|
||||||
|
group.writeEntry("BackgroundNormal", rgbString(blendColors(baseNormal, accentColor, normalBlend)));
|
||||||
|
group.writeEntry("BackgroundAlternate", rgbString(blendColors(baseAlternate, accentColor, alternateBlend)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tuneForegroundEntries(const KSharedConfig::Ptr &config, const QString &groupName, const QColor &accentColor, bool dark)
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{config, groupName};
|
||||||
|
const QColor bgNormal = group.readEntry("BackgroundNormal", dark ? QColor(34, 37, 50) : QColor(252, 252, 252));
|
||||||
|
const QColor baseNormal = contrastingTextColor(bgNormal);
|
||||||
|
const QColor baseInactive = blendColors(baseNormal, bgNormal, dark ? 0.44 : 0.56);
|
||||||
|
const QColor baseLink = dark ? blendColors(accentColor, QColor(Qt::white), 0.28) : blendColors(accentColor, QColor(Qt::black), 0.24);
|
||||||
|
const QColor fgNormal = ensureContrast(baseNormal, bgNormal, MIN_NORMAL_CONTRAST);
|
||||||
|
const QColor fgInactive = ensureContrast(baseInactive, bgNormal, MIN_INACTIVE_CONTRAST);
|
||||||
|
const QColor fgLink = ensureContrast(baseLink, bgNormal, MIN_LINK_CONTRAST);
|
||||||
|
const QColor fgActive = ensureContrast(accentColor, bgNormal, MIN_LINK_CONTRAST);
|
||||||
|
|
||||||
|
group.writeEntry("ForegroundNormal", rgbString(fgNormal));
|
||||||
|
group.writeEntry("ForegroundInactive", rgbString(fgInactive));
|
||||||
|
group.writeEntry("ForegroundActive", rgbString(fgActive));
|
||||||
|
group.writeEntry("ForegroundLink", rgbString(fgLink));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MobileShellSettings::MobileShellSettings(QObject *parent)
|
MobileShellSettings::MobileShellSettings(QObject *parent)
|
||||||
: QObject{parent}
|
: QObject{parent}
|
||||||
, m_config{KSharedConfig::openConfig(CONFIG_FILE)}
|
, m_config{KSharedConfig::openConfig(CONFIG_FILE)}
|
||||||
|
, m_kdeGlobalsConfig{KSharedConfig::openConfig()}
|
||||||
{
|
{
|
||||||
|
m_wallpaperThemeTimer.setSingleShot(true);
|
||||||
|
m_wallpaperThemeTimer.setInterval(450);
|
||||||
|
connect(&m_wallpaperThemeTimer, &QTimer::timeout, this, [this]() -> void {
|
||||||
|
setDarkThemeEnabled(m_pendingWallpaperThemeDark);
|
||||||
|
});
|
||||||
|
|
||||||
m_configWatcher = KConfigWatcher::create(m_config);
|
m_configWatcher = KConfigWatcher::create(m_config);
|
||||||
connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void {
|
connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void {
|
||||||
Q_UNUSED(names)
|
Q_UNUSED(names)
|
||||||
|
|
@ -53,6 +200,7 @@ MobileShellSettings::MobileShellSettings(QObject *parent)
|
||||||
Q_EMIT dynamicTilingLayoutStateChanged();
|
Q_EMIT dynamicTilingLayoutStateChanged();
|
||||||
Q_EMIT snapLayoutsEnabledChanged();
|
Q_EMIT snapLayoutsEnabledChanged();
|
||||||
Q_EMIT allowLogoutChanged();
|
Q_EMIT allowLogoutChanged();
|
||||||
|
Q_EMIT wallpaperThemeEnabledChanged();
|
||||||
}
|
}
|
||||||
if (group.name() == LOCKSCREEN_CONFIG_GROUP) {
|
if (group.name() == LOCKSCREEN_CONFIG_GROUP) {
|
||||||
Q_EMIT lockscreenLeftButtonActionChanged();
|
Q_EMIT lockscreenLeftButtonActionChanged();
|
||||||
|
|
@ -62,6 +210,24 @@ MobileShellSettings::MobileShellSettings(QObject *parent)
|
||||||
Q_EMIT quickSettingsColumnsChanged();
|
Q_EMIT quickSettingsColumnsChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_kdeGlobalsConfigWatcher = KConfigWatcher::create(m_kdeGlobalsConfig);
|
||||||
|
connect(m_kdeGlobalsConfigWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void {
|
||||||
|
if (group.name() != KDE_GLOBALS_CONFIG_GROUP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (names.contains(QByteArrayLiteral("ColorScheme"))) {
|
||||||
|
Q_EMIT colorSchemeChanged();
|
||||||
|
Q_EMIT darkThemeEnabledChanged();
|
||||||
|
}
|
||||||
|
if (names.contains(QByteArrayLiteral("AccentColor")) || names.contains(QByteArrayLiteral("LastUsedCustomAccentColor"))) {
|
||||||
|
Q_EMIT accentColorChanged();
|
||||||
|
}
|
||||||
|
if (names.contains(QByteArrayLiteral("accentColorFromWallpaper"))) {
|
||||||
|
Q_EMIT wallpaperAccentEnabledChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MobileShellSettings::vibrationsEnabled() const
|
bool MobileShellSettings::vibrationsEnabled() const
|
||||||
|
|
@ -243,6 +409,312 @@ void MobileShellSettings::setConvergenceModeEnabled(bool enabled)
|
||||||
job->start();
|
job->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MobileShellSettings::colorScheme() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP};
|
||||||
|
return group.readEntry("ColorScheme", SHIFT_DARK_COLOR_SCHEME);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MobileShellSettings::darkThemeEnabled() const
|
||||||
|
{
|
||||||
|
return isDarkColorScheme(colorScheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::setDarkThemeEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
const QString currentColorScheme = colorScheme();
|
||||||
|
const QString nextColorScheme = effectiveColorSchemeName(enabled);
|
||||||
|
const QString plasmaTheme = enabled ? SHIFT_DARK_PLASMA_THEME : SHIFT_LIGHT_PLASMA_THEME;
|
||||||
|
QColor accentOverride;
|
||||||
|
|
||||||
|
if (wallpaperAccentEnabled() && m_lastWallpaperThemeColor.isValid() && m_lastWallpaperThemeColor.alpha() != 0) {
|
||||||
|
accentOverride = m_lastWallpaperThemeColor;
|
||||||
|
} else if (!wallpaperAccentEnabled()) {
|
||||||
|
const QColor manualAccent = accentColor();
|
||||||
|
if (manualAccent.isValid() && manualAccent.alpha() != 0) {
|
||||||
|
accentOverride = manualAccent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentColorScheme == nextColorScheme && nextColorScheme == baseColorSchemeName(enabled)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accentOverride.isValid() && accentOverride.alpha() != 0) {
|
||||||
|
applyColorScheme({QStringLiteral("--accent-color"), accentOverride.name(QColor::HexRgb), nextColorScheme});
|
||||||
|
} else {
|
||||||
|
QProcess::execute(QStringLiteral("plasma-apply-colorscheme"), {nextColorScheme});
|
||||||
|
m_kdeGlobalsConfig->reparseConfiguration();
|
||||||
|
}
|
||||||
|
QProcess::execute(QStringLiteral("plasma-apply-desktoptheme"), {plasmaTheme});
|
||||||
|
|
||||||
|
Q_EMIT colorSchemeChanged();
|
||||||
|
Q_EMIT darkThemeEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MobileShellSettings::wallpaperThemeEnabled() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
return group.readEntry("wallpaperThemeEnabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::setWallpaperThemeEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (wallpaperThemeEnabled() == enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
if (enabled) {
|
||||||
|
group.writeEntry(WALLPAPER_THEME_MANUAL_DARK_KEY, darkThemeEnabled(), KConfigGroup::Notify);
|
||||||
|
}
|
||||||
|
group.writeEntry("wallpaperThemeEnabled", enabled, KConfigGroup::Notify);
|
||||||
|
m_config->sync();
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
m_wallpaperThemeTimer.stop();
|
||||||
|
setDarkThemeEnabled(group.readEntry(WALLPAPER_THEME_MANUAL_DARK_KEY, darkThemeEnabled()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_EMIT wallpaperThemeEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::applyWallpaperThemeColor(const QColor &color)
|
||||||
|
{
|
||||||
|
if (!wallpaperThemeEnabled() || !color.isValid() || color.alpha() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor opaqueColor = color;
|
||||||
|
opaqueColor.setAlpha(255);
|
||||||
|
|
||||||
|
const bool colorChanged = m_lastWallpaperThemeColor != opaqueColor;
|
||||||
|
m_lastWallpaperThemeColor = opaqueColor;
|
||||||
|
|
||||||
|
if (colorChanged) {
|
||||||
|
Q_EMIT wallpaperThemeColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
const qreal luminance = relativeLuminance(opaqueColor);
|
||||||
|
const bool shouldUseDarkTheme = luminance < 0.5;
|
||||||
|
const bool sameThemeBucket = darkThemeEnabled() == shouldUseDarkTheme;
|
||||||
|
|
||||||
|
if (wallpaperAccentEnabled() && colorChanged && sameThemeBucket) {
|
||||||
|
applyColorScheme({QStringLiteral("--accent-color"), opaqueColor.name(QColor::HexRgb), colorScheme()});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_wallpaperThemeTimer.isActive() && darkThemeEnabled() == shouldUseDarkTheme && !colorChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pendingWallpaperThemeDark = shouldUseDarkTheme;
|
||||||
|
m_wallpaperThemeTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor MobileShellSettings::wallpaperThemeColor() const
|
||||||
|
{
|
||||||
|
return m_lastWallpaperThemeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor MobileShellSettings::accentColor() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP};
|
||||||
|
return group.readEntry("AccentColor", QColor(Qt::transparent));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::setAccentColor(const QColor &color)
|
||||||
|
{
|
||||||
|
if (!color.isValid() || color.alpha() == 0) {
|
||||||
|
resetAccentColor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor opaqueColor = color;
|
||||||
|
opaqueColor.setAlpha(255);
|
||||||
|
|
||||||
|
auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP};
|
||||||
|
group.writeEntry("accentColorFromWallpaper", false, KConfigGroup::Notify);
|
||||||
|
group.writeEntry("LastUsedCustomAccentColor", opaqueColor, KConfigGroup::Notify);
|
||||||
|
m_kdeGlobalsConfig->sync();
|
||||||
|
|
||||||
|
applyColorScheme({QStringLiteral("--accent-color"), opaqueColor.name(QColor::HexRgb), colorScheme()});
|
||||||
|
|
||||||
|
Q_EMIT wallpaperAccentEnabledChanged();
|
||||||
|
Q_EMIT accentColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MobileShellSettings::wallpaperAccentEnabled() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP};
|
||||||
|
return group.readEntry("accentColorFromWallpaper", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::setWallpaperAccentEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (wallpaperAccentEnabled() == enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP};
|
||||||
|
group.writeEntry("accentColorFromWallpaper", enabled, KConfigGroup::Notify);
|
||||||
|
m_kdeGlobalsConfig->sync();
|
||||||
|
|
||||||
|
if (enabled && m_lastWallpaperThemeColor.isValid() && m_lastWallpaperThemeColor.alpha() != 0) {
|
||||||
|
applyColorScheme({QStringLiteral("--accent-color"), m_lastWallpaperThemeColor.name(QColor::HexRgb), colorScheme()});
|
||||||
|
Q_EMIT accentColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
const QColor lastCustomColor = lastUsedCustomAccentColor();
|
||||||
|
if (lastCustomColor.isValid() && lastCustomColor.alpha() != 0) {
|
||||||
|
applyColorScheme({QStringLiteral("--accent-color"), lastCustomColor.name(QColor::HexRgb), colorScheme()});
|
||||||
|
} else {
|
||||||
|
resetAccentColor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_EMIT wallpaperAccentEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::resetAccentColor()
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP};
|
||||||
|
group.writeEntry("accentColorFromWallpaper", false, KConfigGroup::Notify);
|
||||||
|
group.deleteEntry("AccentColor", KConfigGroup::Notify);
|
||||||
|
m_kdeGlobalsConfig->sync();
|
||||||
|
|
||||||
|
applyColorScheme({colorScheme()});
|
||||||
|
|
||||||
|
Q_EMIT wallpaperAccentEnabledChanged();
|
||||||
|
Q_EMIT accentColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor MobileShellSettings::lastUsedCustomAccentColor() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP};
|
||||||
|
return group.readEntry("LastUsedCustomAccentColor", QColor(Qt::transparent));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::applyColorScheme(const QStringList &arguments)
|
||||||
|
{
|
||||||
|
QProcess::execute(QStringLiteral("plasma-apply-colorscheme"), arguments);
|
||||||
|
m_kdeGlobalsConfig->reparseConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MobileShellSettings::effectiveColorSchemeName(bool dark) const
|
||||||
|
{
|
||||||
|
if (!wallpaperThemeEnabled() || !m_lastWallpaperThemeColor.isValid() || m_lastWallpaperThemeColor.alpha() == 0) {
|
||||||
|
return baseColorSchemeName(dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ensureWallpaperColorScheme(m_lastWallpaperThemeColor, dark)) {
|
||||||
|
return baseColorSchemeName(dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallpaperColorSchemeName(dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MobileShellSettings::isDarkColorScheme(const QString &schemeName) const
|
||||||
|
{
|
||||||
|
return schemeName == SHIFT_DARK_COLOR_SCHEME || schemeName == SHIFT_WALLPAPER_DARK_COLOR_SCHEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MobileShellSettings::ensureWallpaperColorScheme(const QColor &accentColor, bool dark) const
|
||||||
|
{
|
||||||
|
if (!accentColor.isValid() || accentColor.alpha() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString sourcePath =
|
||||||
|
QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes/%1.colors").arg(baseColorSchemeName(dark)));
|
||||||
|
if (sourcePath.isEmpty()) {
|
||||||
|
qWarning() << "Unable to locate base Shift color scheme for wallpaper generation:" << baseColorSchemeName(dark);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString outputDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/color-schemes");
|
||||||
|
if (!QDir().mkpath(outputDir)) {
|
||||||
|
qWarning() << "Unable to create dynamic color-scheme directory:" << outputDir;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString outputPath = outputDir + QStringLiteral("/%1.colors").arg(wallpaperColorSchemeName(dark));
|
||||||
|
QFile::remove(outputPath);
|
||||||
|
if (!QFile::copy(sourcePath, outputPath)) {
|
||||||
|
qWarning() << "Unable to create wallpaper-derived color scheme:" << outputPath;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto generatedConfig = KSharedConfig::openConfig(outputPath, KConfig::SimpleConfig);
|
||||||
|
auto generalGroup = KConfigGroup{generatedConfig, QStringLiteral("General")};
|
||||||
|
generalGroup.writeEntry("ColorScheme", wallpaperColorSchemeName(dark));
|
||||||
|
generalGroup.writeEntry("Name", wallpaperColorSchemeDisplayName(dark));
|
||||||
|
|
||||||
|
writeAccentEntries(generatedConfig, QStringLiteral("Colors:Button"), accentColor);
|
||||||
|
writeAccentEntries(generatedConfig, QStringLiteral("Colors:Complementary"), accentColor);
|
||||||
|
writeAccentEntries(generatedConfig, QStringLiteral("Colors:Header"), accentColor);
|
||||||
|
writeAccentEntries(generatedConfig, QStringLiteral("Colors:Header][Inactive"), accentColor);
|
||||||
|
writeAccentEntries(generatedConfig, QStringLiteral("Colors:Tooltip"), accentColor);
|
||||||
|
writeAccentEntries(generatedConfig, QStringLiteral("Colors:View"), accentColor);
|
||||||
|
writeAccentEntries(generatedConfig, QStringLiteral("Colors:Window"), accentColor);
|
||||||
|
|
||||||
|
tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Button"), accentColor, dark);
|
||||||
|
tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:View"), accentColor, dark);
|
||||||
|
tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Window"), accentColor, dark);
|
||||||
|
tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Header"), accentColor, dark);
|
||||||
|
tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Header][Inactive"), accentColor, dark);
|
||||||
|
tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Tooltip"), accentColor, dark);
|
||||||
|
|
||||||
|
tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Button"), accentColor, dark);
|
||||||
|
tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:View"), accentColor, dark);
|
||||||
|
tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Window"), accentColor, dark);
|
||||||
|
tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Header"), accentColor, dark);
|
||||||
|
tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Header][Inactive"), accentColor, dark);
|
||||||
|
tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Tooltip"), accentColor, dark);
|
||||||
|
|
||||||
|
auto complementaryGroup = KConfigGroup{generatedConfig, QStringLiteral("Colors:Complementary")};
|
||||||
|
const QColor complementaryBase = complementaryGroup.readEntry("BackgroundNormal", dark ? QColor(24, 27, 38) : QColor(42, 46, 50));
|
||||||
|
complementaryGroup.writeEntry("BackgroundNormal", rgbString(blendColors(complementaryBase, accentColor, dark ? 0.18 : 0.14)));
|
||||||
|
complementaryGroup.writeEntry("BackgroundAlternate", rgbString(blendColors(complementaryBase, accentColor, dark ? 0.12 : 0.09)));
|
||||||
|
tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Complementary"), accentColor, dark);
|
||||||
|
|
||||||
|
auto selectionGroup = KConfigGroup{generatedConfig, QStringLiteral("Colors:Selection")};
|
||||||
|
const QColor selectionBackground = blendColors(accentColor, dark ? QColor(18, 20, 28) : QColor(255, 255, 255), dark ? 0.82 : 0.9);
|
||||||
|
const QColor selectionAlternate = blendColors(selectionBackground, dark ? QColor(0, 0, 0) : QColor(255, 255, 255), dark ? 0.16 : 0.24);
|
||||||
|
const QColor selectionForeground = ensureContrast(contrastingTextColor(selectionBackground), selectionBackground, MIN_NORMAL_CONTRAST);
|
||||||
|
const QColor selectionInactive =
|
||||||
|
ensureContrast(blendColors(selectionForeground, selectionBackground, dark ? 0.4 : 0.52), selectionBackground, MIN_INACTIVE_CONTRAST);
|
||||||
|
const QColor selectionLink = ensureContrast(blendColors(accentColor, selectionForeground, dark ? 0.3 : 0.2), selectionBackground, MIN_LINK_CONTRAST);
|
||||||
|
selectionGroup.writeEntry("BackgroundNormal", rgbString(selectionBackground));
|
||||||
|
selectionGroup.writeEntry("BackgroundAlternate", rgbString(selectionAlternate));
|
||||||
|
selectionGroup.writeEntry("DecorationFocus", rgbString(accentColor));
|
||||||
|
selectionGroup.writeEntry("DecorationHover", rgbString(accentColor));
|
||||||
|
selectionGroup.writeEntry("ForegroundActive", rgbString(selectionForeground));
|
||||||
|
selectionGroup.writeEntry("ForegroundNormal", rgbString(selectionForeground));
|
||||||
|
selectionGroup.writeEntry("ForegroundInactive", rgbString(selectionInactive));
|
||||||
|
selectionGroup.writeEntry("ForegroundLink", rgbString(selectionLink));
|
||||||
|
|
||||||
|
auto wmGroup = KConfigGroup{generatedConfig, QStringLiteral("WM")};
|
||||||
|
const QColor currentActiveBackground = wmGroup.readEntry("activeBackground", accentColor);
|
||||||
|
const QColor tintedActiveBackground = blendColors(currentActiveBackground, accentColor, dark ? 0.42 : 0.26);
|
||||||
|
const QColor currentInactiveBackground = wmGroup.readEntry("inactiveBackground", tintedActiveBackground);
|
||||||
|
const QColor tintedInactiveBackground = blendColors(currentInactiveBackground, accentColor, dark ? 0.24 : 0.14);
|
||||||
|
const QColor activeForeground = ensureContrast(contrastingTextColor(tintedActiveBackground), tintedActiveBackground, MIN_NORMAL_CONTRAST);
|
||||||
|
const QColor inactiveForeground = ensureContrast(blendColors(contrastingTextColor(tintedInactiveBackground), tintedInactiveBackground, dark ? 0.36 : 0.52),
|
||||||
|
tintedInactiveBackground,
|
||||||
|
MIN_INACTIVE_CONTRAST);
|
||||||
|
wmGroup.writeEntry("activeBackground", rgbString(tintedActiveBackground));
|
||||||
|
wmGroup.writeEntry("activeBlend", rgbString(activeForeground));
|
||||||
|
wmGroup.writeEntry("activeForeground", rgbString(activeForeground));
|
||||||
|
wmGroup.writeEntry("inactiveBackground", rgbString(tintedInactiveBackground));
|
||||||
|
wmGroup.writeEntry("inactiveBlend", rgbString(inactiveForeground));
|
||||||
|
wmGroup.writeEntry("inactiveForeground", rgbString(inactiveForeground));
|
||||||
|
|
||||||
|
generatedConfig->sync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool MobileShellSettings::autoHidePanelsEnabled() const
|
bool MobileShellSettings::autoHidePanelsEnabled() const
|
||||||
{
|
{
|
||||||
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@
|
||||||
#include <KConfigGroup>
|
#include <KConfigGroup>
|
||||||
#include <KConfigWatcher>
|
#include <KConfigWatcher>
|
||||||
#include <KSharedConfig>
|
#include <KSharedConfig>
|
||||||
|
#include <QColor>
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QTimer>
|
||||||
#include <qqmlregistration.h>
|
#include <qqmlregistration.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,6 +52,14 @@ class MobileShellSettings : public QObject
|
||||||
// convergence mode
|
// convergence mode
|
||||||
Q_PROPERTY(bool convergenceModeEnabled READ convergenceModeEnabled WRITE setConvergenceModeEnabled NOTIFY convergenceModeEnabledChanged)
|
Q_PROPERTY(bool convergenceModeEnabled READ convergenceModeEnabled WRITE setConvergenceModeEnabled NOTIFY convergenceModeEnabledChanged)
|
||||||
|
|
||||||
|
// theme
|
||||||
|
Q_PROPERTY(QString colorScheme READ colorScheme NOTIFY colorSchemeChanged)
|
||||||
|
Q_PROPERTY(bool darkThemeEnabled READ darkThemeEnabled WRITE setDarkThemeEnabled NOTIFY darkThemeEnabledChanged)
|
||||||
|
Q_PROPERTY(bool wallpaperThemeEnabled READ wallpaperThemeEnabled WRITE setWallpaperThemeEnabled NOTIFY wallpaperThemeEnabledChanged)
|
||||||
|
Q_PROPERTY(QColor wallpaperThemeColor READ wallpaperThemeColor NOTIFY wallpaperThemeColorChanged)
|
||||||
|
Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged)
|
||||||
|
Q_PROPERTY(bool wallpaperAccentEnabled READ wallpaperAccentEnabled WRITE setWallpaperAccentEnabled NOTIFY wallpaperAccentEnabledChanged)
|
||||||
|
|
||||||
// Auto Hide Panels
|
// Auto Hide Panels
|
||||||
Q_PROPERTY(bool autoHidePanelsEnabled READ autoHidePanelsEnabled WRITE setAutoHidePanelsEnabled NOTIFY autoHidePanelsEnabledChanged)
|
Q_PROPERTY(bool autoHidePanelsEnabled READ autoHidePanelsEnabled WRITE setAutoHidePanelsEnabled NOTIFY autoHidePanelsEnabledChanged)
|
||||||
|
|
||||||
|
|
@ -274,6 +284,19 @@ public:
|
||||||
*/
|
*/
|
||||||
void setConvergenceModeEnabled(bool enabled);
|
void setConvergenceModeEnabled(bool enabled);
|
||||||
|
|
||||||
|
QString colorScheme() const;
|
||||||
|
bool darkThemeEnabled() const;
|
||||||
|
void setDarkThemeEnabled(bool enabled);
|
||||||
|
bool wallpaperThemeEnabled() const;
|
||||||
|
void setWallpaperThemeEnabled(bool enabled);
|
||||||
|
Q_INVOKABLE void applyWallpaperThemeColor(const QColor &color);
|
||||||
|
QColor wallpaperThemeColor() const;
|
||||||
|
QColor accentColor() const;
|
||||||
|
void setAccentColor(const QColor &color);
|
||||||
|
bool wallpaperAccentEnabled() const;
|
||||||
|
void setWallpaperAccentEnabled(bool enabled);
|
||||||
|
Q_INVOKABLE void resetAccentColor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether Auto Hide Panels is enabled.
|
* Whether Auto Hide Panels is enabled.
|
||||||
*/
|
*/
|
||||||
|
|
@ -368,6 +391,12 @@ Q_SIGNALS:
|
||||||
void actionDrawerTopRightModeChanged();
|
void actionDrawerTopRightModeChanged();
|
||||||
void quickSettingsColumnsChanged();
|
void quickSettingsColumnsChanged();
|
||||||
void convergenceModeEnabledChanged();
|
void convergenceModeEnabledChanged();
|
||||||
|
void colorSchemeChanged();
|
||||||
|
void darkThemeEnabledChanged();
|
||||||
|
void wallpaperThemeEnabledChanged();
|
||||||
|
void wallpaperThemeColorChanged();
|
||||||
|
void accentColorChanged();
|
||||||
|
void wallpaperAccentEnabledChanged();
|
||||||
void autoHidePanelsEnabledChanged();
|
void autoHidePanelsEnabledChanged();
|
||||||
void gamingModeEnabledChanged();
|
void gamingModeEnabledChanged();
|
||||||
void gamingDismissHintEnabledChanged();
|
void gamingDismissHintEnabledChanged();
|
||||||
|
|
@ -383,7 +412,17 @@ Q_SIGNALS:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateNavigationBarsInPlasma();
|
void updateNavigationBarsInPlasma();
|
||||||
|
QColor lastUsedCustomAccentColor() const;
|
||||||
|
void applyColorScheme(const QStringList &arguments);
|
||||||
|
QString effectiveColorSchemeName(bool dark) const;
|
||||||
|
bool isDarkColorScheme(const QString &schemeName) const;
|
||||||
|
bool ensureWallpaperColorScheme(const QColor &accentColor, bool dark) const;
|
||||||
|
|
||||||
KConfigWatcher::Ptr m_configWatcher;
|
KConfigWatcher::Ptr m_configWatcher;
|
||||||
|
KConfigWatcher::Ptr m_kdeGlobalsConfigWatcher;
|
||||||
KSharedConfig::Ptr m_config;
|
KSharedConfig::Ptr m_config;
|
||||||
|
KSharedConfig::Ptr m_kdeGlobalsConfig;
|
||||||
|
QTimer m_wallpaperThemeTimer;
|
||||||
|
bool m_pendingWallpaperThemeDark = false;
|
||||||
|
QColor m_lastWallpaperThemeColor;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@
|
||||||
"Description[zh_TW]": "設定系統殼層介面",
|
"Description[zh_TW]": "設定系統殼層介面",
|
||||||
"EnabledByDefault": true,
|
"EnabledByDefault": true,
|
||||||
"FormFactors": [
|
"FormFactors": [
|
||||||
|
"desktop",
|
||||||
"handset",
|
"handset",
|
||||||
"tablet"
|
"tablet"
|
||||||
],
|
],
|
||||||
|
|
@ -130,7 +131,7 @@
|
||||||
"Name[zh_TW]": "殼層",
|
"Name[zh_TW]": "殼層",
|
||||||
"Website": "https://invent.kde.org/marcoa/shift-shell"
|
"Website": "https://invent.kde.org/marcoa/shift-shell"
|
||||||
},
|
},
|
||||||
"X-KDE-Keywords": "system,shell,panel",
|
"X-KDE-Keywords": "system,shell,panel,appearance,theme,wallpaper,dark,light,accent,color scheme",
|
||||||
"X-KDE-Keywords[ar]": "نظام,صدفة,لوحة",
|
"X-KDE-Keywords[ar]": "نظام,صدفة,لوحة",
|
||||||
"X-KDE-Keywords[az]": "sistem,örtük,panel",
|
"X-KDE-Keywords[az]": "sistem,örtük,panel",
|
||||||
"X-KDE-Keywords[ca@valencia]": "sistema,intèrpret d'ordres,quadro",
|
"X-KDE-Keywords[ca@valencia]": "sistema,intèrpret d'ordres,quadro",
|
||||||
|
|
|
||||||
217
kcms/mobileshell/ui/AppearanceForm.qml
Normal file
217
kcms/mobileshell/ui/AppearanceForm.qml
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
* SPDX-License-Identifier: EUPL-1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Dialogs as Dialogs
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.formcard 1 as FormCard
|
||||||
|
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||||
|
|
||||||
|
FormCard.FormCardPage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: i18n("Appearance")
|
||||||
|
|
||||||
|
readonly property bool usingSchemeAccent: ShellSettings.Settings.accentColor.a === 0
|
||||||
|
readonly property color previewAccentColor: usingSchemeAccent ? Kirigami.Theme.highlightColor : ShellSettings.Settings.accentColor
|
||||||
|
readonly property bool wallpaperThemeActive: ShellSettings.Settings.wallpaperThemeEnabled && ShellSettings.Settings.colorScheme.indexOf("ShiftWallpaper") === 0
|
||||||
|
|
||||||
|
Dialogs.ColorDialog {
|
||||||
|
id: accentColorDialog
|
||||||
|
|
||||||
|
title: i18n("Accent Color")
|
||||||
|
selectedColor: root.previewAccentColor
|
||||||
|
onAccepted: ShellSettings.Settings.accentColor = selectedColor
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
selectedColor = root.previewAccentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormHeader {
|
||||||
|
title: i18n("Theme")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
Layout.topMargin: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
FormCard.FormSwitchDelegate {
|
||||||
|
id: darkThemeSwitch
|
||||||
|
|
||||||
|
text: i18n("Dark Theme")
|
||||||
|
description: ShellSettings.Settings.wallpaperThemeEnabled
|
||||||
|
? i18n("Disabled while Theme from Wallpaper is controlling light and dark mode.")
|
||||||
|
: i18n("Use the dark Shift theme manually.")
|
||||||
|
enabled: !ShellSettings.Settings.wallpaperThemeEnabled
|
||||||
|
checked: ShellSettings.Settings.darkThemeEnabled
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked != ShellSettings.Settings.darkThemeEnabled) {
|
||||||
|
ShellSettings.Settings.darkThemeEnabled = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator { above: darkThemeSwitch; below: wallpaperThemeSwitch }
|
||||||
|
|
||||||
|
FormCard.FormSwitchDelegate {
|
||||||
|
id: wallpaperThemeSwitch
|
||||||
|
|
||||||
|
text: i18n("Theme from Wallpaper")
|
||||||
|
description: i18n("Automatically switch between light and dark Shift themes and generate a matching wallpaper-derived color scheme.")
|
||||||
|
checked: ShellSettings.Settings.wallpaperThemeEnabled
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked != ShellSettings.Settings.wallpaperThemeEnabled) {
|
||||||
|
ShellSettings.Settings.wallpaperThemeEnabled = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator { above: wallpaperThemeSwitch; below: wallpaperAccentSwitch }
|
||||||
|
|
||||||
|
FormCard.FormSwitchDelegate {
|
||||||
|
id: wallpaperAccentSwitch
|
||||||
|
|
||||||
|
text: i18n("Accent from Wallpaper")
|
||||||
|
description: i18n("Use the wallpaper's extracted accent color instead of a manually selected accent.")
|
||||||
|
checked: ShellSettings.Settings.wallpaperAccentEnabled
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked != ShellSettings.Settings.wallpaperAccentEnabled) {
|
||||||
|
ShellSettings.Settings.wallpaperAccentEnabled = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator { above: wallpaperAccentSwitch; below: accentColorDelegate }
|
||||||
|
|
||||||
|
FormCard.AbstractFormDelegate {
|
||||||
|
id: accentColorDelegate
|
||||||
|
|
||||||
|
enabled: !ShellSettings.Settings.wallpaperAccentEnabled
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Accent Color")
|
||||||
|
color: accentColorDelegate.enabled ? Kirigami.Theme.textColor : Kirigami.Theme.disabledTextColor
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
radius: Math.min(width, height) / 2
|
||||||
|
color: root.previewAccentColor
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.28)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
text: i18nc("@action:button", "Choose accent color")
|
||||||
|
icon.name: "color-picker"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
enabled: accentColorDelegate.enabled
|
||||||
|
onClicked: accentColorDialog.open()
|
||||||
|
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
text: i18nc("@action:button", "Use scheme accent")
|
||||||
|
icon.name: "edit-clear"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
enabled: accentColorDelegate.enabled && !root.usingSchemeAccent
|
||||||
|
onClicked: ShellSettings.Settings.resetAccentColor()
|
||||||
|
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormSectionText {
|
||||||
|
visible: ShellSettings.Settings.wallpaperThemeEnabled
|
||||||
|
text: root.wallpaperThemeActive
|
||||||
|
? i18n("Wallpaper theming is active. SHIFT is currently using %1.", ShellSettings.Settings.colorScheme)
|
||||||
|
: i18n("Wallpaper theming is enabled, but SHIFT is still waiting for a usable wallpaper color.")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
visible: ShellSettings.Settings.wallpaperThemeEnabled
|
||||||
|
|
||||||
|
FormCard.AbstractFormDelegate {
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Active Color Scheme")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: ShellSettings.Settings.colorScheme
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.AbstractFormDelegate {
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Wallpaper Source Color")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: ShellSettings.Settings.wallpaperThemeColor.a === 0
|
||||||
|
? i18n("Not available yet")
|
||||||
|
: ShellSettings.Settings.wallpaperThemeColor.toString()
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||||
|
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
color: ShellSettings.Settings.wallpaperThemeColor.a === 0
|
||||||
|
? "transparent"
|
||||||
|
: ShellSettings.Settings.wallpaperThemeColor
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.28)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,19 @@ KCM.SimpleKCM {
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
FormCard.FormHeader {
|
||||||
|
title: i18n("Appearance")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
id: appearanceButton
|
||||||
|
icon.name: "preferences-desktop-theme-global"
|
||||||
|
text: i18n("Appearance")
|
||||||
|
onClicked: kcm.push("AppearanceForm.qml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FormCard.FormHeader {
|
FormCard.FormHeader {
|
||||||
title: i18n("General")
|
title: i18n("General")
|
||||||
}
|
}
|
||||||
|
|
@ -314,7 +327,6 @@ KCM.SimpleKCM {
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
currentIndex = indexOfValue(ShellSettings.Settings.statusBarScaleFactor);
|
currentIndex = indexOfValue(ShellSettings.Settings.statusBarScaleFactor);
|
||||||
dialog.parent = root;
|
|
||||||
}
|
}
|
||||||
onCurrentValueChanged: ShellSettings.Settings.statusBarScaleFactor = currentValue
|
onCurrentValueChanged: ShellSettings.Settings.statusBarScaleFactor = currentValue
|
||||||
}
|
}
|
||||||
|
|
@ -354,7 +366,6 @@ KCM.SimpleKCM {
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
currentIndex = indexOfValue(ShellSettings.Settings.actionDrawerTopLeftMode);
|
currentIndex = indexOfValue(ShellSettings.Settings.actionDrawerTopLeftMode);
|
||||||
dialog.parent = root;
|
|
||||||
}
|
}
|
||||||
onCurrentValueChanged: ShellSettings.Settings.actionDrawerTopLeftMode = currentValue
|
onCurrentValueChanged: ShellSettings.Settings.actionDrawerTopLeftMode = currentValue
|
||||||
}
|
}
|
||||||
|
|
@ -376,7 +387,6 @@ KCM.SimpleKCM {
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
currentIndex = indexOfValue(ShellSettings.Settings.actionDrawerTopRightMode);
|
currentIndex = indexOfValue(ShellSettings.Settings.actionDrawerTopRightMode);
|
||||||
dialog.parent = root
|
|
||||||
}
|
}
|
||||||
onCurrentValueChanged: ShellSettings.Settings.actionDrawerTopRightMode = currentValue
|
onCurrentValueChanged: ShellSettings.Settings.actionDrawerTopRightMode = currentValue
|
||||||
}
|
}
|
||||||
|
|
@ -407,7 +417,6 @@ KCM.SimpleKCM {
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
currentIndex = indexOfValue(ShellSettings.Settings.lockscreenLeftButtonAction);
|
currentIndex = indexOfValue(ShellSettings.Settings.lockscreenLeftButtonAction);
|
||||||
dialog.parent = root;
|
|
||||||
}
|
}
|
||||||
onCurrentValueChanged: ShellSettings.Settings.lockscreenLeftButtonAction = currentValue
|
onCurrentValueChanged: ShellSettings.Settings.lockscreenLeftButtonAction = currentValue
|
||||||
}
|
}
|
||||||
|
|
@ -429,7 +438,6 @@ KCM.SimpleKCM {
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
currentIndex = indexOfValue(ShellSettings.Settings.lockscreenRightButtonAction);
|
currentIndex = indexOfValue(ShellSettings.Settings.lockscreenRightButtonAction);
|
||||||
dialog.parent = root;
|
|
||||||
}
|
}
|
||||||
onCurrentValueChanged: ShellSettings.Settings.lockscreenRightButtonAction = currentValue
|
onCurrentValueChanged: ShellSettings.Settings.lockscreenRightButtonAction = currentValue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||||
|
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
@ -65,7 +66,7 @@ Rectangle {
|
||||||
Loader {
|
Loader {
|
||||||
id: wallpaperColors
|
id: wallpaperColors
|
||||||
|
|
||||||
active: desktop.usedInAccentColor && root.containment && root.containment.wallpaper
|
active: (desktop.usedInAccentColor || ShellSettings.Settings.wallpaperThemeEnabled) && root.containment && root.containment.wallpaper
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: Kirigami.ImageColors {
|
sourceComponent: Kirigami.ImageColors {
|
||||||
|
|
@ -80,26 +81,46 @@ Rectangle {
|
||||||
Kirigami.Theme.backgroundColor: backgroundColor
|
Kirigami.Theme.backgroundColor: backgroundColor
|
||||||
Kirigami.Theme.textColor: textColor
|
Kirigami.Theme.textColor: textColor
|
||||||
|
|
||||||
|
readonly property color wallpaperColor: {
|
||||||
|
if (!Qt.colorEqual(imageColors.colorFromPlugin, "transparent")) {
|
||||||
|
return imageColors.colorFromPlugin;
|
||||||
|
}
|
||||||
|
if (!Qt.colorEqual(imageColors.highlight, "transparent")) {
|
||||||
|
return imageColors.highlight;
|
||||||
|
}
|
||||||
|
if (!Qt.colorEqual(imageColors.dominant, "transparent")) {
|
||||||
|
return imageColors.dominant;
|
||||||
|
}
|
||||||
|
return imageColors.fallbackHighlight;
|
||||||
|
}
|
||||||
|
|
||||||
onBackgroundColorChanged: Qt.callLater(update)
|
onBackgroundColorChanged: Qt.callLater(update)
|
||||||
onTextColorChanged: Qt.callLater(update)
|
onTextColorChanged: Qt.callLater(update)
|
||||||
|
onWallpaperColorChanged: Qt.callLater(applyWallpaperTheme)
|
||||||
|
|
||||||
|
function applyWallpaperTheme() {
|
||||||
|
ShellSettings.Settings.applyWallpaperThemeColor(imageColors.wallpaperColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
property Connections settingsConnection: Connections {
|
||||||
|
target: ShellSettings.Settings
|
||||||
|
|
||||||
|
function onWallpaperThemeEnabledChanged() {
|
||||||
|
imageColors.applyWallpaperTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property Binding colorBinding: Binding {
|
property Binding colorBinding: Binding {
|
||||||
target: desktop
|
target: desktop
|
||||||
property: "accentColor"
|
property: "accentColor"
|
||||||
value: {
|
value: imageColors.wallpaperColor
|
||||||
if (!Qt.colorEqual(imageColors.colorFromPlugin, "transparent")) {
|
|
||||||
return imageColors.colorFromPlugin;
|
|
||||||
}
|
|
||||||
if (imageColors.palette.length === 0) {
|
|
||||||
return "transparent";
|
|
||||||
}
|
|
||||||
return imageColors.dominant;
|
|
||||||
}
|
|
||||||
when: desktop.usedInAccentColor // Without this, accentColor may still be updated after usedInAccentColor becomes false
|
when: desktop.usedInAccentColor // Without this, accentColor may still be updated after usedInAccentColor becomes false
|
||||||
}
|
}
|
||||||
|
|
||||||
property Connections repaintConnection: Connections {
|
property Connections repaintConnection: Connections {
|
||||||
target: root.containment.wallpaper
|
target: root.containment.wallpaper
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
|
||||||
function onRepaintNeeded(color) {
|
function onRepaintNeeded(color) {
|
||||||
imageColors.colorFromPlugin = color;
|
imageColors.colorFromPlugin = color;
|
||||||
|
|
||||||
|
|
@ -110,7 +131,10 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoaded: item.update()
|
onLoaded: {
|
||||||
|
item.update();
|
||||||
|
item.applyWallpaperTheme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ if(BASH_EXECUTABLE)
|
||||||
COMMAND ${BASH_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check-shift-theme-identity.sh
|
COMMAND ${BASH_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check-shift-theme-identity.sh
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME shift-dynamic-theming
|
||||||
|
COMMAND ${BASH_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check-shift-dynamic-theming.sh
|
||||||
|
)
|
||||||
|
|
||||||
add_test(
|
add_test(
|
||||||
NAME shift-preview-qml-stubs
|
NAME shift-preview-qml-stubs
|
||||||
COMMAND ${BASH_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check-preview-qml-stubs.sh
|
COMMAND ${BASH_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check-preview-qml-stubs.sh
|
||||||
|
|
|
||||||
154
tests/check-shift-dynamic-theming.sh
Normal file
154
tests/check-shift-dynamic-theming.sh
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
# SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$repo_dir"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '%s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_line() {
|
||||||
|
local file="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
local message="$3"
|
||||||
|
|
||||||
|
grep -Eq -- "$pattern" "$file" || fail "$message"
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_header=components/shellsettingsplugin/mobileshellsettings.h
|
||||||
|
settings_cpp=components/shellsettingsplugin/mobileshellsettings.cpp
|
||||||
|
settings_cmake=components/shellsettingsplugin/CMakeLists.txt
|
||||||
|
kcm_main=kcms/mobileshell/ui/main.qml
|
||||||
|
kcm_appearance=kcms/mobileshell/ui/AppearanceForm.qml
|
||||||
|
desktop_view=shell/contents/views/Desktop.qml
|
||||||
|
kcm_metadata=kcms/mobileshell/kcm_mobileshell.json
|
||||||
|
|
||||||
|
require_line "$settings_header" 'Q_PROPERTY\(QString colorScheme READ colorScheme NOTIFY colorSchemeChanged\)' \
|
||||||
|
"mobile shell settings must expose the active KDE color scheme"
|
||||||
|
require_line "$settings_header" 'Q_PROPERTY\(bool darkThemeEnabled READ darkThemeEnabled WRITE setDarkThemeEnabled NOTIFY darkThemeEnabledChanged\)' \
|
||||||
|
"mobile shell settings must expose Shift dark/light switching"
|
||||||
|
require_line "$settings_header" 'Q_PROPERTY\(bool wallpaperThemeEnabled READ wallpaperThemeEnabled WRITE setWallpaperThemeEnabled NOTIFY wallpaperThemeEnabledChanged\)' \
|
||||||
|
"mobile shell settings must expose wallpaper-driven theme mode"
|
||||||
|
require_line "$settings_header" 'Q_PROPERTY\(QColor wallpaperThemeColor READ wallpaperThemeColor NOTIFY wallpaperThemeColorChanged\)' \
|
||||||
|
"mobile shell settings must expose the extracted wallpaper theme color for UI inspection"
|
||||||
|
require_line "$settings_header" 'Q_PROPERTY\(QColor accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged\)' \
|
||||||
|
"mobile shell settings must expose manual accent color selection"
|
||||||
|
require_line "$settings_header" 'Q_PROPERTY\(bool wallpaperAccentEnabled READ wallpaperAccentEnabled WRITE setWallpaperAccentEnabled NOTIFY wallpaperAccentEnabledChanged\)' \
|
||||||
|
"mobile shell settings must expose wallpaper-derived accent colors"
|
||||||
|
|
||||||
|
require_line "$settings_cpp" 'KSharedConfig::openConfig\(\)' \
|
||||||
|
"dynamic theming must use kdeglobals, not a parallel Shift-only config file"
|
||||||
|
require_line "$settings_cpp" 'readEntry\("ColorScheme", SHIFT_DARK_COLOR_SCHEME\)' \
|
||||||
|
"dark/light mode must be backed by kdeglobals ColorScheme"
|
||||||
|
require_line "$settings_cpp" 'readEntry\("wallpaperThemeEnabled", false\)' \
|
||||||
|
"wallpaper theme mode must be an explicit Shift setting"
|
||||||
|
require_line "$settings_cpp" 'writeEntry\("wallpaperThemeEnabled", enabled, KConfigGroup::Notify\)' \
|
||||||
|
"wallpaper theme mode must persist changes with config notifications"
|
||||||
|
require_line "$settings_cpp" 'WALLPAPER_THEME_MANUAL_DARK_KEY' \
|
||||||
|
"wallpaper theme mode must remember the previous manual dark/light choice"
|
||||||
|
require_line "$settings_cpp" 'readEntry\(WALLPAPER_THEME_MANUAL_DARK_KEY, darkThemeEnabled\(\)\)' \
|
||||||
|
"disabling wallpaper theme mode must restore the previous manual dark/light choice"
|
||||||
|
require_line "$settings_cpp" 'm_wallpaperThemeTimer\.stop\(\)' \
|
||||||
|
"disabling wallpaper theme mode must cancel pending automatic theme changes"
|
||||||
|
require_line "$settings_cpp" 'applyWallpaperThemeColor' \
|
||||||
|
"wallpaper theme mode must expose a shell hook for extracted wallpaper colors"
|
||||||
|
require_line "$settings_cpp" 'Q_EMIT wallpaperThemeColorChanged\(\)' \
|
||||||
|
"wallpaper theme mode must notify the UI when the extracted wallpaper theme color changes"
|
||||||
|
require_line "$settings_cpp" 'm_wallpaperThemeTimer\.setInterval\(450\)' \
|
||||||
|
"wallpaper theme mode must debounce wallpaper color changes"
|
||||||
|
require_line "$settings_cpp" 'm_wallpaperThemeTimer\.start\(\)' \
|
||||||
|
"wallpaper theme mode must apply extracted wallpaper colors through the debounce timer"
|
||||||
|
require_line "$settings_cpp" '0\.2126 \* color\.redF\(\) \+ 0\.7152 \* color\.greenF\(\) \+ 0\.0722 \* color\.blueF\(\)' \
|
||||||
|
"wallpaper theme mode must use relative luminance to choose light or dark Shift schemes"
|
||||||
|
require_line "$settings_cpp" 'writeEntry\("accentColorFromWallpaper", enabled, KConfigGroup::Notify\)' \
|
||||||
|
"wallpaper accent mode must persist KDE's accentColorFromWallpaper key"
|
||||||
|
require_line "$settings_cpp" 'm_lastWallpaperThemeColor\.name\(QColor::HexRgb\)' \
|
||||||
|
"wallpaper accent mode must apply the extracted wallpaper color directly as accent override"
|
||||||
|
require_line "$settings_cpp" 'writeEntry\("LastUsedCustomAccentColor", opaqueColor, KConfigGroup::Notify\)' \
|
||||||
|
"manual accent mode must preserve KDE's LastUsedCustomAccentColor key"
|
||||||
|
require_line "$settings_cpp" 'deleteEntry\("AccentColor", KConfigGroup::Notify\)' \
|
||||||
|
"resetting the accent must clear KDE's AccentColor key"
|
||||||
|
require_line "$settings_cpp" 'plasma-apply-colorscheme' \
|
||||||
|
"color-scheme changes must go through KDE's color applicator"
|
||||||
|
require_line "$settings_cpp" '--accent-color' \
|
||||||
|
"manual accent selection must use KDE's accent-color applicator path"
|
||||||
|
require_line "$settings_cpp" 'plasma-apply-desktoptheme' \
|
||||||
|
"dark/light switching must update the Shift Plasma desktop theme"
|
||||||
|
require_line "$settings_cpp" 'ShiftDark' \
|
||||||
|
"dynamic theming must keep ShiftDark as the dark scheme"
|
||||||
|
require_line "$settings_cpp" 'ShiftLight' \
|
||||||
|
"dynamic theming must keep ShiftLight as the light scheme"
|
||||||
|
require_line "$settings_cpp" 'ShiftWallpaperDark' \
|
||||||
|
"wallpaper theme mode must provide a generated dark Shift wallpaper scheme"
|
||||||
|
require_line "$settings_cpp" 'ShiftWallpaperLight' \
|
||||||
|
"wallpaper theme mode must provide a generated light Shift wallpaper scheme"
|
||||||
|
require_line "$settings_cpp" 'QStandardPaths::locate\(QStandardPaths::GenericDataLocation,' \
|
||||||
|
"wallpaper theme mode must derive generated schemes from the installed Shift base schemes"
|
||||||
|
require_line "$settings_cpp" 'QStandardPaths::writableLocation\(QStandardPaths::GenericDataLocation\)' \
|
||||||
|
"wallpaper theme mode must write generated schemes into the session data location"
|
||||||
|
require_line "$settings_cpp" 'QFile::copy\(sourcePath, outputPath\)' \
|
||||||
|
"wallpaper theme mode must generate a concrete .colors file before applying it"
|
||||||
|
require_line "$settings_cpp" 'tintBackgroundEntries\(' \
|
||||||
|
"wallpaper theme mode must generate tinted background roles in the derived color scheme"
|
||||||
|
require_line "$settings_cpp" 'tuneForegroundEntries\(' \
|
||||||
|
"wallpaper theme mode must generate contrast-aware foreground hierarchy entries"
|
||||||
|
require_line "$settings_cpp" 'contrastRatio\(' \
|
||||||
|
"wallpaper theme mode must use contrast-aware text color selection"
|
||||||
|
require_line "$settings_cpp" 'ensureContrast\(' \
|
||||||
|
"wallpaper theme mode must clamp generated foreground roles to a minimum contrast threshold"
|
||||||
|
require_line "$settings_cpp" 'MIN_NORMAL_CONTRAST' \
|
||||||
|
"wallpaper theme mode must define explicit minimum contrast targets for readability"
|
||||||
|
require_line "$settings_cpp" 'writeEntry\("BackgroundNormal"' \
|
||||||
|
"wallpaper theme mode must update BackgroundNormal in generated scheme groups"
|
||||||
|
require_line "$settings_cpp" 'writeEntry\("inactiveBackground"' \
|
||||||
|
"wallpaper theme mode must generate an inactive window background role"
|
||||||
|
require_line "$settings_cpp" 'shift-dark' \
|
||||||
|
"dynamic theming must keep shift-dark as the dark Plasma theme"
|
||||||
|
require_line "$settings_cpp" 'shift-light' \
|
||||||
|
"dynamic theming must keep shift-light as the light Plasma theme"
|
||||||
|
require_line "$settings_cmake" 'KF6::ConfigCore' \
|
||||||
|
"shell settings plugin must link explicitly to KConfigCore for kdeglobals access"
|
||||||
|
require_line "$kcm_main" 'kcm\.push\("AppearanceForm\.qml"\)' \
|
||||||
|
"mobile shell settings must expose the dynamic theming controls"
|
||||||
|
if grep -Eq -- 'dialog\.parent[[:space:]]*=' "$kcm_main"; then
|
||||||
|
fail "mobile shell KCM must not assign dialog.parent on FormComboBoxDelegate (invalid in current runtime)"
|
||||||
|
fi
|
||||||
|
require_line "$kcm_metadata" '"FormFactors"[[:space:]]*:[[:space:]]*\[' \
|
||||||
|
"shell settings module metadata must declare form factors"
|
||||||
|
require_line "$kcm_metadata" '"desktop"' \
|
||||||
|
"shell settings module must be visible in desktop System Settings form factor"
|
||||||
|
require_line "$kcm_metadata" '"X-KDE-Keywords"[[:space:]]*:[[:space:]]*"[^"]*appearance[^"]*theme[^"]*wallpaper[^"]*accent' \
|
||||||
|
"shell settings module search keywords must include appearance/theme/wallpaper/accent terms"
|
||||||
|
require_line "$kcm_appearance" 'ShellSettings\.Settings\.darkThemeEnabled' \
|
||||||
|
"appearance settings must control Shift dark/light mode"
|
||||||
|
require_line "$kcm_appearance" 'ShellSettings\.Settings\.wallpaperThemeEnabled' \
|
||||||
|
"appearance settings must control wallpaper-driven theme mode"
|
||||||
|
require_line "$kcm_appearance" 'ShellSettings\.Settings\.colorScheme' \
|
||||||
|
"appearance settings must show the active generated wallpaper color scheme"
|
||||||
|
require_line "$kcm_appearance" 'ShellSettings\.Settings\.wallpaperThemeColor' \
|
||||||
|
"appearance settings must show the extracted wallpaper theme color"
|
||||||
|
require_line "$kcm_appearance" 'ShellSettings\.Settings\.wallpaperAccentEnabled' \
|
||||||
|
"appearance settings must control wallpaper-derived accent colors"
|
||||||
|
require_line "$kcm_appearance" 'Dialogs\.ColorDialog' \
|
||||||
|
"appearance settings must provide manual accent color selection"
|
||||||
|
require_line "$kcm_appearance" 'ShellSettings\.Settings\.accentColor = selectedColor' \
|
||||||
|
"appearance settings must apply the selected manual accent color"
|
||||||
|
require_line "$kcm_appearance" 'ShellSettings\.Settings\.resetAccentColor\(\)' \
|
||||||
|
"appearance settings must allow returning to the scheme accent"
|
||||||
|
require_line "$desktop_view" 'usedInAccentColor' \
|
||||||
|
"wallpaper accent mode depends on the Plasma desktop accent extraction hook"
|
||||||
|
require_line "$desktop_view" 'ShellSettings\.Settings\.wallpaperThemeEnabled' \
|
||||||
|
"wallpaper theme mode must reuse the Plasma desktop wallpaper color extraction hook"
|
||||||
|
require_line "$desktop_view" 'ShellSettings\.Settings\.applyWallpaperThemeColor\(imageColors\.wallpaperColor\)' \
|
||||||
|
"desktop wallpaper color extraction must feed Shift's wallpaper theme mode"
|
||||||
|
require_line "$desktop_view" 'onWallpaperThemeEnabledChanged' \
|
||||||
|
"desktop wallpaper color extraction must react when wallpaper theme mode is toggled"
|
||||||
|
require_line tests/CMakeLists.txt 'NAME shift-dynamic-theming' \
|
||||||
|
"dynamic theming regression test must be registered with CTest"
|
||||||
|
|
||||||
|
printf '%s\n' 'shift-dynamic-theming-ok'
|
||||||
Loading…
Reference in a new issue