
Change-Id: Ia38dd2e506e2fbbaa778c717de92ef7c1e3d108f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183060 Tested-by: Julien Nabet <serval2412@yahoo.fr> Reviewed-by: Julien Nabet <serval2412@yahoo.fr>
10 KiB
LibreOffice Themes
How to read this
It is suggested that you have the code open side-by-side and first read some part here and then the code that it talks about.
VLC Plugins and the UpdateSettings functions
LibreOffice VCL (a bunch of interfaces and a base implementation) has four main platform specific implementations (gtk, qt, win,
osx). Each VCL plugin has an UpdateSettings(AllSettings& rSettings)
function somewhere. This function reads styling information
like colors from the platform specific widget toolkit and loads the information into the StyleSettings
instance passed in the
argument (indirectly through AllSettings
).
The StyleSettings Instance
The StyleSettings
(SS) class manages the colors. Various parts of the codebase call getters and setters on SS to get the default
color, or to override it. There exists a static StyleSettings
instance in the application, and the instances which are created
here and there are merged with that static SS instance. we can access the static instance from anywhere in the application by the
following function call.
const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
How UserPreferences are Saved (registry)
There exist two kind of files for state/configuration management, .xcu and .xcs files. These are XML files, the .xcs files are XML schema files which define the "schema" for the configuration like a colorscheme node will have the following entries colors , and the colors will have a light and a dark variant... The .xcu files are the configuration data files which define the default values for the configuration nodes defined in the schema files.
We use the term registry
to refer to the application's configuration and we save the modifications to the default values (set in
the .xcu files) in a file named registrymodifications.xcu
which lives in $XDG_CONFIG_HOME/libreoffice/(somewhere here)
.
ColorConfig, ColorConfig_Impl, and EditableColorConfig
From the themes/colors perspective, think of ColorConfig_Impl
as a code representation of the colors in the registry, and
think of ColorConfig
as a *read-only wrapper over ColorConfig_Impl
. There exists another class in this bunch named
EditableColorConfig
, and as the name suggests it is a read-write wrapper over ColorConfig_Impl
.
The "Appearance" tab on the "Options" dialog interacts with the registry thanks to an EditableColorConfig
instance.
Getting System Colors into the static StyleSettings object
So if you setup some printfdebugging statements in the UpdateSettings
functions and in the ColorConfig
constructor, you will
find that when the application starts, first the UpdateSettings
functions are executed, and then the first every ColorConfig
instance is created.
Also if you add and set a non-static flag to the StyleSettings
and print it out from the UpdateSettings
functions and the
ColorConfig
constructor, you will find that the flag doesn't make it to the static instance (accessed from in ColorConfig
)
immediately. We use such a flag mbSystemColorsLoaded
to see if the static StyleSettings
object has the system colors or not.
The LibreOfficeTheme registry flag
<prop oor:name="LibreOfficeTheme" oor:type="xs:short" oor:nillable="false">
<info>
<desc>Specifies LibreOfficeTheme state.</desc>
<label>LibreOffice Theme</label>
...
To enable or disable theming, we have a LibreOfficeTheme
enum in the registry which is represented by enum class ThemeState
.
in the code. The default value is ENABLED
and the only way for the user to disable it is by changing it to 0
in the expert
configuration.
It's still a dispute whether to enable or disable a theming by default, so please refer to the .xcs file and don't take the explanation for implementation.
enum class ThemeState
{
DISABLED = 0,
ENABLED = 1,
RESET = 2,
};
High Level Code overview of Themes Implementation
We load the colors from the widget toolkit into the StyleSettings
object and set a flag mbSystemColorsLoaded
to true
. Then
in the ColorConfig
constructor ColorConfig::SetupTheme()
. We will be back to SetupTheme
after we understand how theme colors
are stored.
Talking about Singleton ThemeColors class and the path Colors travel
themecolors.hxx
defines a singleton class named ThemeColors
. This class has two static members. The second one is that of the
class itself, and the first one is a boolean flag which is used to check if theme colors are cached or not.
class VCL_DLLPUBLIC ThemeColors
{
ThemeColors() {}
static bool m_bIsThemeCached;
static ThemeColors m_aThemeColors;
...
All the colors are essentially registry values grouped in colorschemes and accessed using various ColorConfig
s
(ColorConfig_Impl, EditableColorConfig, ColorConfig), we just talked about it above. So the theme colors (colors for the UI) are
loaded from the registry into this singleton ThemeColors
instance, and we set the m_bIsThemeCached
flag to true
. Then the
various VCL plugins check the flag and if the theme colors are cached, these colors are sent to the widget toolkit in different
ways depending on the toolkit, like css in case of gtk, QPalette in case of Qt.
Then when the UpdateSettings function is called again, the colors read from the widget toolkit are these custom colors. Then the
StyleSettings
object is loaded with these colors and they make it to every corner of the application which gets its colors from
StyleSettings
object.
Back to ColorConfig::SetupTheme()
So in ColorConfig::SetupTheme()
, we first check if LibreOfficeTheme
enum is set to DISABLED
, and if so then we mark
ThemeColors
as not cached, so no custom colors are set at the toolkit level and return from the SetupTheme()
function. Then we
check if LibreOfficeTheme
is set to RESET
which happens when the user presses the Reset All
button (after which he restarts
the system). If true then we check for mbSystemColorsLoaded
to see if the default colors from the widget toolkit have made it to
the static StyleSettings instance or not, and if that's true as well, we set LibreOfficeTheme
enum to ENABLED
Then in the last part of SetupTheme()
, which we reach only if LibreOfficeTheme
is set to ENABLED
, we check if the theme
colors are cached or not (if the UI colors are loaded from the registry into the static ThemeColors
instance or not). If cached,
we don't touch those over and over. If theme colors are not cached, then we Load
the CurrentScheme
which means that we load
the colors for the current scheme from the registry and store them in ColorConfig_Impl
instance.
...
if (!ThemeColors::IsThemeCached())
{
// registry to ColorConfig::m_pImpl
m_pImpl->Load(GetCurrentSchemeName());
m_pImpl->CommitCurrentSchemeName();
// ColorConfig::m_pImpl to static ThemeColors::m_aThemeColors
LoadThemeColorsFromRegistry();
}
...
Then the LoadThemeColorsFromRegistry
function is called which loads colors from the registry into the ThemeColors
instance by
calling ColorConfig::GetColorValue
for each entry. In ColorConfig::GetColorValue
call, if the color value in the registry is
COL_AUTO
then we call ColorConfig::GetDefaultColor
which returns hardcoded default colors for the document, and StyleSettings
colors for the UI (see lcl_GetDefaultUIColor
).
If the color value is not COL_AUTO
, then the value from the registry is returned, this way we save the user's preferences and
get the default colors from hardcoded colors array and StyleSettings.
void ColorConfig::LoadThemeColorsFromRegistry()
{
ThemeColors& rThemeColors = ThemeColors::GetThemeColors();
rThemeColors.SetWindowColor(GetColorValue(svtools::WINDOWCOLOR).nColor);
rThemeColors.SetWindowTextColor(GetColorValue(svtools::WINDOWTEXTCOLOR).nColor);
...
What happens when "Reset All" is pressed
When the Reset All
button is pressed, all the registry color values are set to COL_AUTO
and LibreOfficeTheme
is set to
RESET
. Then after restart, the IsThemeReset
conditional in ColorConfig::SetupTheme()
checks if StyleSettings has the system
colors or not, and once it has, LibreOfficeTheme
is set to ENABLED
which then goes through the last conditional and
LoadThemeColorsFromRegistry
is called (just explained above). Since all the registry entries were set to COL_AUTO
, we end up
getting default values for all the colors (hardcoded ones for document and StyleSettings colors for UI).
ColorConfigValue now has nLightColor and nDarkColor entries
struct ColorConfigValue
{
bool bIsVisible; // validity depends on the element type
::Color nColor; // used as a cache for the current color
Color nLightColor;
Color nDarkColor;
...
Each color entry has two color values, one for light and one for dark. Based on the ApplicationAppearance
, either light or dark
color value is used. Since the nColor "variable name" is used in 250+ places in the codebase, I found it unreasonable to replace
all the 250+ references with a conditional like this.
Color nColor;
if (IsDarkMode())
nColor = aColorConfig.GetColorValue( svtools::APPBACKGROUND ).nDarkColor;
else
nColor = aColorConfig.GetColorValue( svtools::APPBACKGROUND ).nLightColor;
This would have been very inefficient because IsDarkMode()
is a virtual function (being called 250+ times, maybe every frame??).
So instead of using a conditional, I use nColor
as the cache. When the colors are loaded from the registry (see
ColorConfig_Impl::Load
), I cache the value into nColor
based on ApplicationAppearance
value (whether light or dark mode).
And since we ask the user to restart the application after changing appearance or color values, caching works without any issues.
Automatic scheme as the fallback
In case the scheme that you are trying to load doesn't exist because "the extension was removed?", or "someone edited the registry".. the "Automatic" scheme is used as the fallback.
void ColorConfig_Impl::Load(const OUString& rScheme)
...
if (!ThemeColors::IsAutomaticTheme(sScheme))
{
uno::Sequence<OUString> aSchemes = GetSchemeNames();
bool bFound = std::any_of(aSchemes.begin(), aSchemes.end(),
[&sScheme](const OUString& rSchemeName) { return sScheme == rSchemeName; });
if (!bFound)
sScheme = AUTOMATIC_COLOR_SCHEME;
}
...