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.