Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System Theme Support #3695

Closed
wdcossey opened this issue Mar 25, 2020 · 15 comments · Fixed by #9913
Closed

System Theme Support #3695

wdcossey opened this issue Mar 25, 2020 · 15 comments · Fixed by #9913

Comments

@wdcossey
Copy link
Contributor

Hey everyone,

I am working on adding system theme support to Avalonia, currently this is only supported for Windows 10 (and 8.1).
Once I have finished the Windows 10 support I'll add further support for legacy Windows products and then move onto other operating systems.

It's still early days but I'll try update you while I make progress (perhaps on my forked branch w/o clogging up the issues here).

public enum AvaloniaTheme
{
    Light,
    Dark
}

ThemeResourceProvider

public interface IThemeResourceProvider : IDisposable
{
    bool TryGetResource(object key, AvaloniaTheme requestedTheme, out object value);
}

OS Registration

// Add OS specific ThemeResourceProvider
// i.e Avalonia.Win32
[assembly: ExportThemingSubsystem(OperatingSystemType.WinNT, 1, "WindowsResourceProvider", typeof(ThemeResourceProvider), nameof(ThemeResourceProvider.Initialize))]

ThemeResource Bindings

public class ThemeResourceExtension : IBinding
{
    // Implementation here
}

Setup Default Theme (Currently Light/Dark)
See RequestedTheme property

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="ControlCatalog.App"
             RequestedTheme="Dark">
  <Application.Styles>
    <!-- Add Styles here-->
  </Application.Styles>
</Application>

Usage (Note resource keys are not finalised)

<SolidColorBrush x:Key="HighlightBrush" Color="{ThemeResource SystemAccent}"></SolidColorBrush>

<Path Stretch="Uniform" Fill="{ThemeResource SystemAccentBrush}" Data="<some data>"></Path>

Override Application Theme

<Button RequestedTheme="Light">Light Theme</Button>

image

There's still a bunch of things to do but thought I'd let you know what I have been up too and welcome some feedback or recommendations that anyone might have.

@jmacato
Copy link
Member

jmacato commented Mar 26, 2020

Would be interesting to see how this would pan out with Linux and macOS :)

@wdcossey
Copy link
Contributor Author

Just a few notes:

The intention of this is to get the environments' colour scheme, map those colours to ResourceKeys for use with styling the controls.

This is not intended to style the controls to look like the target system, that would be a massive undertaking and one I am not prepared to do in the foreseeable future.

The fact the the Avalonia controls look similar to those in Windows 10 (UWP) is most likely coincidental, the fact they are simple (flat) makes theme'n them somewhat trivial.

I will fire up my Ubuntu VM and see what I can find out from the OS about and getting theme/color information, that said Linux offers many different desktop environments.

@kekekeks
Copy link
Member

kekekeks commented Mar 26, 2020

@wdcossey
I'm afraid it's not exactly possible on Linux anymore. 20 years ago there was X resources thingy which most DEs kept in sync with their color theme. Nowadays nobody seems to be using those, everything is being styled in toolkit-specific way.
What makes it worse, toolkits (Qt 3/4/5, GTK 2/3/4) have several different theme engines (sometimes those are distro-specific) with incompatible sets of color settings.

See https://stackoverflow.com/a/16286405/2231814 for more details.

@Sorien
Copy link
Contributor

Sorien commented Mar 26, 2020

what about s/t like this https://stackoverflow.com/a/56415144

@kekekeks
Copy link
Member

kekekeks commented Mar 26, 2020

Those:

  • require loading libgtk and friends
  • are theme-engine-specific
  • are GTK-specific (i. e. useless in pure KDE setup that might not even have GTK installed)

The described approach also doesn't work with complex GTK themes, since GTK themes use full CSS selector syntax with stuff like n-th child: https://developer.gnome.org/gtk3/stable/chap-css-overview.html

@Sorien
Copy link
Contributor

Sorien commented Mar 26, 2020

@kekekeks don't think there is any intention to style avalonia controls exactly like system ones just grab some color to match theme a bit if possible, it's far from ideal but no other way

@kekekeks
Copy link
Member

We might end up with black text on black background because we've failed to understand the theme.

