Skip to content

Commit

Permalink
feat: add Maui Embedding
Browse files Browse the repository at this point in the history
  • Loading branch information
dansiegel authored and nickrandolph committed Jul 25, 2023
1 parent 0c2d807 commit 4f3632d
Show file tree
Hide file tree
Showing 28 changed files with 1,107 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
<Choose>
<When Condition="$(_IsWinUI)">
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Condition="$(WinAppSdkVersion)==''"/>
<PackageReference Include="Microsoft.WindowsAppSDK" Condition="$(WinAppSdkVersion)!=''" VersionOverride="$(WinAppSdkVersion)"/>
<PackageReference Include="Microsoft.WindowsAppSDK" Condition="$(WinAppSdkVersion)=='' and '$(UseMaui)' != 'true'"/>
<PackageReference Include="Microsoft.WindowsAppSDK" Condition="$(WinAppSdkVersion)!='' and '$(UseMaui)' != 'true'" VersionOverride="$(WinAppSdkVersion)"/>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
</ItemGroup>
</When>
Expand Down
16 changes: 8 additions & 8 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@
<PackageVersion Include="MSTest.TestFramework" Version="2.2.9" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="6.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="6.0.1" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="6.0.4" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.47.0" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.22.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.2.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="Microsoft.UI.Xaml" Version="2.7.1" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.1.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.1.0" Condition="$(UseMaui) != 'true'" />
<PackageVersion Include="Moq" Version="4.17.2" />
<PackageVersion Include="Refit" Version="6.3.2" />
<PackageVersion Include="Refit.HttpClientFactory" Version="6.3.2" />
Expand Down
29 changes: 29 additions & 0 deletions src/Uno.Extensions.Maui/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text;
global using System.Threading.Tasks;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
global using Microsoft.Maui.Embedding;
global using Microsoft.Maui.Platform;
global using Microsoft.UI.Xaml;
global using Microsoft.UI.Xaml.Controls;
global using Microsoft.UI.Xaml.Markup;
global using Uno.Extensions.Maui.Properties;
global using Application = Microsoft.UI.Xaml.Application;
global using MauiApplication = Microsoft.Maui.Controls.Application;
global using Binding = Microsoft.Maui.Controls.Binding;
global using BindingMode = Microsoft.Maui.Controls.BindingMode;
global using View = Microsoft.Maui.Controls.View;
global using MauiApp = Microsoft.Maui.Hosting.MauiApp;
global using MauiAppBuilder = Microsoft.Maui.Hosting.MauiAppBuilder;
global using IMauiContext = Microsoft.Maui.IMauiContext;
global using MauiContext = Microsoft.Maui.MauiContext;
global using NativeMauiBinding = Microsoft.Maui.Controls.Binding;
global using WinUIColor = Windows.UI.Color;
global using NativeMauiColor = Microsoft.Maui.Graphics.Color;
global using MauiResourceDictionary = Microsoft.Maui.Controls.ResourceDictionary;
global using MauiConverter = Microsoft.Maui.Controls.IValueConverter;
global using WinUIConverter = Microsoft.UI.Xaml.Data.IValueConverter;

43 changes: 43 additions & 0 deletions src/Uno.Extensions.Maui/Internals/ConversionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Uno.Extensions.Maui.Internals;

internal static class ConversionExtensions
{
public static Microsoft.Maui.Controls.ResourceDictionary ToMauiResources(this ResourceDictionary input)
{
var output = new Microsoft.Maui.Controls.ResourceDictionary();
foreach (var merged in input.MergedDictionaries)
{
output.MergedDictionaries.Add(merged.ToMauiResources());
}

foreach (var key in input.Keys)
{
if (input.MergedDictionaries.Any(x => x.ContainsKey(key)))
{
continue;
}

TryAddValue(ref output, key, input[key]);
}

return output;
}

private static void TryAddValue(ref Microsoft.Maui.Controls.ResourceDictionary resources, object sourceKey, object value)
{
if (value is Style winUIStyle)
{
// This needs to be nested to prevent further processing if we cannot generate a Maui Style
if(Interop.MauiInterop.TryGetStyle(winUIStyle, out var style) && style != null)
{
var key = sourceKey is string str ? str : style.TargetType.FullName;
resources[key] = style;
}
}
else if (sourceKey is string key && !string.IsNullOrEmpty(key) && !resources.ContainsKey(key))
{
var mauiValue = ConversionHelpers.ToMauiValue(value);
resources[key] = mauiValue ?? value;
}
}
}
57 changes: 57 additions & 0 deletions src/Uno.Extensions.Maui/Internals/ConversionHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Globalization;
using Microsoft.UI.Xaml.Media;
using LayoutOptions = Microsoft.Maui.Controls.LayoutOptions;
using Thickness = Microsoft.UI.Xaml.Thickness;

