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

Ported Fluent Theme to ControlThemes. #8479

Merged
merged 69 commits into from
Jul 24, 2022

Conversation

grokys
Copy link
Member

@grokys grokys commented Jul 11, 2022

What does the pull request do?

This PR ports the fluent theme to use ControlTheme with @Takoooooo's help.

Due to the way that control themes work, some of the themes required a bit of refactoring - in particular we decided not to allow nested /template/ selectors in control themes, so controls that used these had to have their sub-controls refactored out into separate sub-ControlThemes. This is a bit of a pain but should hopefully improve the stability of themes in the long-term as control themes are no longer relying on implementation details of other control themes.

The only place the lack of nested /template/ selectors turned out to really be a problem was in SplitButton as that control essentially adds additional states to Button (checked, flyout open). For the moment I've used a hack with setting the Tag property but we really need a proper solution for this - probably something like ClassSetter which would allow setting classes on controls from a style, or maybe something like the CSS :part selector (or both).

Depends on #7679
Depends on #8263

IMPORTANT

Currently targeting #7679 - change the PR target back to master before merging.

@grokys grokys changed the base branch from master to refactor/controltemplate-binding-priority July 11, 2022 08:03
Name="grid"
Margin="{TemplateBinding Padding}"
RowDefinitions="Auto, *">
<ContentPresenter
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While refactoring, I noticed that this ContentPresenter doesn't seem to be used. Was it part of a feature that was cut? Can it be removed from the template?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's for Header property that UWP has but we don't. I.e., TextBox.Header, Slider.Header.
It works similarly how our Label works, just built in the template.

<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>

<Style Selector="^[Tag=checked]">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the "tag hack" I mentioned in the description. I'm not happy about it, but it seems to work for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is very ugly... I hope the missing functionality is added so this doesn't become a permanent work-around.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for case, this functionality wasn't removed. You still can use nested /template/ selectors outside of ControlTheme.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, as my luck goes, the places where it would be used (correctly) on my end are in ControlThemes. And I added a larger comment below but if Style still supports this it really seems like ControlTheme should as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still actually host <Style>s in the root element of your control theme template in order to use nested template selectors, however I'd try to avoid it if possible.

</Style>
<Style Selector="^:indeterminate">
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminate}" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While testing I noticed that the indeterminate state for ToggleButton looks the same as the unchecked state. Is this intentional?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an issue in the toggle switch as well. I had a discussion about this in MahApps back then, The solution was to disallow null for IsChecked-property.

See also:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traditionally (in WPF/WinUI) CheckBox derives from ToggleButton and does make use of the three states. This is very common if a CheckBox is used at the top of a column for example representing each row selection.

Since other controls inherit from ToggleButton, and require three states, I don't think this can or should be removed. It is also just fine for ToggleButton to only consider two states in its style as it can only be toggled on/off by the user.

While testing I noticed that the indeterminate state for ToggleButton looks the same as the unchecked state. Is this intentional?

Yes, this is intentional and matches WinUI. It is only really useful in derived controls like CheckBox.

Source code from UWP Generic.xaml
            <!-- Resources for Windows.UI.Xaml.Controls.Primitives.ToggleButton -->
            <StaticResource x:Key="ToggleButtonBackground" ResourceKey="SystemControlBackgroundBaseLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="SystemControlHighlightAccentBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundCheckedDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundIndeterminate" ResourceKey="SystemControlBackgroundBaseLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundIndeterminatePointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundIndeterminatePressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonBackgroundIndeterminateDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
            <StaticResource x:Key="ToggleButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
            <StaticResource x:Key="ToggleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
            <StaticResource x:Key="ToggleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
            <StaticResource x:Key="ToggleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonForegroundChecked" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
            <StaticResource x:Key="ToggleButtonForegroundCheckedPointerOver" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
            <StaticResource x:Key="ToggleButtonForegroundCheckedPressed" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
            <StaticResource x:Key="ToggleButtonForegroundCheckedDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonForegroundIndeterminate" ResourceKey="SystemControlForegroundBaseHighBrush" />
            <StaticResource x:Key="ToggleButtonForegroundIndeterminatePointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
            <StaticResource x:Key="ToggleButtonForegroundIndeterminatePressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
            <StaticResource x:Key="ToggleButtonForegroundIndeterminateDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushChecked" ResourceKey="SystemControlHighlightAltTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushCheckedPointerOver" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushCheckedPressed" ResourceKey="SystemControlTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushCheckedDisabled" ResourceKey="SystemControlDisabledTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushIndeterminate" ResourceKey="SystemControlForegroundTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushIndeterminatePointerOver" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushIndeterminatePressed" ResourceKey="SystemControlHighlightTransparentBrush" />
            <StaticResource x:Key="ToggleButtonBorderBrushIndeterminateDisabled" ResourceKey="SystemControlDisabledTransparentBrush" />

</ControlTheme>
</Style.Resources>

<Setter Property="Theme" Value="{StaticResource FluentUserControl}" />
Copy link
Member Author

@grokys grokys Jul 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the moment, the UserControl theme is being set by a style. We may want to override the effective theme for UserControl to always return {StaticResource typeof(UserControl)} when no theme is set explicitly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overriding makes sense to me. Isn't it the same about Window?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Window is slightly different in that it overrides IStyleable.StyleKey => typeof(Window); whereas UserControl doesn't do that due to #2280. It might make sense to use the same technique for both though now.

