committed by
GitHub
47 changed files with 1565 additions and 133 deletions
@ -1,6 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="SkiaSharp" Version="1.68.0" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,6 +1,5 @@ |
|||
copy ..\samples\ControlCatalog.Desktop\bin\Debug\net461\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\net461\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Gtk3.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\ |
|||
|
|||
@ -0,0 +1,72 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Avalonia.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// The `:not()` style selector.
|
|||
/// </summary>
|
|||
internal class NotSelector : Selector |
|||
{ |
|||
private readonly Selector _previous; |
|||
private readonly Selector _argument; |
|||
private string _selectorString; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NotSelector"/> class.
|
|||
/// </summary>
|
|||
/// <param name="previous">The previous selector.</param>
|
|||
/// <param name="argument">The selector to be not-ed.</param>
|
|||
public NotSelector(Selector previous, Selector argument) |
|||
{ |
|||
_previous = previous; |
|||
_argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument."); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool InTemplate => _argument.InTemplate; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool IsCombinator => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Type TargetType => _previous?.TargetType; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
if (_selectorString == null) |
|||
{ |
|||
_selectorString = ":not(" + _argument.ToString() + ")"; |
|||
} |
|||
|
|||
return _selectorString; |
|||
} |
|||
|
|||
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) |
|||
{ |
|||
var innerResult = _argument.Match(control, subscribe); |
|||
|
|||
switch (innerResult.Result) |
|||
{ |
|||
case SelectorMatchResult.AlwaysThisInstance: |
|||
return SelectorMatch.NeverThisInstance; |
|||
case SelectorMatchResult.AlwaysThisType: |
|||
return SelectorMatch.NeverThisType; |
|||
case SelectorMatchResult.NeverThisInstance: |
|||
return SelectorMatch.AlwaysThisInstance; |
|||
case SelectorMatchResult.NeverThisType: |
|||
return SelectorMatch.AlwaysThisType; |
|||
case SelectorMatchResult.Sometimes: |
|||
return new SelectorMatch(innerResult.Activator.Select(x => !x)); |
|||
default: |
|||
throw new InvalidOperationException("Invalid SelectorMatchResult."); |
|||
} |
|||
} |
|||
|
|||
protected override Selector MovePrevious() => _previous; |
|||
} |
|||
} |
|||
@ -0,0 +1,263 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform.Interop; |
|||
// ReSharper disable IdentifierTypo
|
|||
namespace Avalonia.X11.NativeDialogs |
|||
{ |
|||
|
|||
static unsafe class Glib |
|||
{ |
|||
private const string GlibName = "libglib-2.0.so.0"; |
|||
private const string GObjectName = "libgobject-2.0.so.0"; |
|||
|
|||
[DllImport(GlibName)] |
|||
public static extern void g_slist_free(GSList* data); |
|||
|
|||
[DllImport(GObjectName)] |
|||
private static extern void g_object_ref(IntPtr instance); |
|||
|
|||
[DllImport(GObjectName)] |
|||
private static extern ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal, |
|||
IntPtr handler, IntPtr userData, int flags); |
|||
|
|||
[DllImport(GObjectName)] |
|||
private static extern void g_object_unref(IntPtr instance); |
|||
|
|||
[DllImport(GObjectName)] |
|||
private static extern ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); |
|||
|
|||
private delegate bool timeout_callback(IntPtr data); |
|||
|
|||
[DllImport(GlibName)] |
|||
private static extern ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, |
|||
IntPtr destroy); |
|||
|
|||
|
|||
class ConnectedSignal : IDisposable |
|||
{ |
|||
private readonly IntPtr _instance; |
|||
private GCHandle _handle; |
|||
private readonly ulong _id; |
|||
|
|||
public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id) |
|||
{ |
|||
_instance = instance; |
|||
g_object_ref(instance); |
|||
_handle = handle; |
|||
_id = id; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_handle.IsAllocated) |
|||
{ |
|||
g_signal_handler_disconnect(_instance, _id); |
|||
g_object_unref(_instance); |
|||
_handle.Free(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static IDisposable ConnectSignal<T>(IntPtr obj, string name, T handler) |
|||
{ |
|||
var handle = GCHandle.Alloc(handler); |
|||
var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); |
|||
using (var utf = new Utf8Buffer(name)) |
|||
{ |
|||
var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0); |
|||
if (id == 0) |
|||
throw new ArgumentException("Unable to connect to signal " + name); |
|||
return new ConnectedSignal(obj, handle, id); |
|||
} |
|||
} |
|||
|
|||
|
|||
static bool TimeoutHandler(IntPtr data) |
|||
{ |
|||
var handle = GCHandle.FromIntPtr(data); |
|||
var cb = (Func<bool>)handle.Target; |
|||
if (!cb()) |
|||
{ |
|||
handle.Free(); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private static readonly timeout_callback s_pinnedHandler; |
|||
|
|||
static Glib() |
|||
{ |
|||
s_pinnedHandler = TimeoutHandler; |
|||
} |
|||
|
|||
static void AddTimeout(int priority, uint interval, Func<bool> callback) |
|||
{ |
|||
var handle = GCHandle.Alloc(callback); |
|||
g_timeout_add_full(priority, interval, s_pinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero); |
|||
} |
|||
|
|||
public static Task<T> RunOnGlibThread<T>(Func<T> action) |
|||
{ |
|||
var tcs = new TaskCompletionSource<T>(); |
|||
AddTimeout(0, 0, () => |
|||
{ |
|||
|
|||
try |
|||
{ |
|||
tcs.SetResult(action()); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
tcs.TrySetException(e); |
|||
} |
|||
|
|||
return false; |
|||
}); |
|||
return tcs.Task; |
|||
} |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
unsafe struct GSList |
|||
{ |
|||
public readonly IntPtr Data; |
|||
public readonly GSList* Next; |
|||
} |
|||
|
|||
enum GtkFileChooserAction |
|||
{ |
|||
Open, |
|||
Save, |
|||
SelectFolder, |
|||
} |
|||
|
|||
// ReSharper disable UnusedMember.Global
|
|||
enum GtkResponseType |
|||
{ |
|||
Help = -11, |
|||
Apply = -10, |
|||
No = -9, |
|||
Yes = -8, |
|||
Close = -7, |
|||
Cancel = -6, |
|||
Ok = -5, |
|||
DeleteEvent = -4, |
|||
Accept = -3, |
|||
Reject = -2, |
|||
None = -1, |
|||
} |
|||
// ReSharper restore UnusedMember.Global
|
|||
|
|||
static unsafe class Gtk |
|||
{ |
|||
private static IntPtr s_display; |
|||
private const string GdkName = "libgdk-3.so.0"; |
|||
private const string GtkName = "libgtk-3.so.0"; |
|||
|
|||
[DllImport(GtkName)] |
|||
static extern void gtk_main_iteration(); |
|||
|
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern void gtk_window_set_modal(IntPtr window, bool modal); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern void gtk_window_present(IntPtr gtkWindow); |
|||
|
|||
|
|||
public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData); |
|||
|
|||
public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern IntPtr gtk_file_chooser_dialog_new(Utf8Buffer title, IntPtr parent, |
|||
GtkFileChooserAction action, IntPtr ignore); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern void gtk_file_chooser_set_select_multiple(IntPtr chooser, bool allow); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern void |
|||
gtk_dialog_add_button(IntPtr raw, Utf8Buffer button_text, GtkResponseType response_id); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern GSList* gtk_file_chooser_get_filenames(IntPtr chooser); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern void gtk_widget_realize(IntPtr gtkWidget); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget); |
|||
|
|||
[DllImport(GtkName)] |
|||
public static extern void gtk_widget_hide(IntPtr gtkWidget); |
|||
|
|||
[DllImport(GtkName)] |
|||
static extern bool gtk_init_check(int argc, IntPtr argv); |
|||
|
|||
[DllImport(GdkName)] |
|||
static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid); |
|||
|
|||
[DllImport(GdkName)] |
|||
static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends); |
|||
|
|||
[DllImport(GdkName)] |
|||
static extern IntPtr gdk_display_get_default(); |
|||
|
|||
[DllImport(GtkName)] |
|||
static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags); |
|||
|
|||
[DllImport(GdkName)] |
|||
public static extern void gdk_window_set_transient_for(IntPtr window, IntPtr parent); |
|||
|
|||
public static IntPtr GetForeignWindow(IntPtr xid) => gdk_x11_window_foreign_new_for_display(s_display, xid); |
|||
|
|||
public static Task<bool> StartGtk() |
|||
{ |
|||
var tcs = new TaskCompletionSource<bool>(); |
|||
new Thread(() => |
|||
{ |
|||
try |
|||
{ |
|||
using (var backends = new Utf8Buffer("x11")) |
|||
gdk_set_allowed_backends(backends); |
|||
} |
|||
catch |
|||
{ |
|||
//Ignore
|
|||
} |
|||
|
|||
Environment.SetEnvironmentVariable("WAYLAND_DISPLAY", |
|||
"/proc/fake-display-to-prevent-wayland-initialization-by-gtk3"); |
|||
|
|||
if (!gtk_init_check(0, IntPtr.Zero)) |
|||
{ |
|||
tcs.SetResult(false); |
|||
return; |
|||
} |
|||
|
|||
IntPtr app; |
|||
using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid():N}")) |
|||
app = gtk_application_new(utf, 0); |
|||
if (app == IntPtr.Zero) |
|||
{ |
|||
tcs.SetResult(false); |
|||
return; |
|||
} |
|||
|
|||
s_display = gdk_display_get_default(); |
|||
tcs.SetResult(true); |
|||
while (true) |
|||
gtk_main_iteration(); |
|||
}) {Name = "GTK3THREAD", IsBackground = true}.Start(); |
|||
return tcs.Task; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Platform.Interop; |
|||
using static Avalonia.X11.NativeDialogs.Glib; |
|||
using static Avalonia.X11.NativeDialogs.Gtk; |
|||
// ReSharper disable AccessToModifiedClosure
|
|||
namespace Avalonia.X11.NativeDialogs |
|||
{ |
|||
class GtkSystemDialog : ISystemDialogImpl |
|||
{ |
|||
private Task<bool> _initialized; |
|||
private unsafe Task<string[]> ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action, |
|||
bool multiSelect, string initialFileName) |
|||
{ |
|||
IntPtr dlg; |
|||
using (var name = new Utf8Buffer(title)) |
|||
dlg = gtk_file_chooser_dialog_new(name, IntPtr.Zero, action, IntPtr.Zero); |
|||
UpdateParent(dlg, parent); |
|||
if (multiSelect) |
|||
gtk_file_chooser_set_select_multiple(dlg, true); |
|||
|
|||
gtk_window_set_modal(dlg, true); |
|||
var tcs = new TaskCompletionSource<string[]>(); |
|||
List<IDisposable> disposables = null; |
|||
|
|||
void Dispose() |
|||
{ |
|||
// ReSharper disable once PossibleNullReferenceException
|
|||
foreach (var d in disposables) d.Dispose(); |
|||
disposables.Clear(); |
|||
} |
|||
|
|||
disposables = new List<IDisposable> |
|||
{ |
|||
ConnectSignal<signal_generic>(dlg, "close", delegate |
|||
{ |
|||
tcs.TrySetResult(null); |
|||
Dispose(); |
|||
return false; |
|||
}), |
|||
ConnectSignal<signal_dialog_response>(dlg, "response", (_, resp, __) => |
|||
{ |
|||
string[] result = null; |
|||
if (resp == GtkResponseType.Accept) |
|||
{ |
|||
var resultList = new List<string>(); |
|||
var gs = gtk_file_chooser_get_filenames(dlg); |
|||
var cgs = gs; |
|||
while (cgs != null) |
|||
{ |
|||
if (cgs->Data != IntPtr.Zero) |
|||
resultList.Add(Utf8Buffer.StringFromPtr(cgs->Data)); |
|||
cgs = cgs->Next; |
|||
} |
|||
g_slist_free(gs); |
|||
result = resultList.ToArray(); |
|||
} |
|||
|
|||
gtk_widget_hide(dlg); |
|||
Dispose(); |
|||
tcs.TrySetResult(result); |
|||
return false; |
|||
}) |
|||
}; |
|||
using (var open = new Utf8Buffer("Open")) |
|||
gtk_dialog_add_button(dlg, open, GtkResponseType.Accept); |
|||
using (var open = new Utf8Buffer("Cancel")) |
|||
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel); |
|||
if (initialFileName != null) |
|||
using (var fn = new Utf8Buffer(initialFileName)) |
|||
gtk_file_chooser_set_filename(dlg, fn); |
|||
gtk_window_present(dlg); |
|||
return tcs.Task; |
|||
} |
|||
|
|||
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) |
|||
{ |
|||
await EnsureInitialized(); |
|||
return await await RunOnGlibThread( |
|||
() => ShowDialog(dialog.Title, parent, |
|||
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, |
|||
(dialog as OpenFileDialog)?.AllowMultiple ?? false, |
|||
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory, |
|||
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName))); |
|||
} |
|||
|
|||
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) |
|||
{ |
|||
await EnsureInitialized(); |
|||
return await await RunOnGlibThread(async () => |
|||
{ |
|||
var res = await ShowDialog(dialog.Title, parent, |
|||
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory); |
|||
return res?.FirstOrDefault(); |
|||
}); |
|||
} |
|||
|
|||
async Task EnsureInitialized() |
|||
{ |
|||
if (_initialized == null) _initialized = StartGtk(); |
|||
|
|||
if (!(await _initialized)) |
|||
throw new Exception("Unable to initialize GTK on separate thread"); |
|||
} |
|||
|
|||
void UpdateParent(IntPtr chooser, IWindowImpl parentWindow) |
|||
{ |
|||
var xid = parentWindow.Handle.Handle; |
|||
gtk_widget_realize(chooser); |
|||
var window = gtk_widget_get_window(chooser); |
|||
var parent = GetForeignWindow(xid); |
|||
if (window != IntPtr.Zero && parent != IntPtr.Zero) |
|||
gdk_window_set_transient_for(window, parent); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 8abbe09592668efb573ac4d5548ba2d7e464ba78 |
|||
Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975 |
|||
@ -0,0 +1,62 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.DesignerSupport.Tests |
|||
{ |
|||
static class Helpers |
|||
{ |
|||
public static void StructDiff(object parsed, object expected) => StructDiff(parsed, expected, "{root}"); |
|||
|
|||
static void StructDiff(object parsed, object expected, string path) |
|||
{ |
|||
if (parsed == null && expected == null) |
|||
return; |
|||
if ((parsed == null && expected != null) || (parsed != null && expected == null)) |
|||
throw new Exception( |
|||
$"{path}: Null mismatch: {(parsed == null ? "null" : "not-null")} {(expected == null ? "null" : "not-null")}"); |
|||
|
|||
if (parsed.GetType() != expected.GetType()) |
|||
throw new Exception($"{path}: Type mismatch: {parsed.GetType()} {expected.GetType()}"); |
|||
|
|||
if (parsed is string || parsed.GetType().IsPrimitive) |
|||
{ |
|||
if (!parsed.Equals(expected)) |
|||
throw new Exception($"{path}: Not equal {parsed} {expected}"); |
|||
} |
|||
else if (parsed is IDictionary dic) |
|||
{ |
|||
var dic2 = (IDictionary) expected; |
|||
if (dic.Count != dic2.Count) |
|||
throw new Exception($"{path}: Dictionary count mismatch: {dic.Count} {dic2.Count}"); |
|||
|
|||
foreach (var k in dic.Keys.Cast<object>().OrderBy(o => o.ToString())) |
|||
{ |
|||
var v1 = dic[k]; |
|||
var v2 = dic2[k]; |
|||
StructDiff(v1, v2, path + "['" + k + "']"); |
|||
} |
|||
} |
|||
else if (parsed is IList col) |
|||
{ |
|||
var col2 = (IList) expected; |
|||
if (col.Count != col2.Count) |
|||
throw new Exception($"{path}: Collection count mismatch: {col.Count} {col2.Count}"); |
|||
for (var c = 0; c < col.Count; c++) |
|||
StructDiff(col[c], col2[c], path + "[" + c + "]"); |
|||
} |
|||
else |
|||
{ |
|||
foreach (var prop in parsed.GetType().GetProperties() |
|||
.Where(p => p.GetMethod != null && p.GetMethod.IsPublic)) |
|||
{ |
|||
StructDiff(prop.GetValue(parsed), prop.GetValue(expected), path + "." + prop.Name); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Net.Sockets; |
|||
using System.Reflection; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Remote.Protocol; |
|||
using Avalonia.Remote.Protocol.Viewport; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.DesignerSupport.Tests |
|||
{ |
|||
public class RemoteProtocolTests : IDisposable |
|||
{ |
|||
private readonly List<IDisposable> _disposables = new List<IDisposable>(); |
|||
private IAvaloniaRemoteTransportConnection _server; |
|||
private IAvaloniaRemoteTransportConnection _client; |
|||
private BlockingCollection<object> _serverMessages = new BlockingCollection<object>(); |
|||
private BlockingCollection<object> _clientMessages = new BlockingCollection<object>(); |
|||
private SynchronizationContext _originalContext; |
|||
|
|||
|
|||
class DisabledSyncContext : SynchronizationContext |
|||
{ |
|||
public override void Post(SendOrPostCallback d, object state) |
|||
{ |
|||
throw new InvalidCastException("Not allowed"); |
|||
} |
|||
|
|||
public override void Send(SendOrPostCallback d, object state) |
|||
{ |
|||
throw new InvalidCastException("Not allowed"); |
|||
} |
|||
} |
|||
|
|||
void Init(IMessageTypeResolver clientResolver = null, IMessageTypeResolver serverResolver = null) |
|||
{ |
|||
_originalContext = SynchronizationContext.Current; |
|||
SynchronizationContext.SetSynchronizationContext(new DisabledSyncContext()); |
|||
var clientTransport = new BsonTcpTransport(clientResolver ?? new DefaultMessageTypeResolver()); |
|||
var serverTransport = new BsonTcpTransport(serverResolver ?? new DefaultMessageTypeResolver()); |
|||
|
|||
var tcpListener = new TcpListener(IPAddress.Loopback, 0); |
|||
tcpListener.Start(); |
|||
var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port; |
|||
tcpListener.Stop(); |
|||
|
|||
var tcs = new TaskCompletionSource<int>(); |
|||
serverTransport.Listen(IPAddress.Loopback, port, connected => |
|||
{ |
|||
_server = connected; |
|||
tcs.SetResult(0); |
|||
}); |
|||
_client = clientTransport.Connect(IPAddress.Loopback, port).Result; |
|||
_disposables.Add(_client); |
|||
_client.OnMessage += (_, m) => _clientMessages.Add(m); |
|||
tcs.Task.Wait(); |
|||
_disposables.Add(_server); |
|||
_server.OnMessage += (_, m) => _serverMessages.Add(m); |
|||
|
|||
} |
|||
|
|||
object TakeServer() |
|||
{ |
|||
var src = new CancellationTokenSource(200); |
|||
try |
|||
{ |
|||
return _serverMessages.Take(src.Token); |
|||
} |
|||
finally |
|||
{ |
|||
src.Dispose(); |
|||
} |
|||
|
|||
} |
|||
|
|||
[Fact] |
|||
void EntitiesAreProperlySerializedAndDeserialized() |
|||
{ |
|||
Init(); |
|||
var rnd = new Random(); |
|||
_server.OnMessage += (_, message) => { }; |
|||
|
|||
|
|||
object GetRandomValue(Type t, string pathInfo) |
|||
{ |
|||
if (t.IsArray) |
|||
{ |
|||
var arr = Array.CreateInstance(t.GetElementType(), 1); |
|||
((IList)arr)[0] = GetRandomValue(t.GetElementType(), pathInfo); |
|||
return arr; |
|||
} |
|||
|
|||
if (t == typeof(bool)) |
|||
return true; |
|||
if (t == typeof(int) || t == typeof(long)) |
|||
return rnd.Next(); |
|||
if (t == typeof(byte)) |
|||
return (byte)rnd.Next(255); |
|||
if (t == typeof(double)) |
|||
return rnd.NextDouble(); |
|||
if (t.IsEnum) |
|||
return ((IList)Enum.GetValues(t)).Cast<object>().Last(); |
|||
if (t == typeof(string)) |
|||
return Guid.NewGuid().ToString(); |
|||
if (t == typeof(Guid)) |
|||
return Guid.NewGuid(); |
|||
throw new Exception($"Doesn't know how to fabricate a random value for {t}, path {pathInfo}"); |
|||
} |
|||
|
|||
foreach (var t in typeof(MeasureViewportMessage).Assembly.GetTypes().Where(t => |
|||
t.GetCustomAttribute(typeof(AvaloniaRemoteMessageGuidAttribute)) != null)) |
|||
{ |
|||
var o = Activator.CreateInstance(t); |
|||
foreach (var p in t.GetProperties()) |
|||
p.SetValue(o, GetRandomValue(p.PropertyType, $"{t.FullName}.{p.Name}")); |
|||
|
|||
_client.Send(o).Wait(200); |
|||
var received = TakeServer(); |
|||
Helpers.StructDiff(received, o); |
|||
|
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
[Fact] |
|||
void RemoteProtocolShouldBeBackwardsCompatible() |
|||
{ |
|||
Init(new DefaultMessageTypeResolver(typeof(ExtendedMeasureViewportMessage).Assembly)); |
|||
_client.Send(new ExtendedMeasureViewportMessage() |
|||
{ |
|||
Width = 100, Height = 200, SomeNewProperty = 300, |
|||
SomeArrayProperty = new[]{1,2,3}, |
|||
SubObjectProperty = new ExtendedMeasureViewportMessage.SubObject() |
|||
{ |
|||
Foo = 543 |
|||
} |
|||
}); |
|||
var received = (MeasureViewportMessage)TakeServer(); |
|||
Assert.Equal(100, received.Width); |
|||
Assert.Equal(200, received.Height); |
|||
|
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_disposables.ForEach(d => d.Dispose()); |
|||
SynchronizationContext.SetSynchronizationContext(_originalContext); |
|||
} |
|||
} |
|||
|
|||
[AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")] |
|||
public class ExtendedMeasureViewportMessage |
|||
{ |
|||
public double Width { get; set; } |
|||
|
|||
public int SomeNewProperty { get; set; } |
|||
public int[] SomeArrayProperty { get; set; } |
|||
public class SubObject |
|||
{ |
|||
public int Foo { get; set; } |
|||
} |
|||
public SubObject SubObjectProperty { get; set; } |
|||
public double Height { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Reactive.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Styling.UnitTests |
|||
{ |
|||
public class SelectorTests_Not |
|||
{ |
|||
[Fact] |
|||
public void Not_Selector_Should_Have_Correct_String_Representation() |
|||
{ |
|||
var target = default(Selector).Not(x => x.Class("foo")); |
|||
|
|||
Assert.Equal(":not(.foo)", target.ToString()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Not_OfType_Matches_Control_Of_Incorrect_Type() |
|||
{ |
|||
var control = new Control1(); |
|||
var target = default(Selector).Not(x => x.OfType<Control1>()); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Not_OfType_Doesnt_Match_Control_Of_Correct_Type() |
|||
{ |
|||
var control = new Control2(); |
|||
var target = default(Selector).Not(x => x.OfType<Control1>()); |
|||
|
|||
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Not_Class_Doesnt_Match_Control_With_Class() |
|||
{ |
|||
var control = new Control1 |
|||
{ |
|||
Classes = new Classes { "foo" }, |
|||
}; |
|||
|
|||
var target = default(Selector).Not(x => x.Class("foo")); |
|||
var match = target.Match(control); |
|||
|
|||
Assert.Equal(SelectorMatchResult.Sometimes, match.Result); |
|||
Assert.False(await match.Activator.Take(1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Not_Class_Matches_Control_Without_Class() |
|||
{ |
|||
var control = new Control1 |
|||
{ |
|||
Classes = new Classes { "bar" }, |
|||
}; |
|||
|
|||
var target = default(Selector).Not(x => x.Class("foo")); |
|||
var match = target.Match(control); |
|||
|
|||
Assert.Equal(SelectorMatchResult.Sometimes, match.Result); |
|||
Assert.True(await match.Activator.Take(1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task OfType_Not_Class_Matches_Control_Without_Class() |
|||
{ |
|||
var control = new Control1 |
|||
{ |
|||
Classes = new Classes { "bar" }, |
|||
}; |
|||
|
|||
var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo")); |
|||
var match = target.Match(control); |
|||
|
|||
Assert.Equal(SelectorMatchResult.Sometimes, match.Result); |
|||
Assert.True(await match.Activator.Take(1)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void OfType_Not_Class_Doesnt_Match_Control_Of_Wrong_Type() |
|||
{ |
|||
var control = new Control2 |
|||
{ |
|||
Classes = new Classes { "foo" }, |
|||
}; |
|||
|
|||
var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo")); |
|||
var match = target.Match(control); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Returns_Correct_TargetType() |
|||
{ |
|||
var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo")); |
|||
|
|||
Assert.Equal(typeof(Control1), target.TargetType); |
|||
} |
|||
|
|||
public class Control1 : TestControlBase |
|||
{ |
|||
} |
|||
|
|||
public class Control2 : TestControlBase |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue