From bbfed1c3a5ffd2d741f71dce931716a087a05e79 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 12 Mar 2019 19:45:12 +0300 Subject: [PATCH 1/4] Refactored platform options --- src/Avalonia.Controls/AppBuilderBase.cs | 21 +++++- src/Avalonia.Native/AvaloniaNativePlatform.cs | 65 +++++++++---------- .../AvaloniaNativePlatformExtensions.cs | 32 +++++---- src/Avalonia.Native/PopupImpl.cs | 2 +- src/Avalonia.Native/WindowImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 4 +- src/Avalonia.X11/X11Platform.cs | 6 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 20 ++++-- 8 files changed, 89 insertions(+), 63 deletions(-) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 1f6870d60d..a26b7d7405 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -15,6 +15,7 @@ namespace Avalonia.Controls public abstract class AppBuilderBase where TAppBuilder : AppBuilderBase, new() { private static bool s_setupWasAlreadyCalled; + private Action _optionsInitializers; /// /// Gets or sets the instance. @@ -249,6 +250,24 @@ namespace Avalonia.Controls Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke(); } + /// + /// Configures platform-specific options + /// + public TAppBuilder With(T options) + { + _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToConstant(options); }; + return Self; + } + + /// + /// Configures platform-specific options + /// + public TAppBuilder With(Func options) + { + _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToFunc(options); }; + return Self; + } + /// /// Sets up the platform-speciic services for the . /// @@ -280,7 +299,7 @@ namespace Avalonia.Controls } s_setupWasAlreadyCalled = true; - + _optionsInitializers?.Invoke(); RuntimePlatformServicesInitializer(); WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index ff410ddbe3..43eb959695 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -16,6 +16,7 @@ namespace Avalonia.Native class AvaloniaNativePlatform : IPlatformSettings, IWindowingPlatform { private readonly IAvaloniaNativeFactory _factory; + private AvaloniaNativePlatformOptions _options; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); @@ -27,29 +28,31 @@ namespace Avalonia.Native public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO - public static void Initialize(IntPtr factory, Action configure) + public static void Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { new AvaloniaNativePlatform(new IAvaloniaNativeFactory(factory)) - .DoInitialize(configure); + .DoInitialize(options); } delegate IntPtr CreateAvaloniaNativeDelegate(); - public static void Initialize(string library, Action configure) + public static void Initialize(AvaloniaNativePlatformOptions options) { - var loader = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? (IDynLoader)new Win32Loader() : new UnixLoader(); - var lib = loader.LoadLibrary(library); - var proc = loader.GetProcAddress(lib, "CreateAvaloniaNative", false); - var d = Marshal.GetDelegateForFunctionPointer(proc); + if (options.AvaloniaNativeLibraryPath != null) + { + var loader = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + (IDynLoader)new Win32Loader() : + new UnixLoader(); + var lib = loader.LoadLibrary(options.AvaloniaNativeLibraryPath); + var proc = loader.GetProcAddress(lib, "CreateAvaloniaNative", false); + var d = Marshal.GetDelegateForFunctionPointer(proc); - Initialize(d(), configure); - } - public static void Initialize(Action configure) - { - Initialize(CreateAvaloniaNative(), configure); + Initialize(d(), options); + } + else + Initialize(CreateAvaloniaNative(), options); } private AvaloniaNativePlatform(IAvaloniaNativeFactory factory) @@ -57,14 +60,20 @@ namespace Avalonia.Native _factory = factory; } - void DoInitialize(Action configure) + void DoInitialize(AvaloniaNativePlatformOptions options) { - var opts = new AvaloniaNativeOptions(_factory); - configure?.Invoke(opts); + _options = options; _factory.Initialize(); + if (_factory.MacOptions != null) + { + var macOpts = AvaloniaLocator.Current.GetService(); + if (macOpts != null) + _factory.MacOptions.ShowInDock = macOpts.ShowInDock ? 1 : 0; + } AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())) + .Bind() + .ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())) .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) @@ -76,13 +85,13 @@ namespace Avalonia.Native .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature())) - .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows)) - .Bind().ToConstant(opts); + .Bind() + .ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows)); } public IWindowImpl CreateWindow() { - return new WindowImpl(_factory); + return new WindowImpl(_factory, _options); } public IEmbeddableWindowImpl CreateEmbeddableWindow() @@ -92,7 +101,7 @@ namespace Avalonia.Native public IPopupImpl CreatePopup() { - return new PopupImpl(_factory); + return new PopupImpl(_factory, _options); } } @@ -116,18 +125,4 @@ namespace Avalonia.Native } } } - - public class AvaloniaNativeOptions - { - public AvaloniaNativeMacOptions MacOptions { get; set; } - public bool UseDeferredRendering { get; set; } = true; - public bool UseGpu { get; set; } = true; - internal AvaloniaNativeOptions(IAvaloniaNativeFactory factory) - { - var mac = factory.GetMacOptions(); - if (mac != null) - MacOptions = new AvaloniaNativeMacOptions(mac); - } - - } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index b14a313546..09f822cf46 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -9,21 +9,27 @@ namespace Avalonia { public static class AvaloniaNativePlatformExtensions { - public static T UseAvaloniaNative(this T builder, - string libraryPath = null, - Action configure = null) - where T : AppBuilderBase, new() + public static T UseAvaloniaNative(this T builder) + where T : AppBuilderBase, new() { - if (libraryPath == null) - { - builder.UseWindowingSubsystem(() => AvaloniaNativePlatform.Initialize(configure)); - } - else - { - builder.UseWindowingSubsystem(() => AvaloniaNativePlatform.Initialize(libraryPath, configure)); - } - + builder.UseWindowingSubsystem(() => + AvaloniaNativePlatform.Initialize( + AvaloniaLocator.Current.GetService() ?? + new AvaloniaNativePlatformOptions())); return builder; } } + + public class AvaloniaNativePlatformOptions + { + public bool UseDeferredRendering { get; set; } = true; + public bool UseGpu { get; set; } = true; + public string AvaloniaNativeLibraryPath { get; set; } + } + + // ReSharper disable once InconsistentNaming + public class MacOSPlatformOptions + { + public bool ShowInDock { get; set; } = true; + } } diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 6005f01a60..a470caa80e 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -9,7 +9,7 @@ namespace Avalonia.Native { public class PopupImpl : WindowBaseImpl, IPopupImpl { - public PopupImpl(IAvaloniaNativeFactory factory) + public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts) { using (var e = new PopupEvents(this)) { diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 4a3a23b4c8..076fe9ccae 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -12,7 +12,7 @@ namespace Avalonia.Native public class WindowImpl : WindowBaseImpl, IWindowImpl { IAvnWindow _native; - public WindowImpl(IAvaloniaNativeFactory factory) + public WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts) { using (var e = new WindowEvents(this)) { diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 3c8d8b597f..73b8834375 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -31,10 +31,8 @@ namespace Avalonia.Native private double _savedScaling; private GlPlatformSurface _glSurface; - public WindowBaseImpl() + public WindowBaseImpl(AvaloniaNativePlatformOptions opts) { - var opts = AvaloniaLocator.Current.GetService(); - _gpu = opts.UseGpu; _deferredRendering = opts.UseDeferredRendering; diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 5a19187a22..f7b94b4cd7 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -94,9 +94,11 @@ namespace Avalonia } public static class AvaloniaX11PlatformExtensions { - public static T UseX11(this T builder, X11PlatformOptions options = null) where T : AppBuilderBase, new() + public static T UseX11(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions())); + builder.UseWindowingSubsystem(() => + new AvaloniaX11Platform().Initialize(AvaloniaLocator.Current.GetService() ?? + new X11PlatformOptions())); return builder; } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index cf7ebeed92..f679c2410e 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -26,15 +26,21 @@ namespace Avalonia public static class Win32ApplicationExtensions { public static T UseWin32( - this T builder, - bool deferredRendering = true, bool allowEgl = false) + this T builder) where T : AppBuilderBase, new() { return builder.UseWindowingSubsystem( - () => Win32.Win32Platform.Initialize(deferredRendering, allowEgl), + () => Win32.Win32Platform.Initialize( + AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions()), "Win32"); } } + + public class Win32PlatformOptions + { + public bool UseDeferredRendering { get; set; } = true; + public bool AllowEglInitialization { get; set; } + } } namespace Avalonia.Win32 @@ -63,10 +69,10 @@ namespace Avalonia.Win32 public static void Initialize() { - Initialize(true); + Initialize(new Win32PlatformOptions()); } - public static void Initialize(bool deferredRendering = true, bool allowEgl = false) + public static void Initialize(Win32PlatformOptions options) { AvaloniaLocator.CurrentMutable .Bind().ToSingleton() @@ -80,9 +86,9 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToSingleton() .Bind().ToConstant(s_instance); - if (allowEgl) + if (options.AllowEglInitialization) Win32GlManager.Initialize(); - UseDeferredRendering = deferredRendering; + UseDeferredRendering = options.UseDeferredRendering; _uiThread = Thread.CurrentThread; if (OleContext.Current != null) From 26f5a3fe7a3d9ac404925dfb46da7612ab55e0e1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 12 Mar 2019 20:04:23 +0300 Subject: [PATCH 2/4] Fixed compilation --- samples/interop/Direct3DInteropSample/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/interop/Direct3DInteropSample/Program.cs b/samples/interop/Direct3DInteropSample/Program.cs index d5de5ccb4e..21302fa68a 100644 --- a/samples/interop/Direct3DInteropSample/Program.cs +++ b/samples/interop/Direct3DInteropSample/Program.cs @@ -11,7 +11,9 @@ namespace Direct3DInteropSample { static void Main(string[] args) { - AppBuilder.Configure().UseWin32(deferredRendering: false).UseDirect2D1().Start(); + AppBuilder.Configure() + .With(new Win32PlatformOptions {UseDeferredRendering = false}) + .UseWin32().UseDirect2D1().Start(); } } } From 529ab728cb7ff9595ca10f1369702a15718cba5d Mon Sep 17 00:00:00 2001 From: OronDF343 Date: Fri, 22 Mar 2019 09:17:47 +0200 Subject: [PATCH 3/4] Add support for ConverterParameter to MultiBinding --- src/Markup/Avalonia.Markup/Data/MultiBinding.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 4921b7c9d7..4e2fd40b21 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -23,10 +23,15 @@ namespace Avalonia.Data public IList Bindings { get; set; } = new List(); /// - /// Gets or sets the to use. + /// Gets or sets the to use. /// public IMultiValueConverter Converter { get; set; } + /// + /// Gets or sets a parameter to pass to . + /// + public object ConverterParameter { get; set; } + /// /// Gets or sets the value to use when the binding is unable to produce a value. /// @@ -79,7 +84,7 @@ namespace Avalonia.Data private object ConvertValue(IList values, Type targetType) { - var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentCulture); + var converted = Converter.Convert(values, targetType, ConverterParameter, CultureInfo.CurrentCulture); if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null) { From d065568be984d7f37be51287c453b1d0d4e6ab87 Mon Sep 17 00:00:00 2001 From: OronDF343 Date: Fri, 22 Mar 2019 12:05:11 +0200 Subject: [PATCH 4/4] Added MultiBinding.StringFormat + Relevant unit tests --- .../Avalonia.Markup/Data/MultiBinding.cs | 17 +++- .../Data/MultiBindingTests_Converters.cs | 81 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 4e2fd40b21..f8c21deb8b 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -52,6 +52,11 @@ namespace Avalonia.Data /// public RelativeSource RelativeSource { get; set; } + /// + /// Gets or sets the string format. + /// + public string StringFormat { get; set; } + /// public InstancedBinding Initiate( IAvaloniaObject target, @@ -84,13 +89,23 @@ namespace Avalonia.Data private object ConvertValue(IList values, Type targetType) { - var converted = Converter.Convert(values, targetType, ConverterParameter, CultureInfo.CurrentCulture); + var culture = CultureInfo.CurrentCulture; + var converted = Converter.Convert(values, targetType, ConverterParameter, culture); if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null) { converted = FallbackValue; } + // We only respect `StringFormat` if the type of the property we're assigning to will + // accept a string. Note that this is slightly different to WPF in that WPF only applies + // `StringFormat` for target type `string` (not `object`). + if (!string.IsNullOrWhiteSpace(StringFormat) && + (targetType == typeof(string) || targetType == typeof(object))) + { + converted = string.Format(culture, StringFormat, converted); + } + return converted; } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs new file mode 100644 index 0000000000..bd4b5b9d04 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs @@ -0,0 +1,81 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See license.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; +using Xunit; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class MultiBindingTests_Converters + { + [Fact] + public void StringFormat_Should_Be_Applied() + { + var textBlock = new TextBlock + { + DataContext = new MultiBindingTests_Converters.Class1(), + }; + + var target = new MultiBinding + { + StringFormat = "Foo + Bar = {0}", + Converter = new SumOfDoublesConverter(), + Bindings = + { + new Binding(nameof(MultiBindingTests_Converters.Class1.Foo)), + new Binding(nameof(MultiBindingTests_Converters.Class1.Bar)), + } + }; + + textBlock.Bind(TextBlock.TextProperty, target); + + Assert.Equal("Foo + Bar = 3", textBlock.Text); + } + + [Fact] + public void StringFormat_Should_Not_Be_Applied_When_Binding_To_Non_String_Or_Object() + { + var textBlock = new TextBlock + { + DataContext = new MultiBindingTests_Converters.Class1(), + }; + + var target = new MultiBinding + { + StringFormat = "Hello {0}", + Converter = new SumOfDoublesConverter(), + Bindings = + { + new Binding(nameof(MultiBindingTests_Converters.Class1.Foo)), + new Binding(nameof(MultiBindingTests_Converters.Class1.Bar)), + } + }; + + textBlock.Bind(TextBlock.WidthProperty, target); + + Assert.Equal(3.0, textBlock.Width); + } + + private class SumOfDoublesConverter : IMultiValueConverter + { + public object Convert(IList values, Type targetType, object parameter, CultureInfo culture) + { + return values.OfType().Sum(); + } + } + + private class Class1 + { + public double Foo { get; set; } = 1; + public double Bar { get; set; } = 2; + } + } +}