@@ -125,7 +136,6 @@
Background="{DynamicResource ComboBoxDropDownBackground}"
BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"
BorderThickness="{DynamicResource ComboBoxDropdownBorderThickness}"
Margin="0,-1,0,-1"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this line as it was causing the ComboBox popup border to be truncated at the top and bottom. I've noticed this for a long time but never investigated it. @danwalmsley you seem to have added this line, do you remember why and it is ok to remove it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a copy-paste from Fluent v1 in WinUI

https://github.com/microsoft/microsoft-ui-xaml/blob/7ed0bcf32a0e02b673a3d29555bd604d1f9ac0b0/dev/ComboBox/ComboBox_themeresources_v1.xaml#L721

That Margin (and any others like it) can safely be removed. It was just a work-around for WinUI/UWP specific issues.

@grokys grokys changed the title WIP: Ported Fluent Theme to ControlTemplates. WIP: Ported Fluent Theme to ControlThemes. Jul 11, 2022
It was causing the title bar to be shown in all designer previews.
And refactored shared date/time picker components into a separate file.
Not happy with the Tag hack but no better way to add additional states to a control currently.
And tweak some designer previews.
@grokys grokys force-pushed the feature/7120-fluent-control-themes branch from 36952df to 20b2bc2 Compare July 11, 2022 10:03
@grokys
Copy link
Member Author

grokys commented Jul 11, 2022

Rebased to credit @Takoooooo for the work he did on this.

<Viewbox>
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" />
</Button>
<Button x:Name="PART_MinimiseButton"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danwalmsley I changed these to be buttons, because previously as panels you lost the ability to press and then cancel the press by releasing outside the bounds of the control.

Previously there was a `MenuItem > Separator` selector, but I don't really see any reason to just apply this style directly to `Separator` itself. It's mainly used in menus and if one wants to use it elsewhere then they'd need to set these properties anyway.
@robloo
Copy link
Contributor

robloo commented Jul 12, 2022

In particular we decided not to allow nested /template/ selectors in control themes, so controls that used these had to have their sub-controls refactored out into separate sub-ControlThemes

I've found other places where directly accessing the template of a control within a template is very useful. Not trying to start another big discussion but if I had a vote I would not want to remove this functionality. It is totally find for one control default style to depend on another control's default style within the same theme (translating to Avalonia's "new terminology" default style = ControlTheme, Theme = FluentTheme). That is a well established convention in XAML styling from the very beginning.

Additionally, you are adding code duplication of styles so it would be easy for a button in one control style to be out-of-sync or outdated with the main button's default style. That is an undetectable error -- however, referring to a template part that no longer exists can be detected. So... well, I would not have done this for

  1. Code duplication
  2. Sure there is separation now so some changes don't break dependent control styles; however, now you have undetectable places where code can become out-of-sync design-wise
  3. Deviation from existing XAML convention which has worked fine for years. A single theme can have inter-dependencies.

@grokys
Copy link
Member Author

grokys commented Jul 12, 2022

I've found other places where directly accessing the template of a control within a template is very useful. Not trying to start another big discussion but if I had a vote I would not want to remove this functionality.

Yeah we had a pretty intense discussion about this internally as well, but the general consensus was:

  • The /template/ selector was generally speaking a mistake. It came from the CSS /deep/ selector which was removed and for good reason - it breaks encapsulation and is essentially the equivalent of changing private fields in a class using reflection
  • A /template/ selector which can poke into any template was needed previously because of the way our styles worked - there was no scoping of styles
  • The CSS /deep/ selector was replaced by the :part selector which is much better because it defines an API contract

So the plan is to:

  • Add a :part selector - the details of this need to be fleshed out, but we already have concept of "parts" and we already have the [TemplatePart] attribute, so conceptually it's a good fit for Avalonia
  • Add a ClassSetter which can be used to add extra state to other controls from a style
  • Long term, eventually do a gentle deprecation of the /template/ selector outside control themes

To give some context: we're currently basically unable to change our templates between major releases because the template contents essentially become a public API due to /template/. So we have to balance the fact that 1) /template is a problem and 2) removing /template/ would break a lot of code if we did it now.

Given the above we felt that preventing meddling with implementation details of other controls in a new feature is a good start, but removing the feature as it already exists is probably a step too far right now.

We are actually open to removing the limitation on nested /template/ selectors in control themes if a problem arises that literally cannot be solved by any other means, but as you know it's far easier to add features than remove them! So for the moment I'm hoping that by disallowing nested template selectors we can understand what exactly they're needed for and try to come up with better solutions for these use-cases.

Does that make sense?

@grokys
Copy link
Member Author

grokys commented Jul 12, 2022

A single theme can have inter-dependencies.

The problem with this is, how do you define a theme? Are we talking about the fluent theme as defined in Avalonia.Themes.Fluent? Or does it include Avalonia.Controls.DataGrid/ColorPicker fluent themes? If it also includes those then it probably should include 3rd party fluent themes, right? And so you're back to having the contents of our themes be a public API.

@grokys
Copy link
Member Author

grokys commented Jul 12, 2022

Started a discussion to discuss the usages of /template/ selectors: #8490

Base automatically changed from refactor/controltemplate-binding-priority to master July 22, 2022 18:19
@grokys grokys changed the title WIP: Ported Fluent Theme to ControlThemes. Ported Fluent Theme to ControlThemes. Jul 22, 2022
@grokys grokys marked this pull request as ready for review July 22, 2022 18:28
@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 0.10.999-cibuild0022401-beta. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json) [PRBUILDID]

@maxkatz6 maxkatz6 merged commit ceac3a5 into master Jul 24, 2022
@maxkatz6 maxkatz6 deleted the feature/7120-fluent-control-themes branch July 24, 2022 00:35
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 this pull request may close these issues.

5 participants