namespace Uno.Extensions.Maui.Internals;

internal static class ConversionHelpers
{
internal static object? ToMauiValue(object input)
{
return input switch
{
int valueAsInt => valueAsInt,
double valueAsDouble => valueAsDouble,
float valueAsFloat => valueAsFloat,
WinUIConverter converter => new UnoHostConverter(converter),
SolidColorBrush solidColorBrush => new Microsoft.Maui.Controls.SolidColorBrush(new NativeMauiColor(solidColorBrush.Color.R, solidColorBrush.Color.G, solidColorBrush.Color.B, solidColorBrush.Color.A)),
WinUIColor color => new NativeMauiColor(color.R, color.G, color.B, color.A),
Thickness thickness => new Microsoft.Maui.Thickness(thickness.Left, thickness.Top, thickness.Right, thickness.Bottom),
HorizontalAlignment horizontalAlignment => GetLayoutOptions(horizontalAlignment),
VerticalAlignment verticalAlignment => GetLayoutOptions(verticalAlignment),
_ => null
};
}

private static LayoutOptions GetLayoutOptions(HorizontalAlignment alignment) =>
alignment switch
{
HorizontalAlignment.Center => LayoutOptions.Center,
HorizontalAlignment.Right => LayoutOptions.End,
HorizontalAlignment.Left => LayoutOptions.Start,
_ => LayoutOptions.Fill
};

private static LayoutOptions GetLayoutOptions(VerticalAlignment alignment) =>
alignment switch
{
VerticalAlignment.Center => LayoutOptions.Center,
VerticalAlignment.Bottom => LayoutOptions.End,
VerticalAlignment.Top => LayoutOptions.Start,
_ => LayoutOptions.Fill
};

private class UnoHostConverter : MauiConverter
{
private WinUIConverter _converter { get; }

public UnoHostConverter(WinUIConverter converter) => _converter = converter;

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
_converter.Convert(value, targetType, parameter, culture.Name);

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
_converter.ConvertBack(value, targetType, parameter, culture.Name);
}
}
9 changes: 9 additions & 0 deletions src/Uno.Extensions.Maui/Internals/UnoHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Uno.Extensions.Maui.Internals;

internal class UnoHost : Microsoft.Maui.Controls.VisualElement
{
public UnoHost(ResourceDictionary resources)
{
Resources = resources.ToMauiResources();
}
}
43 changes: 43 additions & 0 deletions src/Uno.Extensions.Maui/Internals/UnoMauiEmbeddingInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.Maui.Hosting;

namespace Uno.Extensions.Maui.Internals;

internal class UnoMauiEmbeddingInitializer : IMauiInitializeService
{
private readonly Application _app;

public UnoMauiEmbeddingInitializer(Application app)
{
_app = app;
}

public void Initialize(IServiceProvider services)
{
var resources = _app.Resources.ToMauiResources();
var iApp = services.GetRequiredService<global::Microsoft.Maui.IApplication>();
if (HasResources(resources) && iApp is MauiApplication mauiApp)
{
mauiApp.Resources.MergedDictionaries.Add(resources);
}
}

private static bool HasResources(MauiResourceDictionary resources)
{
if (resources.Keys.Any())
{
return true;
}
else if (resources.MergedDictionaries is not null && resources.MergedDictionaries.Any())
{
for (var i = 0; i < resources.MergedDictionaries.Count; i++)
{
if (HasResources(resources.MergedDictionaries.ElementAt(i)))
{
return true;
}
}
}

return false;
}
}
8 changes: 8 additions & 0 deletions src/Uno.Extensions.Maui/Interop/IWinUIToMauiStyleHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Uno.Extensions.Maui.Interop;

public interface IWinUIToMauiStyleHandler
{
Type TargetType { get; }

(Microsoft.Maui.Controls.BindableProperty Property, object? Value)? Process(DependencyProperty property, object value);
}
20 changes: 20 additions & 0 deletions src/Uno.Extensions.Maui/Interop/LabelStyleHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Uno.Extensions.Maui.Interop;

internal class LabelStyleHandler : WinUIToMauiStyleHandler
{
public override Type TargetType => typeof(Microsoft.Maui.Controls.Label);

public override (Microsoft.Maui.Controls.BindableProperty Property, object? Value)? Process(DependencyProperty property, object value)
{
if (property == Control.ForegroundProperty && TryConvertValue(value, out var converted) && converted is Microsoft.Maui.Controls.SolidColorBrush brush)
{
return (Microsoft.Maui.Controls.Label.TextColorProperty, brush.Color);
}
else if (property == TextBlock.FontSizeProperty)
{
return (Microsoft.Maui.Controls.Label.FontSizeProperty, ConvertToMauiValue(value));
}

return null;
}
}
13 changes: 13 additions & 0 deletions src/Uno.Extensions.Maui/Interop/LayoutStyleHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Uno.Extensions.Maui.Interop;

