-
Notifications
You must be signed in to change notification settings - Fork 742
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: BackdropMaterial fallback support
- Loading branch information
1 parent
9313e67
commit dfee434
Showing
3 changed files
with
266 additions
and
0 deletions.
There are no files selected for viewing
34 changes: 34 additions & 0 deletions
34
src/Uno.UI/Microsoft/UI/Xaml/Controls/Materials/Backdrop/BackdropMaterial.Properties.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Windows.UI.Xaml; | ||
using Windows.UI.Xaml.Controls; | ||
|
||
namespace Microsoft.UI.Xaml.Controls | ||
{ | ||
public partial class BackdropMaterial | ||
{ | ||
/// <summary> | ||
/// Gets the value of the BackdropMaterial.ApplyToRootOrPageBackground XAML attached property for the target element. | ||
/// </summary> | ||
/// <param name="element">The object from which the property value is read.</param> | ||
/// <returns>The BackdropMaterial.ApplyToRootOrPageBackground XAML attached property value of the requested object.</returns> | ||
public static bool GetApplyToRootOrPageBackground(Control element) => | ||
(bool)element.GetValue(ApplyToRootOrPageBackgroundProperty); | ||
|
||
/// <summary> | ||
/// Sets the value of the BackdropMaterial.ApplyToRootOrPageBackground XAML attached property for a target element. | ||
/// </summary> | ||
/// <param name="element">The object to which the property value is written.</param> | ||
/// <param name="value">The value to set.</param> | ||
public static void SetApplyToRootOrPageBackground(Control element, bool value) => | ||
element.SetValue(ApplyToRootOrPageBackgroundProperty, value); | ||
|
||
/// <summary> | ||
/// Applies the backdrop material to the root or background of the XAML content. | ||
/// </summary> | ||
public static DependencyProperty ApplyToRootOrPageBackgroundProperty { get; } = | ||
DependencyProperty.RegisterAttached( | ||
"ApplyToRootOrPageBackground", | ||
typeof(bool), | ||
typeof(BackdropMaterial), | ||
new PropertyMetadata(false, OnApplyToRootOrPageBackgroundChanged)); | ||
} | ||
} |
213 changes: 213 additions & 0 deletions
213
src/Uno.UI/Microsoft/UI/Xaml/Controls/Materials/Backdrop/BackdropMaterial.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
using System; | ||
using System.Threading; | ||
using Uno.Disposables; | ||
using Uno.UI.Helpers.WinUI; | ||
using Windows.UI; | ||
using Windows.UI.ViewManagement; | ||
using Windows.UI.Xaml; | ||
using Windows.UI.Xaml.Controls; | ||
using Windows.UI.Xaml.Media; | ||
|
||
namespace Microsoft.UI.Xaml.Controls | ||
{ | ||
/// <summary> | ||
/// Helper class to apply a backdrop material to the root of the XAML content. | ||
/// </summary> | ||
public partial class BackdropMaterial | ||
{ | ||
private readonly static ThreadLocal<int> _connectedBrushCount = new ThreadLocal<int>(); | ||
private readonly static ThreadLocal<MicaController> _micaController = new ThreadLocal<MicaController>(); | ||
|
||
internal static DependencyProperty StateProperty { get; } = | ||
DependencyProperty.RegisterAttached("State", typeof(BackdropMaterialState), typeof(BackdropMaterial), new PropertyMetadata(null)); | ||
|
||
private static void OnApplyToRootOrPageBackgroundChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) | ||
{ | ||
if (sender is Control control) | ||
{ | ||
// When the ApplyToRootOrPageBackgroundChanged property is set on a control, create a | ||
// object to attach to that element in a "secret" slot called BackdropMaterial.State. | ||
// This object's lifetime manages the MicaController registration or ownership of the | ||
// assignment of the Background property. | ||
if (GetApplyToRootOrPageBackground(control)) | ||
{ | ||
control.SetValue(StateProperty, new BackdropMaterialState(control)); | ||
} | ||
else | ||
{ | ||
if (control.GetValue(StateProperty) is BackdropMaterialState state) | ||
{ | ||
state.Dispose(); | ||
} | ||
|
||
control.ClearValue(StateProperty); | ||
} | ||
} | ||
} | ||
|
||
private static void CreateOrDestroyMicaController() | ||
{ | ||
// If we are connecting the first BackdropMaterial on this thread, create and configure the MicaController. | ||
// Or if we're disconnecting the last one, clean up the shared MicaController. | ||
if (_connectedBrushCount.Value > 0 && _micaController.Value == null) | ||
{ | ||
var currentWindow = Window.Current; | ||
|
||
_micaController.Value = new MicaController(); | ||
if (!_micaController.Value.SetTarget(currentWindow)) | ||
{ | ||
_micaController.Value = null; | ||
} | ||
} | ||
else if (_connectedBrushCount.Value == 0 && _micaController.Value != null) | ||
{ | ||
_micaController.Value = null; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// This object gets attached to the target of the ApplyToRootOrPageBackground property to track additional | ||
/// state that needs to be cleaned up if that target ever goes away. | ||
/// </summary> | ||
private partial class BackdropMaterialState : DependencyObject, IDisposable | ||
{ | ||
private readonly DispatcherHelper _dispatcherHelper; | ||
private readonly WeakReference<Control> _target; | ||
private readonly IDisposable _themeChangedRevoker; | ||
private readonly IDisposable _colorValuesChangedRevoker; | ||
private readonly UISettings _uiSettings = new UISettings(); | ||
private readonly IDisposable _highContrastChangedRevoker; | ||
|
||
private bool _isHighContrast; | ||
private bool _isDisposed; | ||
|
||
public BackdropMaterialState(Control target) | ||
{ | ||
_dispatcherHelper = new DispatcherHelper(this); | ||
_target = new WeakReference<Control>(target); | ||
|
||
// Track whether we're connected and update the number of connected BackdropMaterial on this thread. | ||
_connectedBrushCount.Value++; | ||
CreateOrDestroyMicaController(); | ||
|
||
// Normally QI would be fine, but .NET is lying about implementing this interface (e.g. C# TestFrame derives from Frame and this QI | ||
// returns success even on RS2, but it's not implemented by XAML until RS3). | ||
if (SharedHelpers.IsRS3OrHigher()) | ||
{ | ||
if (target is FrameworkElement targetThemeChanged) | ||
{ | ||
void OnActualThemeChanged(FrameworkElement sender, object args) | ||
{ | ||
UpdateFallbackBrush(); | ||
} | ||
|
||
targetThemeChanged.ActualThemeChanged += OnActualThemeChanged; | ||
_themeChangedRevoker = Disposable.Create(() => targetThemeChanged.ActualThemeChanged -= OnActualThemeChanged); | ||
} | ||
} | ||
|
||
void OnColorValuesChanged(UISettings uiSettings, object args) | ||
{ | ||
_dispatcherHelper.RunAsync(() => UpdateFallbackBrush()); | ||
} | ||
|
||
_uiSettings.ColorValuesChanged += OnColorValuesChanged; | ||
_colorValuesChangedRevoker = Disposable.Create(() => _uiSettings.ColorValuesChanged -= OnColorValuesChanged); | ||
|
||
// Listen for High Contrast changes | ||
var accessibilitySettings = new AccessibilitySettings(); | ||
_isHighContrast = accessibilitySettings.HighContrast; | ||
|
||
void OnHighContrastChanged(AccessibilitySettings sender, object args) | ||
{ | ||
_dispatcherHelper.RunAsync(() => | ||
{ | ||
_isHighContrast = accessibilitySettings.HighContrast; | ||
UpdateFallbackBrush(); | ||
}); | ||
} | ||
|
||
accessibilitySettings.HighContrastChanged += OnHighContrastChanged; | ||
_highContrastChangedRevoker = Disposable.Create(() => accessibilitySettings.HighContrastChanged -= OnHighContrastChanged); | ||
|
||
UpdateFallbackBrush(); | ||
} | ||
|
||
public BackdropMaterialState() => Dispose(); | ||
|
||
public void Dispose() | ||
{ | ||
if (!_isDisposed) | ||
{ | ||
_isDisposed = true; | ||
_connectedBrushCount.Value--; | ||
CreateOrDestroyMicaController(); | ||
|
||
_highContrastChangedRevoker.Dispose(); | ||
_themeChangedRevoker.Dispose(); | ||
_colorValuesChangedRevoker.Dispose(); | ||
} | ||
} | ||
|
||
private void UpdateFallbackBrush() | ||
{ | ||
if (_target.TryGetTarget(out var target)) | ||
{ | ||
if (_micaController.Value == null) | ||
{ | ||
// When not using mica, use the theme and high contrast states to determine the fallback color. | ||
ElementTheme GetTheme() | ||
{ | ||
// See other IsRS3OrHigher usage for comment explaining why the version check and QI. | ||
if (SharedHelpers.IsRS3OrHigher()) | ||
{ | ||
if (target is FrameworkElement targetTheme) | ||
{ | ||
// TODO Uno specific: ActualTheme always returns Default now, which is invalid | ||
// this specific needs to be reverted when #3302 is implemented. | ||
return Application.Current?.RequestedTheme == ApplicationTheme.Light ? ElementTheme.Light : ElementTheme.Dark; | ||
//return targetTheme.ActualTheme; | ||
} | ||
} | ||
|
||
var value = _uiSettings.GetColorValue(UIColorType.Background); | ||
if (value.B == 0) | ||
{ | ||
return ElementTheme.Dark; | ||
} | ||
|
||
return ElementTheme.Light; | ||
} | ||
var theme = GetTheme(); | ||
|
||
Color GetColor() | ||
{ | ||
if (_isHighContrast) | ||
{ | ||
return _uiSettings.GetColorValue(UIColorType.Background); | ||
} | ||
|
||
if (theme == ElementTheme.Dark) | ||
{ | ||
return MicaController.DarkThemeColor; | ||
} | ||
else | ||
{ | ||
return MicaController.LightThemeColor; | ||
} | ||
} | ||
var color = GetColor(); | ||
|
||
target.Background = new SolidColorBrush(color); | ||
} | ||
else | ||
{ | ||
// When Mica is involved, use transparent for the background (this is so that the hit testing | ||
// behavior is consistent with/without the material). | ||
target.Background = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/Uno.UI/Microsoft/UI/Xaml/Controls/Materials/Backdrop/MicaController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using Windows.UI; | ||
|
||
namespace Microsoft.UI.Xaml.Controls | ||
{ | ||
internal class MicaController | ||
{ | ||
internal static readonly Color DarkThemeColor = Color.FromArgb(255, 32, 32, 32); | ||
internal const float DarkThemeTintOpacity = 0.8f; | ||
|
||
internal static readonly Color LightThemeColor = Color.FromArgb(255, 243, 243, 243); | ||
internal const float LightThemeTintOpacity = 0.5f; | ||
|
||
internal bool SetTarget(Windows.UI.Xaml.Window xamlWindow) | ||
{ | ||
// Uno specific: Actual Mica is not yet supported on any target. | ||
return false; | ||
} | ||
} | ||
} |