[RFC] KColorScheme replacement
Open, Needs TriagePublic

Description

Intro / Motivations

KColorScheme is in an odd place where it is difficult for end users who want to make colorschemes, but it doesn't have the right amount of granularity for designers. It's also quite annoying to make use of all of its color roles since you have to make instances of KColorScheme or KStatefulBrush [1]. Meanwhile, QPalette is already built into Qt Widgets, Qt Quick Controls and Qt Quick (in Qt 6), so we tend to use that instead (at least for Qt Widgets). This feeds into the difficulty for end users because it's not clear when, where or why some color roles won't have an effect.

One of the reasons why I want a new color scheme system is to give us an opportunity to completely get rid of problematic conventions for how KColorScheme is currently used and things that are common in existing color schemes that cause us problems.

  • Complementary color set is misleading because the convention is to use it as a kind of incomplete persistently dark color palette.
  • Inactive selection background color is different from active selection background color. I understand why this was done and I agree that it's good in the right contexts, but it's applied to absolutely everything, not just list view items and there's no way to turn it off without making it so we have to manually manage the ColorGroup to avoid the Inactive ColorGroup.
  • Unused roles (mostly roles that don't map 1:1 to a QPalette ColorRole) tend not to be customized, but still get written to the color scheme files, so we have a bunch of roles in KColorScheme that we can't actually use for anything because they will look bad with a lot of 3rd party color schemes. The only solution to this is to detect whether or not a color is the default color and then replace it with a color that we think is better for the situation, but that's an ugly hack [2].

Basically, KColorScheme2 is a system for managing multiple QPalettes. You can have a QPalette for each combination of context and style. Changing the context or style of a widget changes the QPalette of that widget. This makes it much more convenient to use than KColorScheme. A potential downside is higher RAM usage since every single application could have multiple QPalettes. Perhaps there are ways to mitigate or even stop that from being an issue?

I have some concerns that this won't really be a major improvement over KColorScheme unless parts of this end up in Qt itself. Otherwise, we can still use the new features in our QStyle and QQC2 style, but they won't be as widely used as I'd like them to be.


  • [1] Kirigami.Theme (which uses KColorScheme through qqc2-desktops-style) is a big exception since it's actually pretty convenient to use in QML, but it still has the other KColorScheme issues.
  • [2] I do this for Button AlternateBackground in qqc2-breeze-style and I can get away with it on Plasma Mobile, but it's an ugly hack and I wouldn't do this for Plasma Desktop.

Legacy Support

We should have a way to migrate old color schemes automatically just so users don't have to start all over from scratch. Maybe not a perfect recreation of the original, but it should be possible to create a reasonably similar color scheme.

Contexts

  • Default
  • Possibly many kinds of contexts (window header area, main sidebar, main view area, etc.)
  • Maybe make it extensible by app devs?

Styles

  • Light
  • Dark
  • Maybe make it extensible by app devs?

Definition/Inheritance Behavior

In order to prevent the creation of color schemes from becoming too complex for end users, they shouldn't need to customize every single Context, Style, ColorGroup and ColorRole to get a usable color scheme.

  • An undefined or incomplete Default Context will get missing parts from the platform theme.
    • Diagram showing where default QPalette colors come from:
    • The default QPalette will be set by our platform theme, which will use the system color scheme
    • When there is no system color scheme with a complete Default Context, use hardcoded colors to fill the missing parts.
    • It would probably be a good idea if there were hardcoded fallback colors for both the Light and Dark styles so that Light and Dark will always have something usable.
  • All Contexts inherit from the Default Context by default.
  • All Styles inherit from the same Style in the Default Context by default.
  • ColorGroups are handled the way QPalette normally handles them by default.
    • I think this means Inactive is the same as Active and Disabled is just 50% opacity by default.
  • How does QPlatformTheme::Palette/QQuickTheme::Scope fit in? TBD
    • 3 levels (Context, Style, ColorGroup) is already quite a lot, so not sure it's a good idea to have a 4th level, but it could be useful and reusing more of Qt's existing APIs makes it easier to integrate 3rd party apps. Maybe organizing differently could bring an improvement?
  • If some or no ColorRoles are defined for a section, get the missing parts via inheritance.
  • Ignore invalid ColorRoles.
  • Use inherited/default QPalette values when values are invalid.
  • Each section can explicitly define a context, style and color group to inherit from.
    • If an invalid Context, Style or ColorGroup is explicitly inherited from, use default inheritance for the invalid type.

Originally, I thought of having Styles at the top level since I thought of Light and Dark as being higher level than Contexts. However, I think it's more likely that app developers will want to choose different Styles based on the Context. The fact that Styles go under Contexts shouldn’t matter to end users unless they want to manually edit color scheme files.

Maybe:

  • Allow new Contexts and Styles to be declared in the color scheme file?

Data Organization

Each section could be structured something like this:

[Context][Style][ColorGroup]
Inherits=Context,Style,ColorGroup
ColorRole=uint8,uint8,uint8,uint8
# alpha is optional

A more realistic example:

[SideBar][Dark][Disabled]
Inherits=MainView,Dark,Disabled
Base=0,0,0
Text=255,255,255,127

API

  • Is it possible to make it work using static functions? It would be very convenient if we could do something like KColorScheme2::setContext(SomeContext, widget) or KColorScheme2::setStyle(Dark, widget), to set the palette for a widget and have the palette propagate to children of the widget. This is sort of similar to Kirigami.Theme.colorSet: Kirigami.Theme.View in QML.
  • Is it possible to make it work for Qt Widgets and QQC2/Qt Quick? Maybe as long as a QObject has a palette property or palette() and setPalette() functions, it can work with the KColorScheme2 functions?
  • End users can select Light or Dark via settings, so have a way to get the current Style and the Style chosen by the user.

Qt

  • If the Context and Style things were in Qt itself, 3rd party developers would be more likely to use them.
  • QQuickTheme from QQC2 apparently has palette Scopes. Using these seems to be a bit better than the status quo. It doesn't address the situation where the same controls need different colors for different situations though.
    • QQuickTheme provides the default palette for each type of control.
    • This function describes how we can set default colors for each of the supported controls. We just need to adjust KHintsSettings::loadPalettes(). We don't even need a new color scheme system to use this to some degree.
static QPlatformTheme::Palette platformPalette(QQuickTheme::Scope scope)
{
    switch (scope) {
    case QQuickTheme::Button: return QPlatformTheme::ButtonPalette;
    case QQuickTheme::CheckBox: return QPlatformTheme::CheckBoxPalette;
    case QQuickTheme::ComboBox: return QPlatformTheme::ComboBoxPalette;
    case QQuickTheme::GroupBox: return QPlatformTheme::GroupBoxPalette;
    case QQuickTheme::ItemView: return QPlatformTheme::ItemViewPalette;
    case QQuickTheme::Label: return QPlatformTheme::LabelPalette;
    case QQuickTheme::ListView: return QPlatformTheme::ItemViewPalette;
    case QQuickTheme::Menu: return QPlatformTheme::MenuPalette;
    case QQuickTheme::MenuBar: return QPlatformTheme::MenuBarPalette;
    case QQuickTheme::RadioButton: return QPlatformTheme::RadioButtonPalette;
    case QQuickTheme::SpinBox: return QPlatformTheme::TextLineEditPalette;
    case QQuickTheme::Switch: return QPlatformTheme::CheckBoxPalette;
    case QQuickTheme::TabBar: return QPlatformTheme::TabBarPalette;
    case QQuickTheme::TextArea: return QPlatformTheme::TextEditPalette;
    case QQuickTheme::TextField: return QPlatformTheme::TextLineEditPalette;
    case QQuickTheme::ToolBar: return QPlatformTheme::ToolButtonPalette;
    case QQuickTheme::ToolTip: return QPlatformTheme::ToolTipPalette;
    case QQuickTheme::Tumbler: return QPlatformTheme::ItemViewPalette;
    default: return QPlatformTheme::SystemPalette;
    }
}

Example Usecases

  • Gwenview
    • Rather than making a custom always dark QPalette for fullscreen mode, just switch to the Dark Style when in fullscreen mode.
  • Window header areas
    • When you give the window header area a different background color from the rest of the window, you also need controls contained in the header area to have colors that fit in well, but KColorScheme color sets don't actually have enough colors for anything more visually complex than a header area with flat toolbuttons.

Related Objects

ndavis created this task.Jun 24 2021, 10:46 AM
ndavis renamed this task from KColorScheme replacement to [RFC] KColorScheme replacement.
ndavis updated the task description. (Show Details)
ndavis updated the task description. (Show Details)Jun 24 2021, 11:02 AM
ndavis updated the task description. (Show Details)Jun 24 2021, 11:12 AM
ndavis updated the task description. (Show Details)Jun 24 2021, 11:20 AM
ndavis updated the task description. (Show Details)Jun 24 2021, 11:55 AM
ndavis updated the task description. (Show Details)Jun 24 2021, 11:59 AM

On a technical side this ties into T11587 (Making KColorScheme Tier 1, e.g. in KGuiAddons)

A few other things that came up recently:
We want some kind of API that allows us to read and watch the system color prefernce so we can e.g. switch between a light and dark scheme depending on system settings. https://invent.kde.org/frameworks/kconfigwidgets/-/merge_requests/50 goes into that direction for Windows. The same is relevant on Android

On Windows the system has more settings than just light/dark, it also allows to set accent colors etc. Lacking a good upstream Qt solution (https://bugreports.qt.io/browse/QTBUG-72028) it might be cool to be able to derive a full KColorScheme from the windows settings

For Android we also want the whole thing to not require QtWidgets

ndavis updated the task description. (Show Details)Jun 24 2021, 12:02 PM
ndavis updated the task description. (Show Details)Jun 24 2021, 12:09 PM
ndavis updated the task description. (Show Details)Jun 24 2021, 12:57 PM
ndavis updated the task description. (Show Details)
ndavis updated the task description. (Show Details)Jun 24 2021, 1:01 PM
ndavis updated the task description. (Show Details)Jun 24 2021, 1:04 PM
ndavis updated the task description. (Show Details)Jun 24 2021, 1:34 PM
ndavis updated the task description. (Show Details)Jun 24 2021, 1:41 PM
ndavis updated the task description. (Show Details)Jun 24 2021, 1:49 PM
ndavis updated the task description. (Show Details)
ndavis updated the task description. (Show Details)Jun 24 2021, 1:52 PM
ndavis updated the task description. (Show Details)Jun 24 2021, 1:58 PM
ndavis added a comment.EditedJun 24 2021, 2:01 PM

I was thinking we might need equivalents to NegativeText, NeutralText and PositiveText in QPalette for this to work out, but then I realized, couldn't we have Negative, Neutral and Positive Contexts instead? Maybe it would be extreme overkill to have a whole QPalette of colors for a negative context, but it would allow us to tint backgrounds or text differently based on the type of GUI element we're giving the negative color to.

Or maybe not contexts, but LightNegative, DarkNegative, etc. Styles since those will be shared across Contexts if they're in the Default Context.

mikeljohnson added a subscriber: mikeljohnson.EditedJun 24 2021, 2:31 PM

am I correct to assume that with this approach we would go from:

Kirigami.Theme.colorSet: Kirigami.Theme.View

to

palette: Kirigami.Theme.View?

if so, that seems pretty good

am I correct to assume that with this approach we would go from:

Kirigami.Theme.colorSet: Kirigami.Theme.View

to

palette: Kirigami.Theme.View?

if so, that seems pretty good

No, the closest thing to an equivalent expression in the new system would be Kirigami.Theme.context: Kirigami.Theme.MainView or something similar, but focusing on how syntax changes misses the most important changes. This is a new color scheme system.

I thought the whole point of this is to use QPallete with above comment being more about that than the syntax

I thought the whole point of this is to use QPallete with above comment being more about that than the syntax

I'm not sure how to explain this since I don't know what part of the task description confused you.

Also, that's not how you would use the palette property in QML anyway. Here's an example of how you might use this color scheme system in QML to get a persistently dark Rectangle using colors for a Sidebar Context.

Rectangle {
    Kirigami.Theme.context: Kirigami.Theme.Sidebar
    Kirigami.Theme.style: Kirigami.Theme.Dark
    color: palette.base // Available in Qt 6
}

Here, the palette has been changed when we set those attached Kirigami.Theme properties. This is basically how Kirigami.Theme already works, but we use Kirigami.Theme.backgroundColor instead of Qt's own APIs. You won't see the biggest difference in QML though, except for the extra options. For instance, doing a persistently dark UI in QML requires the misleadingly named Complementary color set or just hardcoding colors.

The biggest difference should be in Qt Widgets where doing custom stuff is much more of a pain.

ahh, alright thanks for explaining

ndavis updated the task description. (Show Details)Jun 26 2021, 10:07 PM
ndavis updated the task description. (Show Details)Jul 2 2021, 10:57 AM
ndavis updated the task description. (Show Details)Jul 2 2021, 11:58 AM

Is there something actionable here that we can do before KF 6.0?

Is there something actionable here that we can do before KF 6.0?

Not really.