Skip to content

Commit

Permalink
Implement ActivityIndicatorHandler in WinUI (#761)
Browse files Browse the repository at this point in the history
* Implement ActivityIndicatorHandler in WinUI

* Fix build error

* Enable nullable in ActivityIndicatorExtensions

* Added nullable enable flag in WinUI ActivityIndicatorHandler

* Fix build error

* Apply suggestions from code review

Co-authored-by: Hadrian Tang <hadrianwttang@outlook.com>

* Fix merge

* Fixed ResourceDictionary error

* Move extensions to be internal

Co-authored-by: Rui Marinho <me@ruimarinho.net>
Co-authored-by: Hadrian Tang <hadrianwttang@outlook.com>
  • Loading branch information
3 people authored May 18, 2021
1 parent 2216517 commit 4641d1d
Show file tree
Hide file tree
Showing 10 changed files with 635 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/Compatibility/Core/src/WinUI/ActivityIndicatorRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void OnControlLoaded(object sender, RoutedEventArgs routedEventArgs)
UpdateColor();
}

[PortHandler]
void UpdateColor()
{
Color color = Element.Color;
Expand All @@ -60,6 +61,7 @@ void UpdateColor()
}
}

[PortHandler]
void UpdateIsRunning()
{
Control.ElementOpacity = Element.IsRunning ? Element.Opacity : 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Microsoft.Maui.Controls.Compatibility.Platform.UWP
{
[PortHandler]
internal static class FrameworkElementExtensions
{
static readonly Lazy<ConcurrentDictionary<Type, DependencyProperty>> ForegroundProperties =
Expand Down
1 change: 1 addition & 0 deletions src/Controls/src/Core/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Microsoft.Maui.Controls.Internals
{
[PortHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ReflectionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
using System;
using Microsoft.UI.Xaml.Controls;

#nullable enable
namespace Microsoft.Maui.Handlers
{
public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator, ProgressBar>
public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator, MauiActivityIndicator>
{
protected override ProgressBar CreateNativeView() => new ProgressBar();
object? _foregroundDefault;

protected override MauiActivityIndicator CreateNativeView() => new MauiActivityIndicator
{
IsIndeterminate = true,
Style = UI.Xaml.Application.Current.Resources["MauiActivityIndicatorStyle"] as UI.Xaml.Style
};

protected override void SetupDefaults(MauiActivityIndicator nativeView)
{
_foregroundDefault = nativeView.GetForegroundCache();

base.SetupDefaults(nativeView);
}

[MissingMapper]
public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator) { }
public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
{
handler.NativeView?.UpdateIsRunning(activityIndicator);
}

[MissingMapper]
public static void MapColor(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator) { }
public static void MapColor(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
{
handler.NativeView?.UpdateColor(activityIndicator, handler._foregroundDefault);
}
}
}
95 changes: 95 additions & 0 deletions src/Core/src/Platform/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Microsoft.Maui
{
internal static class ReflectionExtensions
{
public static FieldInfo? GetField(this Type type, Func<FieldInfo, bool> predicate)
{
return GetFields(type).FirstOrDefault(predicate);
}

public static FieldInfo? GetField(this Type type, string name)
{
return type.GetField(fi => fi.Name == name);
}

public static IEnumerable<FieldInfo> GetFields(this Type type)
{
return GetParts(type, i => i.DeclaredFields);
}

public static IEnumerable<PropertyInfo> GetProperties(this Type type)
{
return GetParts(type, ti => ti.DeclaredProperties);
}

public static PropertyInfo? GetProperty(this Type type, string name)
{
Type? t = type;
while (t != null)
{
TypeInfo ti = t.GetTypeInfo();
PropertyInfo? property = ti.GetDeclaredProperty(name);

if (property != null)
return property;

t = ti.BaseType;
}

return null;
}

internal static object[]? GetCustomAttributesSafe(this Assembly assembly, Type attrType)
{
try
{
#if !NETSTANDARD1_0
return assembly.GetCustomAttributes(attrType, true);
#else
return assembly.GetCustomAttributes(attrType).ToArray();
#endif
}
catch (FileNotFoundException)
{
// Sometimes the previewer doesn't actually have everything required for these loads to work
// TODO: Register the exception in the Log when we have the Logger ported
}

return null;
}

public static Type[] GetExportedTypes(this Assembly assembly)
{
return assembly.ExportedTypes.ToArray();
}

public static bool IsAssignableFrom(this Type self, Type c)
{
return self.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo());
}

public static bool IsInstanceOfType(this Type self, object o)
{
return self.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
}

static IEnumerable<T> GetParts<T>(Type type, Func<TypeInfo, IEnumerable<T>> selector)
{
Type? t = type;
while (t != null)
{
TypeInfo ti = t.GetTypeInfo();
foreach (T f in selector(ti))
yield return f;
t = ti.BaseType;
}
}
}
}
33 changes: 33 additions & 0 deletions src/Core/src/Platform/Windows/ActivityIndicatorExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#nullable enable
using Microsoft.Maui.Graphics;

namespace Microsoft.Maui
{
public static class ActivityIndicatorExtensions
{
public static void UpdateIsRunning(this MauiActivityIndicator mauiActivityIndicator, IActivityIndicator activityIndicator)
{
// TODO: Use IView Opacity if the ActivityIndicator is running.
mauiActivityIndicator.ElementOpacity = activityIndicator.IsRunning ? 1 : 0;
}
public static void UpdateColor(this MauiActivityIndicator mauiActivityIndicator, IActivityIndicator activityIndicator)
{
mauiActivityIndicator.UpdateColor(activityIndicator, null);
}

public static void UpdateColor(this MauiActivityIndicator mauiActivityIndicator, IActivityIndicator activityIndicator, object? foregroundDefault)
{
Color color = activityIndicator.Color;

if (color.IsDefault())
{
if (foregroundDefault != null)
mauiActivityIndicator.RestoreForegroundCache(foregroundDefault);
}
else
{
mauiActivityIndicator.Foreground = color.ToNative();
}
}
}
}
144 changes: 144 additions & 0 deletions src/Core/src/Platform/Windows/FrameworkElementExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#nullable enable
using System;
using System.Linq;
using Microsoft.UI.Xaml;
using System.Collections.Concurrent;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.UI.Xaml.Controls;
using WBinding = Microsoft.UI.Xaml.Data.Binding;
using WBrush = Microsoft.UI.Xaml.Media.Brush;
using WBindingExpression = Microsoft.UI.Xaml.Data.BindingExpression;

namespace Microsoft.Maui
{
internal static class FrameworkElementExtensions
{
static readonly Lazy<ConcurrentDictionary<Type, DependencyProperty>> ForegroundProperties =
new Lazy<ConcurrentDictionary<Type, DependencyProperty>>(() => new ConcurrentDictionary<Type, DependencyProperty>());

public static WBrush GetForeground(this FrameworkElement element)
{
if (element == null)
throw new ArgumentNullException(nameof(element));

return (WBrush)element.GetValue(GetForegroundProperty(element));
}

public static WBinding? GetForegroundBinding(this FrameworkElement element)
{
WBindingExpression expr = element.GetBindingExpression(GetForegroundProperty(element));

if (expr == null)
return null;

return expr.ParentBinding;
}

public static object GetForegroundCache(this FrameworkElement element)
{
WBinding? binding = GetForegroundBinding(element);

if (binding != null)
return binding;

return GetForeground(element);
}

public static void RestoreForegroundCache(this FrameworkElement element, object cache)
{
var binding = cache as WBinding;
if (binding != null)
SetForeground(element, binding);
else
SetForeground(element, (WBrush)cache);
}

public static void SetForeground(this FrameworkElement element, WBrush foregroundBrush)
{
if (element == null)
throw new ArgumentNullException(nameof(element));

element.SetValue(GetForegroundProperty(element), foregroundBrush);
}

public static void SetForeground(this FrameworkElement element, WBinding binding)
{
if (element == null)
throw new ArgumentNullException(nameof(element));

element.SetBinding(GetForegroundProperty(element), binding);
}

internal static IEnumerable<T?> GetDescendantsByName<T>(this DependencyObject parent, string elementName) where T : DependencyObject
{
int myChildrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < myChildrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
var controlName = child.GetValue(FrameworkElement.NameProperty) as string;
if (controlName == elementName && child is T t)
yield return t;
else
{
foreach (var subChild in child.GetDescendantsByName<T>(elementName))
yield return subChild;
}
}
}

internal static T? GetFirstDescendant<T>(this DependencyObject element) where T : FrameworkElement
{
int count = VisualTreeHelper.GetChildrenCount(element);
for (var i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(element, i);

if ((child as T ?? GetFirstDescendant<T>(child)) is T target)
return target;
}

return null;
}

static DependencyProperty? GetForegroundProperty(FrameworkElement element)
{
if (element is Control)
return Control.ForegroundProperty;
if (element is TextBlock)
return TextBlock.ForegroundProperty;

Type type = element.GetType();

if (!ForegroundProperties.Value.TryGetValue(type, out var foregroundProperty))
{
if (ReflectionExtensions.GetFields(type).FirstOrDefault(f => f.Name == "ForegroundProperty") is not FieldInfo field)
throw new ArgumentException("type is not a Foregroundable type");

if (field.GetValue(null) is DependencyProperty property)
ForegroundProperties.Value.TryAdd(type, property);

return null;
}

return foregroundProperty;
}

internal static IEnumerable<T?> GetChildren<T>(this DependencyObject parent) where T : DependencyObject
{
int myChildrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < myChildrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T t)
yield return t;
else
{
foreach (var subChild in child.GetChildren<T>())
yield return subChild;
}
}
}
}
}
27 changes: 27 additions & 0 deletions src/Core/src/Platform/Windows/MauiActivityIndicator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace Microsoft.Maui
{
public class MauiActivityIndicator : ProgressBar
{
public static readonly DependencyProperty ElementOpacityProperty = DependencyProperty.Register(
nameof(ElementOpacity), typeof(double), typeof(MauiActivityIndicator), new PropertyMetadata(default(double)));

public double ElementOpacity
{
get => (double)GetValue(ElementOpacityProperty);
set => SetValue(ElementOpacityProperty, value);
}

protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize)
{
var result = base.MeasureOverride(availableSize);

if (!double.IsInfinity(availableSize.Width))
result.Width = availableSize.Width;

return result;
}
}
}
Loading

0 comments on commit 4641d1d

Please sign in to comment.