@kekekeks
Copy link
Member

The only viable approach I can think of is to get the theme name from the system and ship pre-configured colors for the most popular ones. If a particular machine uses a theme outside of the pre-configured list, we should attempt to match one based on the current DE and distro name. For power users, we could look for an Avalonia-specific configuration file from somewhere in ~/.config/ and get colors from there.

@wdcossey
Copy link
Contributor Author

@kekekeks I suspected that might have been the case with at least one or more operating systems, hence why this is currently focused on Windows.

The implementation I am working on focuses around ExportThemingSubsystemAttribute, thus making it configurable per OS, you aren't forced to implement anything for Linux based OS's or you could just simply make something completely bespoke.

{ThemeResource <Name>} attempts to load the requested resource from ResourceNodeExtensions.FindResource first (just like DynamicResource) before attempting to load the resource from the IThemeResourceProvider, this is done purposely, allowing developers to override the ThemeResource.

@wdcossey
Copy link
Contributor Author

I will also add Default and Custom options to AvaloniaTheme (most likely to be renamed to ElementTheme [like UWP])

public enum AvaloniaTheme
{
    Light,
    Dark
}

Default for the current theme(s).
Custom with some custom logic to try replicate Windows 10 theme, i.e. overriding the SystemAccent colour generates a theme on the fly.

@Gillibald
Copy link
Contributor

To be honest this should be optional and live in a separate repo. There are plans to support control themes. I don't understand the benefit of a ThemeResource. What is different to a regular DynamicResource? Most of my WPF projects just use DynamicResources for changing the look of the application.

@handicraftsman
Copy link

I don't know if same can be applied to GTK, but in KDE there is a C++ library which allows getting current color scheme - dark/light theme is merely background color value there.

@wdcossey
Copy link
Contributor Author

I am currently working on an alternative solution to the theming in Avalonia.

First I have to update the controls to support more colour/brush properties/options as the current implementation is a bit sparse (i.e ButtonBorderBrushNormal/Focused/Hover/etc)

Whilst I haven't dropped my idea of adding some system theme (colour) support it will be an addition to what I am currently working on.

@robloo
Copy link
Contributor

robloo commented Dec 20, 2020

Would be great to see ThemeResource support for those of us with UWP/WinUI code. XAML compatiblity is one really good reason to support this. I'm not entirely sure how Avalonia supports light/dark and default/fluent theme changes but I bet this would simplify some things there as well. In several ways ThemeResource is a special-case of DynamicResource. But it also:

  • Improves XAML readability especially in controls -- themed resources are clear in the templates. This eliminates some potential mistakes made during theming.
  • Simplifies theme change handling for application developers. The framework handles everything automatically.
  • As a special-case of DynamicResource, some optimizations are possible during theme switching that wouldn't be easy if everything was DynamicResources

@Gillibald

I don't understand the benefit of a ThemeResource. What is different to a regular DynamicResource? Most of my WPF projects just use DynamicResources for changing the look of the application.

A dynamic resource defers value lookup until it's required. It also automatically re-evaluates the value when the resource dictionary is changed behind the scenes. A ThemeResource looks the value up right away -- but automatically changes to a different resource dictionary and looks the value up again when the theme changes. A DynamicResource cannot process these types of system theme change notifications and automatically change the UI. Some code must be swapping out the resource dictionaries to trigger the updates.

  • DynamicResource : Microsoft docs here. "A DynamicResource will create a temporary expression during the initial compilation and thus defer lookup for resources until the requested resource value is actually required in order to construct an object."
  • ThemeResource : Microsoft docs here are best "a ThemeResource reference can dynamically use different dictionaries as the primary lookup location, depending on which theme is currently being used by the system." Also note that a "ThemeResource can re-evaluate at run-time" and does so automatically whenever the theme changes.

@maxkatz6
Copy link
Member

I'm not entirely sure how Avalonia supports light/dark and default/fluent theme changes

DynamicResource can replace ThemeResource, so it does in Avalonia, although with overhead (as you described, ThemeResource is way more lightweight).
In same time we do not support ThemeDictionary that simplifies resources declaration per theme, so we have to create separated resource files per each theme and unload/load them in runtime to switch the theme.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants