Browse Source

Merge branch 'master' into datagrid-integration

Steven Kirk 6 years ago
parent
commit
ed5907b15c

+ 1 - 1
samples/ControlCatalog/Pages/ScreenPage.cs

@@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
         {
             base.Render(context);
             Window w = (Window)VisualRoot;
-            Screen[] screens = w.Screens.All;
+            var screens = w.Screens.All;
             var scaling = ((IRenderRoot)w).RenderScaling;
 
             Pen p = new Pen(Brushes.Black);

+ 3 - 1
samples/interop/Direct3DInteropSample/Program.cs

@@ -11,7 +11,9 @@ namespace Direct3DInteropSample
     {
         static void Main(string[] args)
         {
-            AppBuilder.Configure<App>().UseWin32(deferredRendering: false).UseDirect2D1().Start<MainWindow>();
+            AppBuilder.Configure<App>()
+                .With(new Win32PlatformOptions {UseDeferredRendering = false})
+                .UseWin32().UseDirect2D1().Start<MainWindow>();
         }
     }
 }

+ 20 - 1
src/Avalonia.Controls/AppBuilderBase.cs

@@ -15,6 +15,7 @@ namespace Avalonia.Controls
     public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
     {
         private static bool s_setupWasAlreadyCalled;
+        private Action _optionsInitializers;
 
         /// <summary>
         /// Gets or sets the <see cref="IRuntimePlatform"/> instance.
@@ -249,6 +250,24 @@ namespace Avalonia.Controls
             Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke();
         }
 
+        /// <summary>
+        /// Configures platform-specific options
+        /// </summary>
+        public TAppBuilder With<T>(T options)
+        {
+            _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind<T>().ToConstant(options); };
+            return Self;
+        }
+        
+        /// <summary>
+        /// Configures platform-specific options
+        /// </summary>
+        public TAppBuilder With<T>(Func<T> options)
+        {
+            _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind<T>().ToFunc(options); };
+            return Self;
+        }
+        
         /// <summary>
         /// Sets up the platform-speciic services for the <see cref="Application"/>.
         /// </summary>
@@ -280,7 +299,7 @@ namespace Avalonia.Controls
             }
 
             s_setupWasAlreadyCalled = true;
-
+            _optionsInitializers?.Invoke();
             RuntimePlatformServicesInitializer();
             WindowingSubsystemInitializer();
             RenderingSubsystemInitializer();

+ 5 - 3
src/Avalonia.Controls/Platform/IScreenImpl.cs

@@ -1,9 +1,11 @@
-namespace Avalonia.Platform
+using System.Collections.Generic;
+
+namespace Avalonia.Platform
 {
     public interface IScreenImpl
     {
         int ScreenCount { get; }
 
-        Screen[] AllScreens { get; }
+        IReadOnlyList<Screen> AllScreens { get; }
     }
-}
+}

+ 3 - 2
src/Avalonia.Controls/Screens.cs

@@ -1,4 +1,5 @@
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
 using Avalonia.Platform;
 using Avalonia.Utilities;
 using Avalonia.VisualTree;
@@ -10,7 +11,7 @@ namespace Avalonia.Controls
         private readonly IScreenImpl _iScreenImpl;
 
         public int ScreenCount => _iScreenImpl.ScreenCount;
-        public Screen[] All => _iScreenImpl?.AllScreens;
+        public IReadOnlyList<Screen> All => _iScreenImpl?.AllScreens;
         public Screen Primary => All.FirstOrDefault(x => x.Primary);
 
         public Screens(IScreenImpl iScreenImpl)

+ 2 - 2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -156,7 +156,7 @@ namespace Avalonia.DesignerSupport.Remote
     {
         public int ScreenCount => 1;
 
-        public Screen[] AllScreens { get; } =
-            {new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true)};
+        public IReadOnlyList<Screen> AllScreens { get; } =
+            new Screen[] { new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
     }
 }

+ 30 - 35
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<AvaloniaNativeOptions> 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<AvaloniaNativeOptions> 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<CreateAvaloniaNativeDelegate>(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<CreateAvaloniaNativeDelegate>(proc);
 
-            Initialize(d(), configure);
-        }
 
-        public static void Initialize(Action<AvaloniaNativeOptions> 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<AvaloniaNativeOptions> 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<MacOSPlatformOptions>();
+                if (macOpts != null)
+                    _factory.MacOptions.ShowInDock = macOpts.ShowInDock ? 1 : 0;
+            }
 
             AvaloniaLocator.CurrentMutable
-                .Bind<IPlatformThreadingInterface>().ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
+                .Bind<IPlatformThreadingInterface>()
+                .ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
                 .Bind<IStandardCursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
                 .Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
                 .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
@@ -76,13 +85,13 @@ namespace Avalonia.Native
                 .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
                 .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
                 .Bind<IWindowingPlatformGlFeature>().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
-                .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
-                .Bind<AvaloniaNativeOptions>().ToConstant(opts);
+                .Bind<PlatformHotkeyConfiguration>()
+                .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);
-        }
-
-    }
 }

+ 19 - 13
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@@ -9,21 +9,27 @@ namespace Avalonia
 {
     public static class AvaloniaNativePlatformExtensions
     {
-        public static T UseAvaloniaNative<T>(this T builder,
-                                             string libraryPath = null,
-                                             Action<AvaloniaNativeOptions> configure = null)
-                                             where T : AppBuilderBase<T>, new()
+        public static T UseAvaloniaNative<T>(this T builder)
+            where T : AppBuilderBase<T>, new()
         {
-            if (libraryPath == null)
-            {
-                builder.UseWindowingSubsystem(() => AvaloniaNativePlatform.Initialize(configure));
-            }
-            else
-            {
-                builder.UseWindowingSubsystem(() => AvaloniaNativePlatform.Initialize(libraryPath, configure));
-            }
-
+            builder.UseWindowingSubsystem(() =>
+                AvaloniaNativePlatform.Initialize(
+                    AvaloniaLocator.Current.GetService<AvaloniaNativePlatformOptions>() ??
+                    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;
+    }
 }

+ 1 - 1
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))
             {

+ 2 - 1
src/Avalonia.Native/ScreenImpl.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using Avalonia.Native.Interop;
 using Avalonia.Platform;
 
@@ -18,7 +19,7 @@ namespace Avalonia.Native
 
         public int ScreenCount => _native.GetScreenCount();
 
-        public Screen[] AllScreens
+        public IReadOnlyList<Screen> AllScreens
         {
             get
             {

+ 1 - 1
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))
             {

+ 1 - 3
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<AvaloniaNativeOptions>();
-
             _gpu = opts.UseGpu;
             _deferredRendering = opts.UseDeferredRendering;
 

+ 4 - 2
src/Avalonia.X11/X11Platform.cs

@@ -94,9 +94,11 @@ namespace Avalonia
     }
     public static class AvaloniaX11PlatformExtensions
     {
-        public static T UseX11<T>(this T builder, X11PlatformOptions options = null) where T : AppBuilderBase<T>, new()
+        public static T UseX11<T>(this T builder) where T : AppBuilderBase<T>, new()
         {
-            builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions()));
+            builder.UseWindowingSubsystem(() =>
+                new AvaloniaX11Platform().Initialize(AvaloniaLocator.Current.GetService<X11PlatformOptions>() ??
+                                                     new X11PlatformOptions()));
             return builder;
         }
 

+ 1 - 1
src/Avalonia.X11/X11Screens.cs

@@ -156,7 +156,7 @@ namespace Avalonia.X11
 
         public int ScreenCount => _impl.Screens.Length;
 
-        public Screen[] AllScreens =>
+        public IReadOnlyList<Screen> AllScreens =>
             _impl.Screens.Select(s => new Screen(s.Bounds, s.WorkingArea, s.Primary)).ToArray();
     }
 

+ 3 - 2
src/Gtk/Avalonia.Gtk3/ScreenImpl.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using Avalonia.Gtk3.Interop;
 using Avalonia.Platform;
 
@@ -8,11 +9,11 @@ namespace Avalonia.Gtk3
     {
         public int ScreenCount
         {
-            get => AllScreens.Length;
+            get => _allScreens.Length;
         }
         
         private Screen[] _allScreens;
-        public Screen[] AllScreens
+        public IReadOnlyList<Screen> AllScreens
         {
             get
             {

+ 22 - 2
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@@ -23,10 +23,15 @@ namespace Avalonia.Data
         public IList<IBinding> Bindings { get; set; } = new List<IBinding>();
 
         /// <summary>
-        /// Gets or sets the <see cref="IValueConverter"/> to use.
+        /// Gets or sets the <see cref="IMultiValueConverter"/> to use.
         /// </summary>
         public IMultiValueConverter Converter { get; set; }
 
+        /// <summary>
+        /// Gets or sets a parameter to pass to <see cref="Converter"/>.
+        /// </summary>
+        public object ConverterParameter { get; set; }
+
         /// <summary>
         /// Gets or sets the value to use when the binding is unable to produce a value.
         /// </summary>
@@ -47,6 +52,11 @@ namespace Avalonia.Data
         /// </summary>
         public RelativeSource RelativeSource { get; set; }
 
+        /// <summary>
+        /// Gets or sets the string format.
+        /// </summary>
+        public string StringFormat { get; set; }
+
         /// <inheritdoc/>
         public InstancedBinding Initiate(
             IAvaloniaObject target,
@@ -79,13 +89,23 @@ namespace Avalonia.Data
 
         private object ConvertValue(IList<object> values, Type targetType)
         {
-            var converted = Converter.Convert(values, targetType, null, 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;
         }
     }

+ 2 - 1
src/Windows/Avalonia.Win32/ScreenImpl.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using Avalonia.Platform;
 using static Avalonia.Win32.Interop.UnmanagedMethods;
 
@@ -15,7 +16,7 @@ namespace Avalonia.Win32
         }
 
         private Screen[] _allScreens;
-        public  Screen[] AllScreens
+        public IReadOnlyList<Screen> AllScreens
         {
             get
             {

+ 13 - 7
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -26,15 +26,21 @@ namespace Avalonia
     public static class Win32ApplicationExtensions
     {
         public static T UseWin32<T>(
-            this T builder,
-            bool deferredRendering = true, bool allowEgl = false) 
+            this T builder) 
                 where T : AppBuilderBase<T>, new()
         {
             return builder.UseWindowingSubsystem(
-                () => Win32.Win32Platform.Initialize(deferredRendering, allowEgl),
+                () => Win32.Win32Platform.Initialize(
+                    AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? 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<IClipboard>().ToSingleton<ClipboardImpl>()
@@ -80,9 +86,9 @@ namespace Avalonia.Win32
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
                 .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance);
-            if (allowEgl)
+            if (options.AllowEglInitialization)
                 Win32GlManager.Initialize();
-            UseDeferredRendering = deferredRendering;
+            UseDeferredRendering = options.UseDeferredRendering;
             _uiThread = Thread.CurrentThread;
 
             if (OleContext.Current != null)

+ 81 - 0
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<object> values, Type targetType, object parameter, CultureInfo culture)
+            {
+                return values.OfType<double>().Sum();
+            }
+        }
+
+        private class Class1
+        {
+            public double Foo { get; set; } = 1;
+            public double Bar { get; set; } = 2;
+        }
+    }
+}