internal class LayoutStyleHandler : WinUIToMauiStyleHandler
{
public override Type TargetType => typeof(Microsoft.Maui.Controls.Layout);

public override (Microsoft.Maui.Controls.BindableProperty Property, object? Value)? Process(DependencyProperty property, object value)
{
return property == Control.PaddingProperty
? (Microsoft.Maui.Controls.Layout.PaddingProperty, ConvertToMauiValue(value))
: null;
}
}
80 changes: 80 additions & 0 deletions src/Uno.Extensions.Maui/Interop/MauiInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
namespace Uno.Extensions.Maui.Interop;

internal static class MauiInterop
{
private static readonly List<ControlMapping> _mappings = new();
private static readonly List<IWinUIToMauiStyleHandler> _styleHandlers = new();

static MauiInterop()
{
MapControl<TextBlock, Microsoft.Maui.Controls.Label>();
MapControl<TextBox, Microsoft.Maui.Controls.Entry>();
MapControl<Grid, Microsoft.Maui.Controls.Grid>();
MapControl<StackPanel, Microsoft.Maui.Controls.StackLayout>();

MapStyleHandler<LabelStyleHandler>();
MapStyleHandler<VisualElementStyleHandler>();
MapStyleHandler<ViewStyleHandler>();
MapStyleHandler<LayoutStyleHandler>();
}

public static void MapControl<TWinUI, TMaui>()
where TWinUI : FrameworkElement
where TMaui : Microsoft.Maui.Controls.View =>
_mappings.Add(new(typeof(TWinUI), typeof(TMaui)));

public static void MapStyleHandler<THandler>()
where THandler : IWinUIToMauiStyleHandler, new() =>
_styleHandlers.Add(new THandler());

public static bool TryGetMapping(Type winUI, out Type? maui)
{
maui = _mappings.FirstOrDefault(x => x.WinUI == winUI)?.Maui;
return maui is not null;
}

public static bool TryGetStyle(Style winUIStyle, out Microsoft.Maui.Controls.Style? style)
{
style = null;

if (winUIStyle.TargetType is null
|| !TryGetMapping(winUIStyle.TargetType, out var targetType)
|| targetType is null
|| (!winUIStyle.Setters.Any() && winUIStyle.BasedOn is null))
{
return false;
}

var tempStyle = new Microsoft.Maui.Controls.Style(targetType);
foreach(var setter in winUIStyle.Setters.OfType<Setter>())
{
if (setter.Property is null || setter.Value is null)
{
continue;
}

var handlers = GetHandlers(targetType);

handlers.ForEach(handler =>
{
var processed = handler.Process(setter.Property, setter.Value);
if (processed is null)
{
return;
}

tempStyle.Setters.Add(new Microsoft.Maui.Controls.Setter { Property = processed.Value.Property, Value = processed.Value.Value });
});
}

style = tempStyle;

return style.Setters.Any() || style.BasedOn is not null;
}

private static List<IWinUIToMauiStyleHandler> GetHandlers(Type targetType) =>
_styleHandlers.Where(x => x.TargetType == targetType
|| x.TargetType.IsAssignableFrom(targetType)).ToList();

private record ControlMapping(Type WinUI, Type Maui);
}
13 changes: 13 additions & 0 deletions src/Uno.Extensions.Maui/Interop/ViewStyleHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Uno.Extensions.Maui.Interop;

internal class ViewStyleHandler : WinUIToMauiStyleHandler
{
public override Type TargetType => typeof(Microsoft.Maui.Controls.View);

public override (Microsoft.Maui.Controls.BindableProperty Property, object? Value)? Process(DependencyProperty property, object value)
{
return property == Control.MarginProperty
? (Microsoft.Maui.Controls.View.MarginProperty, ConvertToMauiValue(value))
: null;
}
}
13 changes: 13 additions & 0 deletions src/Uno.Extensions.Maui/Interop/VisualElementStyleHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Uno.Extensions.Maui.Interop;

internal class VisualElementStyleHandler : WinUIToMauiStyleHandler
{
public override Type TargetType => typeof(Microsoft.Maui.Controls.VisualElement);

public override (Microsoft.Maui.Controls.BindableProperty Property, object? Value)? Process(DependencyProperty property, object value)
{
return property == Control.BackgroundProperty
? (Microsoft.Maui.Controls.VisualElement.BackgroundProperty, ConvertToMauiValue(value))
: null;
}
}
Loading

0 comments on commit 4f3632d

Please sign in to comment.