From 16ea1f37621b0400c96e5f4085195ce4a4573b53 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 7 Oct 2018 22:05:00 +0300 Subject: [PATCH 001/207] [X11] Platform threading interface and platform sanity checks --- Avalonia.sln | 54 ++++ samples/PlatformSanityChecks/App.xaml | 6 + samples/PlatformSanityChecks/App.xaml.cs | 13 + .../PlatformSanityChecks.csproj | 24 ++ samples/PlatformSanityChecks/Program.cs | 140 ++++++++++ src/Avalonia.X11/Avalonia.X11.csproj | 12 + src/Avalonia.X11/X11Exception.cs | 12 + src/Avalonia.X11/X11Platform.cs | 39 +++ src/Avalonia.X11/X11PlatformThreading.cs | 251 ++++++++++++++++++ src/Avalonia.X11/XLib.cs | 105 ++++++++ 10 files changed, 656 insertions(+) create mode 100644 samples/PlatformSanityChecks/App.xaml create mode 100644 samples/PlatformSanityChecks/App.xaml.cs create mode 100644 samples/PlatformSanityChecks/PlatformSanityChecks.csproj create mode 100644 samples/PlatformSanityChecks/Program.cs create mode 100644 src/Avalonia.X11/Avalonia.X11.csproj create mode 100644 src/Avalonia.X11/X11Exception.cs create mode 100644 src/Avalonia.X11/X11Platform.cs create mode 100644 src/Avalonia.X11/X11PlatformThreading.cs create mode 100644 src/Avalonia.X11/XLib.cs diff --git a/Avalonia.sln b/Avalonia.sln index f71a94888d..388dbb6e6a 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -188,6 +188,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.OpenGL", "src\Avalonia.OpenGL\Avalonia.OpenGL.csproj", "{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{212D02D5-C873-469A-8E78-4A6350EC4A1A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{8B5768BB-71F9-4E23-89B5-DDBA6458B856}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -1689,6 +1693,54 @@ Global {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.Build.0 = Release|Any CPU {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhone.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhone.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|Any CPU.Build.0 = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhone.ActiveCfg = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhone.Build.0 = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhone.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhone.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|Any CPU.Build.0 = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhone.ActiveCfg = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhone.Build.0 = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1742,6 +1794,8 @@ Global {CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2} {4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {212D02D5-C873-469A-8E78-4A6350EC4A1A} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} + {8B5768BB-71F9-4E23-89B5-DDBA6458B856} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/samples/PlatformSanityChecks/App.xaml b/samples/PlatformSanityChecks/App.xaml new file mode 100644 index 0000000000..25bab6ae35 --- /dev/null +++ b/samples/PlatformSanityChecks/App.xaml @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs new file mode 100644 index 0000000000..508fc1e34b --- /dev/null +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Markup.Xaml; + +namespace PlatformSanityChecks +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj new file mode 100644 index 0000000000..00b5b10106 --- /dev/null +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -0,0 +1,24 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + diff --git a/samples/PlatformSanityChecks/Program.cs b/samples/PlatformSanityChecks/Program.cs new file mode 100644 index 0000000000..8a3aa82981 --- /dev/null +++ b/samples/PlatformSanityChecks/Program.cs @@ -0,0 +1,140 @@ +using System; +using System.Diagnostics; +using System.Reactive.Disposables; +using System.Runtime.CompilerServices; +using System.Threading; +using Avalonia; +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.X11; + +namespace PlatformSanityChecks +{ + public class Program + { + static Thread UiThread; + + static void Main(string[] args) + { + UiThread = Thread.CurrentThread; + AppBuilder.Configure().RuntimePlatformServicesInitializer(); + var app = new App(); + + new AvaloniaX11Platform().Initialize(); + + CheckPlatformThreading(); + + + + } + + static bool CheckAccess() => UiThread == Thread.CurrentThread; + + static void VerifyAccess() + { + if (!CheckAccess()) + Die("Call from invalid thread"); + } + + static Exception Die(string error) + { + Console.Error.WriteLine(error); + Console.Error.WriteLine(Environment.StackTrace); + Process.GetCurrentProcess().Kill(); + throw new Exception(error); + } + + static IDisposable Enter([CallerMemberName] string caller = null) + { + Console.WriteLine("Entering " + caller); + return Disposable.Create(() => { Console.WriteLine("Leaving " + caller); }); + } + + static void EnterLoop(Action cb, [CallerMemberName] string caller = null) + { + using (Enter(caller)) + { + var cts = new CancellationTokenSource(); + cb(cts); + Dispatcher.UIThread.MainLoop(cts.Token); + if (!cts.IsCancellationRequested) + Die("Unexpected loop exit"); + } + } + + static void CheckTimerOrdering() => EnterLoop(cts => + { + bool firstFired = false, secondFired = false; + DispatcherTimer.Run(() => + { + Console.WriteLine("Second tick"); + VerifyAccess(); + if (!firstFired) + throw Die("Invalid timer ordering"); + if (secondFired) + throw Die("Invocation of finished timer"); + secondFired = true; + cts.Cancel(); + return false; + }, TimeSpan.FromSeconds(2)); + DispatcherTimer.Run(() => + { + Console.WriteLine("First tick"); + VerifyAccess(); + if (secondFired) + throw Die("Invalid timer ordering"); + if (firstFired) + throw Die("Invocation of finished timer"); + firstFired = true; + return false; + }, TimeSpan.FromSeconds(1)); + }); + + static void CheckTimerTicking() => EnterLoop(cts => + { + int ticks = 0; + var st = Stopwatch.StartNew(); + DispatcherTimer.Run(() => + { + ticks++; + Console.WriteLine($"Tick {ticks} at {st.Elapsed}"); + if (ticks == 5) + { + if (st.Elapsed.TotalSeconds < 4.5) + Die("Timer is too fast"); + if (st.Elapsed.TotalSeconds > 6) + Die("Timer is too slow"); + cts.Cancel(); + return false; + } + + return true; + }, TimeSpan.FromSeconds(1)); + + + }); + + + static void CheckSignaling() => EnterLoop(cts => + { + ThreadPool.QueueUserWorkItem(_ => + { + Thread.Sleep(100); + Dispatcher.UIThread.Post(() => + { + VerifyAccess(); + cts.Cancel(); + }); + }); + }); + + static void CheckPlatformThreading() + { + CheckSignaling(); + CheckTimerOrdering(); + CheckTimerTicking(); + + } + + } +} diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj new file mode 100644 index 0000000000..a035febe5b --- /dev/null +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + true + + + + + + + diff --git a/src/Avalonia.X11/X11Exception.cs b/src/Avalonia.X11/X11Exception.cs new file mode 100644 index 0000000000..2ac5a31d9b --- /dev/null +++ b/src/Avalonia.X11/X11Exception.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.X11 +{ + public class X11Exception : Exception + { + public X11Exception(string message) : base(message) + { + + } + } +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs new file mode 100644 index 0000000000..c266433eef --- /dev/null +++ b/src/Avalonia.X11/X11Platform.cs @@ -0,0 +1,39 @@ +using System; +using Avalonia.Controls; +using Avalonia.Platform; +using Avalonia.X11; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + public class AvaloniaX11Platform + { + public void Initialize() + { + Display = XOpenDisplay(IntPtr.Zero); + DeferredDisplay = XOpenDisplay(IntPtr.Zero); + if (Display == IntPtr.Zero) + throw new Exception("XOpenDisplay failed"); + + + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(new X11PlatformThreading(Display)); + + } + + public IntPtr DeferredDisplay { get; set; } + public IntPtr Display { get; set; } + } +} + +namespace Avalonia +{ + public static class AvaloniaX11PlatformExtensions + { + public static T UseX11(this T builder) where T : AppBuilderBase, new() + { + builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); + return builder; + } + } + +} diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs new file mode 100644 index 0000000000..18b08085e3 --- /dev/null +++ b/src/Avalonia.X11/X11PlatformThreading.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Avalonia.Platform; +using Avalonia.Threading; +using static Avalonia.X11.XLib; + +namespace Avalonia.X11 +{ + public unsafe class X11PlatformThreading : IPlatformThreadingInterface + { + private readonly IntPtr _display; + private Thread _mainThread; + + [StructLayout(LayoutKind.Explicit)] + struct epoll_data + { + [FieldOffset(0)] + public IntPtr ptr; + [FieldOffset(0)] + public int fd; + [FieldOffset(0)] + public uint u32; + [FieldOffset(0)] + public ulong u64; + } + + private const int EPOLLIN = 1; + private const int EPOLL_CTL_ADD = 1; + private const int O_NONBLOCK = 2048; + + [StructLayout(LayoutKind.Sequential)] + struct epoll_event + { + public uint events; + public epoll_data data; + } + + [DllImport("libc")] + extern static int epoll_create1(int size); + + [DllImport("libc")] + extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event); + + [DllImport("libc")] + extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout); + + [DllImport("libc")] + extern static int pipe2(int* fds, int flags); + [DllImport("libc")] + extern static IntPtr write(int fd, void* buf, IntPtr count); + + [DllImport("libc")] + extern static IntPtr read(int fd, void* buf, IntPtr count); + + enum EventCodes + { + X11 = 1, + Signal =2 + } + + private int _sigread, _sigwrite; + private object _lock = new object(); + private bool _signaled; + private DispatcherPriority _signaledPriority; + private int _epoll; + private Stopwatch _clock = Stopwatch.StartNew(); + + class X11Timer : IDisposable + { + private readonly X11PlatformThreading _parent; + + public X11Timer(X11PlatformThreading parent, DispatcherPriority prio, TimeSpan interval, Action tick) + { + _parent = parent; + Priority = prio; + Tick = tick; + Interval = interval; + Reschedule(); + } + + public DispatcherPriority Priority { get; } + public TimeSpan NextTick { get; private set; } + public TimeSpan Interval { get; } + public Action Tick { get; } + public bool Disposed { get; private set; } + + public void Reschedule() + { + NextTick = _parent._clock.Elapsed + Interval; + } + + public void Dispose() + { + Disposed = true; + lock (_parent._lock) + _parent._timers.Remove(this); + } + } + + List _timers = new List(); + + public X11PlatformThreading(IntPtr display) + { + _display = display; + _mainThread = Thread.CurrentThread; + var fd = XLib.XConnectionNumber(display); + var ev = new epoll_event() + { + events = EPOLLIN, + data = {u32 = (int)EventCodes.X11} + }; + _epoll = epoll_create1(0); + if (_epoll == -1) + throw new X11Exception("epoll_create1 failed"); + + if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, ref ev) == -1) + throw new X11Exception("Unable to attach X11 connection handle to epoll"); + + var fds = stackalloc int[2]; + pipe2(fds, O_NONBLOCK); + _sigread = fds[0]; + _sigwrite = fds[1]; + + ev = new epoll_event + { + events = EPOLLIN, + data = {u32 = (int)EventCodes.Signal} + }; + if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1) + throw new X11Exception("Unable to attach signal pipe to epoll"); + } + + int TimerComparer(X11Timer t1, X11Timer t2) + { + return t2.Priority - t1.Priority; + } + + public void RunLoop(CancellationToken cancellationToken) + { + var readyTimers = new List(); + while (!cancellationToken.IsCancellationRequested) + { + var now = _clock.Elapsed; + TimeSpan? nextTick = null; + readyTimers.Clear(); + lock(_timers) + foreach (var t in _timers) + { + if (nextTick == null || t.NextTick < nextTick.Value) + nextTick = t.NextTick; + if (t.NextTick < now) + readyTimers.Add(t); + } + + readyTimers.Sort(TimerComparer); + + foreach (var t in readyTimers) + { + if (cancellationToken.IsCancellationRequested) + return; + t.Tick(); + if(!t.Disposed) + { + t.Reschedule(); + if (nextTick == null || t.NextTick < nextTick.Value) + nextTick = t.NextTick; + } + } + + if (cancellationToken.IsCancellationRequested) + return; + epoll_event ev; + var len = epoll_wait(_epoll, &ev, 1, + nextTick == null ? -1 : Math.Max(1, (int)(nextTick.Value - _clock.Elapsed).TotalMilliseconds)); + if (cancellationToken.IsCancellationRequested) + return; + if (len == 0) + { + // We handle timer-related stuff at the beginning of the loop + continue; + } + else + { + if (ev.data.u32 == (int)EventCodes.Signal) + { + int buf = 0; + while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0) + { + } + + DispatcherPriority prio; + lock (_lock) + { + _signaled = false; + prio = _signaledPriority; + _signaledPriority = DispatcherPriority.MinValue; + } + Signaled?.Invoke(prio); + } + else + { + while (XPending(_display)) + { + if (cancellationToken.IsCancellationRequested) + return; + XNextEvent(_display, out var xev); + } + } + } + } + } + + + + public void Signal(DispatcherPriority priority) + { + lock (_lock) + { + if (priority > _signaledPriority) + _signaledPriority = priority; + + if(_signaled) + return; + _signaled = true; + int buf = 0; + write(_sigwrite, &buf, new IntPtr(1)); + } + } + + public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread; + public event Action Signaled; + + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) + { + if (_mainThread != Thread.CurrentThread) + throw new InvalidOperationException("StartTimer can be only called from UI thread"); + if (interval <= TimeSpan.Zero) + throw new ArgumentException("Interval must be positive", nameof(interval)); + + // We assume that we are on the main thread and outside of epoll_wait, so there is no need for wakeup signal + + var timer = new X11Timer(this, priority, interval, tick); + lock(_timers) + _timers.Add(timer); + return timer; + } + } +} diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs new file mode 100644 index 0000000000..88d36091ca --- /dev/null +++ b/src/Avalonia.X11/XLib.cs @@ -0,0 +1,105 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.X11 +{ + public static class XLib + { + + [DllImport("libX11.so.6")] + public static extern IntPtr XInitThreads(); + + [DllImport("libX11.so.6")] + public static extern IntPtr XOpenDisplay(IntPtr name); + + [DllImport("libX11.so.6")] + public static extern int XConnectionNumber(IntPtr display); + + [DllImport("libX11.so.6")] + public static extern IntPtr XLockDisplay(IntPtr display); + + [DllImport("libX11.so.6")] + public static extern IntPtr XUnlockDisplay(IntPtr display); + + [DllImport("libX11.so.6")] + public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); + + [DllImport("libX11.so.6")] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); + + [DllImport("libX11.so.6")] + public static extern int XInitImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern int XDestroyImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern IntPtr XSetErrorHandler(XErrorHandler handler); + + [DllImport("libX11.so.6")] + public static extern int XSync(IntPtr display, bool discard); + + [DllImport("libX11.so.6")] + public static extern int XNextEvent(IntPtr display, out XEvent ev); + + [DllImport("libX11.so.6")] + public static extern bool XPending(IntPtr display); + + [StructLayout(LayoutKind.Sequential)] + public struct XAnyEvent + { + + public int Type; + public ulong Serial; /* # of last request processed by server */ + public bool send_event; /* true if this came from a SendEvent request */ + public IntPtr display; /* Display the event was read from */ + public IntPtr window; /* window on which event was requested in event mask */ + } + + [StructLayout(LayoutKind.Explicit)] + public unsafe struct XEvent + { + [FieldOffset(0)] public int Type; + [FieldOffset(0)] public XAnyEvent XAny; + [FieldOffset(0)] private fixed int pad[40]; + } + + public delegate int XErrorHandler(IntPtr display, ref XErrorEvent error); + + [DllImport("libX11.so.6")] + public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, + int srcx, int srcy, int destx, int desty, uint width, uint height); + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct XErrorEvent + { + public int type; + public IntPtr* display; /* Display the event was read from */ + public ulong serial; /* serial number of failed request */ + public byte error_code; /* error code of failed request */ + public byte request_code; /* Major op-code of failed request */ + public byte minor_code; /* Minor op-code of failed request */ + public IntPtr resourceid; /* resource id */ + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct XImage + { + public int width, height; /* size of image */ + public int xoffset; /* number of pixels offset in X direction */ + public int format; /* XYBitmap, XYPixmap, ZPixmap */ + public IntPtr data; /* pointer to image data */ + public int byte_order; /* data byte order, LSBFirst, MSBFirst */ + public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ + public int bitmap_bit_order; /* LSBFirst, MSBFirst */ + public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ + public int depth; /* depth of image */ + public int bytes_per_line; /* accelerator to next scanline */ + public int bits_per_pixel; /* bits per pixel (ZPixmap) */ + public ulong red_mask; /* bits in z arrangement */ + public ulong green_mask; + public ulong blue_mask; + private fixed byte funcs[128]; + } + } +} From 666efd311b4db57b2648725be64847230d5973c5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 23 Oct 2018 18:06:26 +0300 Subject: [PATCH 002/207] [X11] Initial windowing and mouse events --- samples/PlatformSanityChecks/Program.cs | 2 +- src/Avalonia.Input/MouseDevice.cs | 4 +- src/Avalonia.Input/Raw/RawInputEventArgs.cs | 4 +- src/Avalonia.Input/Raw/RawKeyEventArgs.cs | 2 +- src/Avalonia.Input/Raw/RawMouseEventArgs.cs | 2 +- .../Raw/RawMouseWheelEventArgs.cs | 2 +- .../Raw/RawTextInputEventArgs.cs | 2 +- src/Avalonia.X11/Avalonia.X11.csproj | 1 + src/Avalonia.X11/Stubs.cs | 103 + src/Avalonia.X11/X11Atoms.cs | 341 ++++ src/Avalonia.X11/X11Enums.cs | 110 + src/Avalonia.X11/X11Framebuffer.cs | 57 + src/Avalonia.X11/X11FramebufferSurface.cs | 27 + src/Avalonia.X11/X11Info.cs | 35 + src/Avalonia.X11/X11Platform.cs | 47 +- src/Avalonia.X11/X11PlatformThreading.cs | 24 +- src/Avalonia.X11/X11Structs.cs | 1815 +++++++++++++++++ src/Avalonia.X11/X11Window.cs | 492 +++++ src/Avalonia.X11/XError.cs | 30 + src/Avalonia.X11/XLib.cs | 468 ++++- 20 files changed, 3474 insertions(+), 94 deletions(-) create mode 100644 src/Avalonia.X11/Stubs.cs create mode 100644 src/Avalonia.X11/X11Atoms.cs create mode 100644 src/Avalonia.X11/X11Enums.cs create mode 100644 src/Avalonia.X11/X11Framebuffer.cs create mode 100644 src/Avalonia.X11/X11FramebufferSurface.cs create mode 100644 src/Avalonia.X11/X11Info.cs create mode 100644 src/Avalonia.X11/X11Structs.cs create mode 100644 src/Avalonia.X11/X11Window.cs create mode 100644 src/Avalonia.X11/XError.cs diff --git a/samples/PlatformSanityChecks/Program.cs b/samples/PlatformSanityChecks/Program.cs index 8a3aa82981..3fdc9b1e77 100644 --- a/samples/PlatformSanityChecks/Program.cs +++ b/samples/PlatformSanityChecks/Program.cs @@ -20,7 +20,7 @@ namespace PlatformSanityChecks AppBuilder.Configure().RuntimePlatformServicesInitializer(); var app = new App(); - new AvaloniaX11Platform().Initialize(); + AvaloniaX11PlatformExtensions.InitializeX11Platform(); CheckPlatformThreading(); diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index e581772978..e01dedeede 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -18,7 +18,7 @@ namespace Avalonia.Input { private int _clickCount; private Rect _lastClickRect; - private uint _lastClickTime; + private ulong _lastClickTime; private IInputElement _captured; private IDisposable _capturedSubscription; @@ -152,7 +152,7 @@ namespace Avalonia.Input ClearPointerOver(this, root); } - private bool MouseDown(IMouseDevice device, uint timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers) + private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); diff --git a/src/Avalonia.Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Input/Raw/RawInputEventArgs.cs index 041c361d30..78c1b58624 100644 --- a/src/Avalonia.Input/Raw/RawInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawInputEventArgs.cs @@ -21,7 +21,7 @@ namespace Avalonia.Input.Raw /// /// The associated device. /// The event timestamp. - public RawInputEventArgs(IInputDevice device, uint timestamp) + public RawInputEventArgs(IInputDevice device, ulong timestamp) { Contract.Requires(device != null); @@ -47,6 +47,6 @@ namespace Avalonia.Input.Raw /// /// Gets the timestamp associated with the event. /// - public uint Timestamp { get; private set; } + public ulong Timestamp { get; private set; } } } diff --git a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs b/src/Avalonia.Input/Raw/RawKeyEventArgs.cs index 0c208b4596..044f244138 100644 --- a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawKeyEventArgs.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input.Raw { public RawKeyEventArgs( IKeyboardDevice device, - uint timestamp, + ulong timestamp, RawKeyEventType type, Key key, InputModifiers modifiers) : base(device, timestamp) diff --git a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseEventArgs.cs index 914833624f..c5637d66cc 100644 --- a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawMouseEventArgs.cs @@ -35,7 +35,7 @@ namespace Avalonia.Input.Raw /// The input modifiers. public RawMouseEventArgs( IInputDevice device, - uint timestamp, + ulong timestamp, IInputRoot root, RawMouseEventType type, Point position, diff --git a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs index f4078af9e1..d2e5faab6c 100644 --- a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs @@ -8,7 +8,7 @@ namespace Avalonia.Input.Raw { public RawMouseWheelEventArgs( IInputDevice device, - uint timestamp, + ulong timestamp, IInputRoot root, Point position, Vector delta, InputModifiers inputModifiers) diff --git a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs index 6e427c3751..0d1e5d2422 100644 --- a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input.Raw { public string Text { get; set; } - public RawTextInputEventArgs(IKeyboardDevice device, uint timestamp, string text) : base(device, timestamp) + public RawTextInputEventArgs(IKeyboardDevice device, ulong timestamp, string text) : base(device, timestamp) { Text = text; } diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index a035febe5b..3f61961571 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs new file mode 100644 index 0000000000..ed14ab7878 --- /dev/null +++ b/src/Avalonia.X11/Stubs.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +namespace Avalonia.X11 +{ + class CursorFactoryStub : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + return new PlatformHandle(IntPtr.Zero, "FAKE"); + } + } + + class ClipboardStub : IClipboard + { + private string _text; + public Task GetTextAsync() + { + return Task.FromResult(_text); + } + + public Task SetTextAsync(string text) + { + _text = text; + return Task.CompletedTask; + } + + public Task ClearAsync() + { + _text = null; + return Task.CompletedTask; + } + } + + class PlatformSettingsStub : IPlatformSettings + { + public Size DoubleClickSize { get; } = new Size(2, 2); + public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + } + + class SystemDialogsStub : ISystemDialogImpl + { + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + return Task.FromResult((string[])null); + } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + return Task.FromResult((string)null); + } + } + + class IconLoaderStub : IPlatformIconLoader + { + class FakeIcon : IWindowIconImpl + { + private readonly byte[] _data; + + public FakeIcon(byte[] data) + { + _data = data; + } + public void Save(Stream outputStream) + { + outputStream.Write(_data, 0, _data.Length); + } + } + + public IWindowIconImpl LoadIcon(string fileName) + { + return new FakeIcon(File.ReadAllBytes(fileName)); + } + + public IWindowIconImpl LoadIcon(Stream stream) + { + var ms = new MemoryStream(); + stream.CopyTo(ms); + return new FakeIcon(ms.ToArray()); + } + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms); + return new FakeIcon(ms.ToArray()); + } + } + + class ScreenStub : IScreenImpl + { + public int ScreenCount { get; } = 1; + + public Screen[] AllScreens { get; } = + {new Screen(new Rect(0, 0, 1920, 1280), new Rect(0, 0, 1920, 1280), true)}; + } +} diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs new file mode 100644 index 0000000000..e7b83f59d3 --- /dev/null +++ b/src/Avalonia.X11/X11Atoms.cs @@ -0,0 +1,341 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Novell, Inc. (http://www.novell.com) +// +// + +using System; +using static Avalonia.X11.XLib; +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable IdentifierTypo +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable CommentTypo +// ReSharper disable ArrangeThisQualifier +// ReSharper disable NotAccessedField.Global +// ReSharper disable InconsistentNaming +// ReSharper disable StringLiteralTypo +#pragma warning disable 649 + +namespace Avalonia.X11 { + + internal class X11Atoms { + + // Our atoms + public readonly IntPtr AnyPropertyType = (IntPtr)0; + public readonly IntPtr XA_PRIMARY = (IntPtr)1; + public readonly IntPtr XA_SECONDARY = (IntPtr)2; + public readonly IntPtr XA_ARC = (IntPtr)3; + public readonly IntPtr XA_ATOM = (IntPtr)4; + public readonly IntPtr XA_BITMAP = (IntPtr)5; + public readonly IntPtr XA_CARDINAL = (IntPtr)6; + public readonly IntPtr XA_COLORMAP = (IntPtr)7; + public readonly IntPtr XA_CURSOR = (IntPtr)8; + public readonly IntPtr XA_CUT_BUFFER0 = (IntPtr)9; + public readonly IntPtr XA_CUT_BUFFER1 = (IntPtr)10; + public readonly IntPtr XA_CUT_BUFFER2 = (IntPtr)11; + public readonly IntPtr XA_CUT_BUFFER3 = (IntPtr)12; + public readonly IntPtr XA_CUT_BUFFER4 = (IntPtr)13; + public readonly IntPtr XA_CUT_BUFFER5 = (IntPtr)14; + public readonly IntPtr XA_CUT_BUFFER6 = (IntPtr)15; + public readonly IntPtr XA_CUT_BUFFER7 = (IntPtr)16; + public readonly IntPtr XA_DRAWABLE = (IntPtr)17; + public readonly IntPtr XA_FONT = (IntPtr)18; + public readonly IntPtr XA_INTEGER = (IntPtr)19; + public readonly IntPtr XA_PIXMAP = (IntPtr)20; + public readonly IntPtr XA_POINT = (IntPtr)21; + public readonly IntPtr XA_RECTANGLE = (IntPtr)22; + public readonly IntPtr XA_RESOURCE_MANAGER = (IntPtr)23; + public readonly IntPtr XA_RGB_COLOR_MAP = (IntPtr)24; + public readonly IntPtr XA_RGB_BEST_MAP = (IntPtr)25; + public readonly IntPtr XA_RGB_BLUE_MAP = (IntPtr)26; + public readonly IntPtr XA_RGB_DEFAULT_MAP = (IntPtr)27; + public readonly IntPtr XA_RGB_GRAY_MAP = (IntPtr)28; + public readonly IntPtr XA_RGB_GREEN_MAP = (IntPtr)29; + public readonly IntPtr XA_RGB_RED_MAP = (IntPtr)30; + public readonly IntPtr XA_STRING = (IntPtr)31; + public readonly IntPtr XA_VISUALID = (IntPtr)32; + public readonly IntPtr XA_WINDOW = (IntPtr)33; + public readonly IntPtr XA_WM_COMMAND = (IntPtr)34; + public readonly IntPtr XA_WM_HINTS = (IntPtr)35; + public readonly IntPtr XA_WM_CLIENT_MACHINE = (IntPtr)36; + public readonly IntPtr XA_WM_ICON_NAME = (IntPtr)37; + public readonly IntPtr XA_WM_ICON_SIZE = (IntPtr)38; + public readonly IntPtr XA_WM_NAME = (IntPtr)39; + public readonly IntPtr XA_WM_NORMAL_HINTS = (IntPtr)40; + public readonly IntPtr XA_WM_SIZE_HINTS = (IntPtr)41; + public readonly IntPtr XA_WM_ZOOM_HINTS = (IntPtr)42; + public readonly IntPtr XA_MIN_SPACE = (IntPtr)43; + public readonly IntPtr XA_NORM_SPACE = (IntPtr)44; + public readonly IntPtr XA_MAX_SPACE = (IntPtr)45; + public readonly IntPtr XA_END_SPACE = (IntPtr)46; + public readonly IntPtr XA_SUPERSCRIPT_X = (IntPtr)47; + public readonly IntPtr XA_SUPERSCRIPT_Y = (IntPtr)48; + public readonly IntPtr XA_SUBSCRIPT_X = (IntPtr)49; + public readonly IntPtr XA_SUBSCRIPT_Y = (IntPtr)50; + public readonly IntPtr XA_UNDERLINE_POSITION = (IntPtr)51; + public readonly IntPtr XA_UNDERLINE_THICKNESS = (IntPtr)52; + public readonly IntPtr XA_STRIKEOUT_ASCENT = (IntPtr)53; + public readonly IntPtr XA_STRIKEOUT_DESCENT = (IntPtr)54; + public readonly IntPtr XA_ITALIC_ANGLE = (IntPtr)55; + public readonly IntPtr XA_X_HEIGHT = (IntPtr)56; + public readonly IntPtr XA_QUAD_WIDTH = (IntPtr)57; + public readonly IntPtr XA_WEIGHT = (IntPtr)58; + public readonly IntPtr XA_POINT_SIZE = (IntPtr)59; + public readonly IntPtr XA_RESOLUTION = (IntPtr)60; + public readonly IntPtr XA_COPYRIGHT = (IntPtr)61; + public readonly IntPtr XA_NOTICE = (IntPtr)62; + public readonly IntPtr XA_FONT_NAME = (IntPtr)63; + public readonly IntPtr XA_FAMILY_NAME = (IntPtr)64; + public readonly IntPtr XA_FULL_NAME = (IntPtr)65; + public readonly IntPtr XA_CAP_HEIGHT = (IntPtr)66; + public readonly IntPtr XA_WM_CLASS = (IntPtr)67; + public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68; + + public readonly IntPtr WM_PROTOCOLS; + public readonly IntPtr WM_DELETE_WINDOW; + public readonly IntPtr WM_TAKE_FOCUS; + public readonly IntPtr _NET_SUPPORTED; + public readonly IntPtr _NET_CLIENT_LIST; + public readonly IntPtr _NET_NUMBER_OF_DESKTOPS; + public readonly IntPtr _NET_DESKTOP_GEOMETRY; + public readonly IntPtr _NET_DESKTOP_VIEWPORT; + public readonly IntPtr _NET_CURRENT_DESKTOP; + public readonly IntPtr _NET_DESKTOP_NAMES; + public readonly IntPtr _NET_ACTIVE_WINDOW; + public readonly IntPtr _NET_WORKAREA; + public readonly IntPtr _NET_SUPPORTING_WM_CHECK; + public readonly IntPtr _NET_VIRTUAL_ROOTS; + public readonly IntPtr _NET_DESKTOP_LAYOUT; + public readonly IntPtr _NET_SHOWING_DESKTOP; + public readonly IntPtr _NET_CLOSE_WINDOW; + public readonly IntPtr _NET_MOVERESIZE_WINDOW; + public readonly IntPtr _NET_WM_MOVERESIZE; + public readonly IntPtr _NET_RESTACK_WINDOW; + public readonly IntPtr _NET_REQUEST_FRAME_EXTENTS; + public readonly IntPtr _NET_WM_NAME; + public readonly IntPtr _NET_WM_VISIBLE_NAME; + public readonly IntPtr _NET_WM_ICON_NAME; + public readonly IntPtr _NET_WM_VISIBLE_ICON_NAME; + public readonly IntPtr _NET_WM_DESKTOP; + public readonly IntPtr _NET_WM_WINDOW_TYPE; + public readonly IntPtr _NET_WM_STATE; + public readonly IntPtr _NET_WM_ALLOWED_ACTIONS; + public readonly IntPtr _NET_WM_STRUT; + public readonly IntPtr _NET_WM_STRUT_PARTIAL; + public readonly IntPtr _NET_WM_ICON_GEOMETRY; + public readonly IntPtr _NET_WM_ICON; + public readonly IntPtr _NET_WM_PID; + public readonly IntPtr _NET_WM_HANDLED_ICONS; + public readonly IntPtr _NET_WM_USER_TIME; + public readonly IntPtr _NET_FRAME_EXTENTS; + public readonly IntPtr _NET_WM_PING; + public readonly IntPtr _NET_WM_SYNC_REQUEST; + public readonly IntPtr _NET_SYSTEM_TRAY_S; + public readonly IntPtr _NET_SYSTEM_TRAY_ORIENTATION; + public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE; + public readonly IntPtr _NET_WM_STATE_MAXIMIZED_HORZ; + public readonly IntPtr _NET_WM_STATE_MAXIMIZED_VERT; + public readonly IntPtr _XEMBED; + public readonly IntPtr _XEMBED_INFO; + public readonly IntPtr _MOTIF_WM_HINTS; + public readonly IntPtr _NET_WM_STATE_SKIP_TASKBAR; + public readonly IntPtr _NET_WM_STATE_ABOVE; + public readonly IntPtr _NET_WM_STATE_MODAL; + public readonly IntPtr _NET_WM_STATE_HIDDEN; + public readonly IntPtr _NET_WM_CONTEXT_HELP; + public readonly IntPtr _NET_WM_WINDOW_OPACITY; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DESKTOP; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DOCK; + public readonly IntPtr _NET_WM_WINDOW_TYPE_TOOLBAR; + public readonly IntPtr _NET_WM_WINDOW_TYPE_MENU; + public readonly IntPtr _NET_WM_WINDOW_TYPE_UTILITY; + public readonly IntPtr _NET_WM_WINDOW_TYPE_SPLASH; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DIALOG; + public readonly IntPtr _NET_WM_WINDOW_TYPE_NORMAL; + public readonly IntPtr CLIPBOARD; + public readonly IntPtr PRIMARY; + public readonly IntPtr DIB; + public readonly IntPtr OEMTEXT; + public readonly IntPtr UNICODETEXT; + public readonly IntPtr TARGETS; + public readonly IntPtr PostAtom; + public readonly IntPtr HoverState; + public readonly IntPtr AsyncAtom; + + + public X11Atoms (IntPtr display) { + + // make sure this array stays in sync with the statements below + string [] atom_names = new string[] { + "WM_PROTOCOLS", + "WM_DELETE_WINDOW", + "WM_TAKE_FOCUS", + "_NET_SUPPORTED", + "_NET_CLIENT_LIST", + "_NET_NUMBER_OF_DESKTOPS", + "_NET_DESKTOP_GEOMETRY", + "_NET_DESKTOP_VIEWPORT", + "_NET_CURRENT_DESKTOP", + "_NET_DESKTOP_NAMES", + "_NET_ACTIVE_WINDOW", + "_NET_WORKAREA", + "_NET_SUPPORTING_WM_CHECK", + "_NET_VIRTUAL_ROOTS", + "_NET_DESKTOP_LAYOUT", + "_NET_SHOWING_DESKTOP", + "_NET_CLOSE_WINDOW", + "_NET_MOVERESIZE_WINDOW", + "_NET_WM_MOVERESIZE", + "_NET_RESTACK_WINDOW", + "_NET_REQUEST_FRAME_EXTENTS", + "_NET_WM_NAME", + "_NET_WM_VISIBLE_NAME", + "_NET_WM_ICON_NAME", + "_NET_WM_VISIBLE_ICON_NAME", + "_NET_WM_DESKTOP", + "_NET_WM_WINDOW_TYPE", + "_NET_WM_STATE", + "_NET_WM_ALLOWED_ACTIONS", + "_NET_WM_STRUT", + "_NET_WM_STRUT_PARTIAL", + "_NET_WM_ICON_GEOMETRY", + "_NET_WM_ICON", + "_NET_WM_PID", + "_NET_WM_HANDLED_ICONS", + "_NET_WM_USER_TIME", + "_NET_FRAME_EXTENTS", + "_NET_WM_PING", + "_NET_WM_SYNC_REQUEST", + "_NET_SYSTEM_TRAY_OPCODE", + "_NET_SYSTEM_TRAY_ORIENTATION", + "_NET_WM_STATE_MAXIMIZED_HORZ", + "_NET_WM_STATE_MAXIMIZED_VERT", + "_NET_WM_STATE_HIDDEN", + "_XEMBED", + "_XEMBED_INFO", + "_MOTIF_WM_HINTS", + "_NET_WM_STATE_SKIP_TASKBAR", + "_NET_WM_STATE_ABOVE", + "_NET_WM_STATE_MODAL", + "_NET_WM_CONTEXT_HELP", + "_NET_WM_WINDOW_OPACITY", + "_NET_WM_WINDOW_TYPE_DESKTOP", + "_NET_WM_WINDOW_TYPE_DOCK", + "_NET_WM_WINDOW_TYPE_TOOLBAR", + "_NET_WM_WINDOW_TYPE_MENU", + "_NET_WM_WINDOW_TYPE_UTILITY", + "_NET_WM_WINDOW_TYPE_DIALOG", + "_NET_WM_WINDOW_TYPE_SPLASH", + "_NET_WM_WINDOW_TYPE_NORMAL", + "CLIPBOARD", + "PRIMARY", + "COMPOUND_TEXT", + "UTF8_STRING", + "TARGETS", + "_SWF_AsyncAtom", + "_SWF_PostMessageAtom", + "_SWF_HoverAtom" }; + + IntPtr[] atoms = new IntPtr [atom_names.Length];; + + XInternAtoms (display, atom_names, atom_names.Length, false, atoms); + + int off = 0; + WM_PROTOCOLS = atoms [off++]; + WM_DELETE_WINDOW = atoms [off++]; + WM_TAKE_FOCUS = atoms [off++]; + _NET_SUPPORTED = atoms [off++]; + _NET_CLIENT_LIST = atoms [off++]; + _NET_NUMBER_OF_DESKTOPS = atoms [off++]; + _NET_DESKTOP_GEOMETRY = atoms [off++]; + _NET_DESKTOP_VIEWPORT = atoms [off++]; + _NET_CURRENT_DESKTOP = atoms [off++]; + _NET_DESKTOP_NAMES = atoms [off++]; + _NET_ACTIVE_WINDOW = atoms [off++]; + _NET_WORKAREA = atoms [off++]; + _NET_SUPPORTING_WM_CHECK = atoms [off++]; + _NET_VIRTUAL_ROOTS = atoms [off++]; + _NET_DESKTOP_LAYOUT = atoms [off++]; + _NET_SHOWING_DESKTOP = atoms [off++]; + _NET_CLOSE_WINDOW = atoms [off++]; + _NET_MOVERESIZE_WINDOW = atoms [off++]; + _NET_WM_MOVERESIZE = atoms [off++]; + _NET_RESTACK_WINDOW = atoms [off++]; + _NET_REQUEST_FRAME_EXTENTS = atoms [off++]; + _NET_WM_NAME = atoms [off++]; + _NET_WM_VISIBLE_NAME = atoms [off++]; + _NET_WM_ICON_NAME = atoms [off++]; + _NET_WM_VISIBLE_ICON_NAME = atoms [off++]; + _NET_WM_DESKTOP = atoms [off++]; + _NET_WM_WINDOW_TYPE = atoms [off++]; + _NET_WM_STATE = atoms [off++]; + _NET_WM_ALLOWED_ACTIONS = atoms [off++]; + _NET_WM_STRUT = atoms [off++]; + _NET_WM_STRUT_PARTIAL = atoms [off++]; + _NET_WM_ICON_GEOMETRY = atoms [off++]; + _NET_WM_ICON = atoms [off++]; + _NET_WM_PID = atoms [off++]; + _NET_WM_HANDLED_ICONS = atoms [off++]; + _NET_WM_USER_TIME = atoms [off++]; + _NET_FRAME_EXTENTS = atoms [off++]; + _NET_WM_PING = atoms [off++]; + _NET_WM_SYNC_REQUEST = atoms [off++]; + _NET_SYSTEM_TRAY_OPCODE = atoms [off++]; + _NET_SYSTEM_TRAY_ORIENTATION = atoms [off++]; + _NET_WM_STATE_MAXIMIZED_HORZ = atoms [off++]; + _NET_WM_STATE_MAXIMIZED_VERT = atoms [off++]; + _NET_WM_STATE_HIDDEN = atoms [off++]; + _XEMBED = atoms [off++]; + _XEMBED_INFO = atoms [off++]; + _MOTIF_WM_HINTS = atoms [off++]; + _NET_WM_STATE_SKIP_TASKBAR = atoms [off++]; + _NET_WM_STATE_ABOVE = atoms [off++]; + _NET_WM_STATE_MODAL = atoms [off++]; + _NET_WM_CONTEXT_HELP = atoms [off++]; + _NET_WM_WINDOW_OPACITY = atoms [off++]; + _NET_WM_WINDOW_TYPE_DESKTOP = atoms [off++]; + _NET_WM_WINDOW_TYPE_DOCK = atoms [off++]; + _NET_WM_WINDOW_TYPE_TOOLBAR = atoms [off++]; + _NET_WM_WINDOW_TYPE_MENU = atoms [off++]; + _NET_WM_WINDOW_TYPE_UTILITY = atoms [off++]; + _NET_WM_WINDOW_TYPE_DIALOG = atoms [off++]; + _NET_WM_WINDOW_TYPE_SPLASH = atoms [off++]; + _NET_WM_WINDOW_TYPE_NORMAL = atoms [off++]; + CLIPBOARD = atoms [off++]; + PRIMARY = atoms [off++]; + OEMTEXT = atoms [off++]; + UNICODETEXT = atoms [off++]; + TARGETS = atoms [off++]; + AsyncAtom = atoms [off++]; + PostAtom = atoms [off++]; + HoverState = atoms [off++]; + + DIB = XA_PIXMAP; + + var defScreen = XDefaultScreen(display); + // XXX multi screen stuff here + _NET_SYSTEM_TRAY_S = XInternAtom (display, "_NET_SYSTEM_TRAY_S" + defScreen.ToString(), false); + } + + } + +} + diff --git a/src/Avalonia.X11/X11Enums.cs b/src/Avalonia.X11/X11Enums.cs new file mode 100644 index 0000000000..d97e1c42bb --- /dev/null +++ b/src/Avalonia.X11/X11Enums.cs @@ -0,0 +1,110 @@ +using System; + +namespace Avalonia.X11 +{ + + public enum Status + { + Success = 0, /* everything's okay */ + BadRequest = 1, /* bad request code */ + BadValue = 2, /* int parameter out of range */ + BadWindow = 3, /* parameter not a Window */ + BadPixmap = 4, /* parameter not a Pixmap */ + BadAtom = 5, /* parameter not an Atom */ + BadCursor = 6, /* parameter not a Cursor */ + BadFont = 7, /* parameter not a Font */ + BadMatch = 8, /* parameter mismatch */ + BadDrawable = 9, /* parameter not a Pixmap or Window */ + BadAccess = 10, /* depending on context: + - key/button already grabbed + - attempt to free an illegal + cmap entry + - attempt to store into a read-only + color map entry. + - attempt to modify the access control + list from other than the local host. + */ + BadAlloc = 11, /* insufficient resources */ + BadColor = 12, /* no such colormap */ + BadGC = 13, /* parameter not a GC */ + BadIDChoice = 14, /* choice not in range or already used */ + BadName = 15, /* font or color name doesn't exist */ + BadLength = 16, /* Request length incorrect */ + BadImplementation = 17, /* server is defective */ + + FirstExtensionError = 128, + LastExtensionError = 255, + + } + + [Flags] + public enum XEventMask : int + { + NoEventMask = 0, + KeyPressMask = (1 << 0), + KeyReleaseMask = (1 << 1), + ButtonPressMask = (1 << 2), + ButtonReleaseMask = (1 << 3), + EnterWindowMask = (1 << 4), + LeaveWindowMask = (1 << 5), + PointerMotionMask = (1 << 6), + PointerMotionHintMask = (1 << 7), + Button1MotionMask = (1 << 8), + Button2MotionMask = (1 << 9), + Button3MotionMask = (1 << 10), + Button4MotionMask = (1 << 11), + Button5MotionMask = (1 << 12), + ButtonMotionMask = (1 << 13), + KeymapStateMask = (1 << 14), + ExposureMask = (1 << 15), + VisibilityChangeMask = (1 << 16), + StructureNotifyMask = (1 << 17), + ResizeRedirectMask = (1 << 18), + SubstructureNotifyMask = (1 << 19), + SubstructureRedirectMask = (1 << 20), + FocusChangeMask = (1 << 21), + PropertyChangeMask = (1 << 22), + ColormapChangeMask = (1 << 23), + OwnerGrabButtonMask = (1 << 24) + } + + [Flags] + public enum XModifierMask + { + ShiftMask = (1 << 0), + LockMask = (1 << 1), + ControlMask = (1 << 2), + Mod1Mask = (1 << 3), + Mod2Mask = (1 << 4), + Mod3Mask = (1 << 5), + Mod4Mask = (1 << 6), + Mod5Mask = (1 << 7), + Button1Mask = (1 << 8), + Button2Mask = (1 << 9), + Button3Mask = (1 << 10), + Button4Mask = (1 << 11), + Button5Mask = (1 << 12), + AnyModifier = (1 << 15) + + } + + [Flags] + public enum XCreateWindowFlags + { + CWBackPixmap = (1 << 0), + CWBackPixel = (1 << 1), + CWBorderPixmap = (1 << 2), + CWBorderPixel = (1 << 3), + CWBitGravity = (1 << 4), + CWWinGravity = (1 << 5), + CWBackingStore = (1 << 6), + CWBackingPlanes = (1 << 7), + CWBackingPixel = (1 << 8), + CWOverrideRedirect = (1 << 9), + CWSaveUnder = (1 << 10), + CWEventMask = (1 << 11), + CWDontPropagate = (1 << 12), + CWColormap = (1 << 13), + CWCursor = (1 << 14), + } +} diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs new file mode 100644 index 0000000000..ef93a7347b --- /dev/null +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -0,0 +1,57 @@ +using System; +using Avalonia.Platform; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + class X11Framebuffer : ILockedFramebuffer + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + private IUnmanagedBlob _blob; + + public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor) + { + _display = display; + _xid = xid; + Width = width*factor; + Height = height*factor; + RowBytes = Width * 4; + Dpi = new Vector(96, 96) * factor; + Format = PixelFormat.Bgra8888; + _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * Height); + Address = _blob.Address; + } + + public void Dispose() + { + var image = new XImage(); + int bitsPerPixel = 32; + image.width = Width; + image.height = Height; + image.format = 2; //ZPixmap; + image.data = Address; + image.byte_order = 0;// LSBFirst; + image.bitmap_unit = bitsPerPixel; + image.bitmap_bit_order = 0;// LSBFirst; + image.bitmap_pad = bitsPerPixel; + image.depth = 32; + image.bytes_per_line = RowBytes - Width * 4; + image.bits_per_pixel = bitsPerPixel; + XLockDisplay(_display); + XInitImage(ref image); + var gc = XCreateGC(_display, _xid, 0, IntPtr.Zero); + XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height); + XFreeGC(_display, gc); + XSync(_display, true); + XUnlockDisplay(_display); + _blob.Dispose(); + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Vector Dpi { get; } + public PixelFormat Format { get; } + } +} diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs new file mode 100644 index 0000000000..05b21efb0c --- /dev/null +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -0,0 +1,27 @@ +using System; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + public class X11FramebufferSurface : IFramebufferPlatformSurface + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + + public X11FramebufferSurface(IntPtr display, IntPtr xid) + { + _display = display; + _xid = xid; + } + + public ILockedFramebuffer Lock() + { + XLockDisplay(_display); + XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height, + out var bw, out var d); + XUnlockDisplay(_display); + return new X11Framebuffer(_display, _xid, width, height, 1); + } + } +} diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs new file mode 100644 index 0000000000..6ad9faffbc --- /dev/null +++ b/src/Avalonia.X11/X11Info.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using static Avalonia.X11.XLib; +// ReSharper disable UnusedAutoPropertyAccessor.Local +namespace Avalonia.X11 +{ + class X11Info + { + public IntPtr Display { get; } + public IntPtr DeferredDisplay { get; } + public int DefaultScreen { get; } + public IntPtr BlackPixel { get; } + public IntPtr RootWindow { get; } + public IntPtr DefaultRootWindow { get; } + public XVisualInfo MatchedVisual { get; } + public X11Atoms Atoms { get; } + + public IntPtr LastActivityTimestamp { get; set; } + + public unsafe X11Info(IntPtr display, IntPtr deferredDisplay) + { + Display = display; + DeferredDisplay = deferredDisplay; + DefaultScreen = XDefaultScreen(display); + BlackPixel = XBlackPixel(display, DefaultScreen); + RootWindow = XRootWindow(display, DefaultScreen); + DefaultRootWindow = XDefaultRootWindow(display); + Atoms = new X11Atoms(display); + XMatchVisualInfo(display, DefaultScreen, 32, 4, out var info); + MatchedVisual = info; + } + } +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index c266433eef..33497be411 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -1,27 +1,66 @@ using System; +using System.Collections.Generic; using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.X11; using static Avalonia.X11.XLib; namespace Avalonia.X11 { - public class AvaloniaX11Platform + class AvaloniaX11Platform : IWindowingPlatform { + private Lazy _keyboardDevice = new Lazy(() => new KeyboardDevice()); + private Lazy _mouseDevice = new Lazy(() => new MouseDevice()); + public KeyboardDevice KeyboardDevice => _keyboardDevice.Value; + public MouseDevice MouseDevice => _mouseDevice.Value; + public Dictionary> Windows = new Dictionary>(); + public X11Info Info { get; private set; } public void Initialize() { + XInitThreads(); Display = XOpenDisplay(IntPtr.Zero); DeferredDisplay = XOpenDisplay(IntPtr.Zero); if (Display == IntPtr.Zero) throw new Exception("XOpenDisplay failed"); - + XError.Init(); + Info = new X11Info(Display, DeferredDisplay); AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(new X11PlatformThreading(Display)); + .Bind().ToConstant(this) + .Bind().ToConstant(new X11PlatformThreading(Display, Windows)) + .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) + .Bind().ToFunc(() => KeyboardDevice) + .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToSingleton() + .Bind().ToConstant(new PlatformSettingsStub()) + .Bind().ToConstant(new SystemDialogsStub()) + .Bind().ToConstant(new IconLoaderStub()); + EglGlPlatformFeature.TryInitialize(); } public IntPtr DeferredDisplay { get; set; } public IntPtr Display { get; set; } + public IWindowImpl CreateWindow() + { + return new X11Window(this, false); + } + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + throw new NotSupportedException(); + } + + public IPopupImpl CreatePopup() + { + return new X11Window(this, true); + } } } @@ -34,6 +73,8 @@ namespace Avalonia builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); return builder; } + + public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize(); } } diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs index 18b08085e3..298b4da19c 100644 --- a/src/Avalonia.X11/X11PlatformThreading.cs +++ b/src/Avalonia.X11/X11PlatformThreading.cs @@ -9,9 +9,10 @@ using static Avalonia.X11.XLib; namespace Avalonia.X11 { - public unsafe class X11PlatformThreading : IPlatformThreadingInterface + unsafe class X11PlatformThreading : IPlatformThreadingInterface { private readonly IntPtr _display; + private readonly Dictionary> _eventHandlers; private Thread _mainThread; [StructLayout(LayoutKind.Explicit)] @@ -102,9 +103,10 @@ namespace Avalonia.X11 List _timers = new List(); - public X11PlatformThreading(IntPtr display) + public X11PlatformThreading(IntPtr display, Dictionary> eventHandlers) { _display = display; + _eventHandlers = eventHandlers; _mainThread = Thread.CurrentThread; var fd = XLib.XConnectionNumber(display); var ev = new epoll_event() @@ -202,12 +204,22 @@ namespace Avalonia.X11 } else { - while (XPending(_display)) + while (true) { - if (cancellationToken.IsCancellationRequested) - return; - XNextEvent(_display, out var xev); + var pending = XPending(_display); + if (pending == 0) + break; + while (pending > 0) + { + if (cancellationToken.IsCancellationRequested) + return; + XNextEvent(_display, out var xev); + pending--; + if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler)) + handler(xev); + } } + Dispatcher.UIThread.RunJobs(); } } } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs new file mode 100644 index 0000000000..a18bd4be74 --- /dev/null +++ b/src/Avalonia.X11/X11Structs.cs @@ -0,0 +1,1815 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software",, to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. +// +// Authors: +// Peter Bartok pbartok@novell.com +// + + +// NOT COMPLETE + +using System; +using System.ComponentModel; +using System.Collections; +using System.Drawing; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable IdentifierTypo +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable CommentTypo +// ReSharper disable ArrangeThisQualifier +// ReSharper disable NotAccessedField.Global +#pragma warning disable 649 + +namespace Avalonia.X11 { + // + // In the structures below, fields of type long are mapped to IntPtr. + // This will work on all platforms where sizeof(long)==sizeof(void*), which + // is almost all platforms except WIN64. + // + + [StructLayout(LayoutKind.Sequential)] + internal struct XAnyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XKeyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal int state; + internal int keycode; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XButtonEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal XModifierMask state; + internal int button; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMotionEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal XModifierMask state; + internal byte is_hint; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCrossingEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal NotifyMode mode; + internal NotifyDetail detail; + internal bool same_screen; + internal bool focus; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XFocusChangeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int mode; + internal NotifyDetail detail; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XKeymapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal byte key_vector0; + internal byte key_vector1; + internal byte key_vector2; + internal byte key_vector3; + internal byte key_vector4; + internal byte key_vector5; + internal byte key_vector6; + internal byte key_vector7; + internal byte key_vector8; + internal byte key_vector9; + internal byte key_vector10; + internal byte key_vector11; + internal byte key_vector12; + internal byte key_vector13; + internal byte key_vector14; + internal byte key_vector15; + internal byte key_vector16; + internal byte key_vector17; + internal byte key_vector18; + internal byte key_vector19; + internal byte key_vector20; + internal byte key_vector21; + internal byte key_vector22; + internal byte key_vector23; + internal byte key_vector24; + internal byte key_vector25; + internal byte key_vector26; + internal byte key_vector27; + internal byte key_vector28; + internal byte key_vector29; + internal byte key_vector30; + internal byte key_vector31; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int count; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XGraphicsExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr drawable; + internal int x; + internal int y; + internal int width; + internal int height; + internal int count; + internal int major_code; + internal int minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XNoExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr drawable; + internal int major_code; + internal int minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XVisibilityEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCreateWindowEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XDestroyWindowEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XUnmapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal bool from_configure; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMapRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XReparentEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal IntPtr parent; + internal int x; + internal int y; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XConfigureEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr above; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XGravityEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int x; + internal int y; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XResizeRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int width; + internal int height; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XConfigureRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr above; + internal int detail; + internal IntPtr value_mask; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCirculateEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int place; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCirculateRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int place; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XPropertyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr atom; + internal IntPtr time; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionClearEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr selection; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr owner; + internal IntPtr requestor; + internal IntPtr selection; + internal IntPtr target; + internal IntPtr property; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr requestor; + internal IntPtr selection; + internal IntPtr target; + internal IntPtr property; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XColormapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr colormap; + internal bool c_new; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XClientMessageEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr message_type; + internal int format; + internal IntPtr ptr1; + internal IntPtr ptr2; + internal IntPtr ptr3; + internal IntPtr ptr4; + internal IntPtr ptr5; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMappingEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int request; + internal int first_keycode; + internal int count; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XErrorEvent { + internal XEventName type; + internal IntPtr display; + internal IntPtr resourceid; + internal IntPtr serial; + internal byte error_code; + internal XRequest request_code; + internal byte minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XEventPad { + internal IntPtr pad0; + internal IntPtr pad1; + internal IntPtr pad2; + internal IntPtr pad3; + internal IntPtr pad4; + internal IntPtr pad5; + internal IntPtr pad6; + internal IntPtr pad7; + internal IntPtr pad8; + internal IntPtr pad9; + internal IntPtr pad10; + internal IntPtr pad11; + internal IntPtr pad12; + internal IntPtr pad13; + internal IntPtr pad14; + internal IntPtr pad15; + internal IntPtr pad16; + internal IntPtr pad17; + internal IntPtr pad18; + internal IntPtr pad19; + internal IntPtr pad20; + internal IntPtr pad21; + internal IntPtr pad22; + internal IntPtr pad23; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct XEvent { + [ FieldOffset(0) ] internal XEventName type; + [ FieldOffset(0) ] internal XAnyEvent AnyEvent; + [ FieldOffset(0) ] internal XKeyEvent KeyEvent; + [ FieldOffset(0) ] internal XButtonEvent ButtonEvent; + [ FieldOffset(0) ] internal XMotionEvent MotionEvent; + [ FieldOffset(0) ] internal XCrossingEvent CrossingEvent; + [ FieldOffset(0) ] internal XFocusChangeEvent FocusChangeEvent; + [ FieldOffset(0) ] internal XExposeEvent ExposeEvent; + [ FieldOffset(0) ] internal XGraphicsExposeEvent GraphicsExposeEvent; + [ FieldOffset(0) ] internal XNoExposeEvent NoExposeEvent; + [ FieldOffset(0) ] internal XVisibilityEvent VisibilityEvent; + [ FieldOffset(0) ] internal XCreateWindowEvent CreateWindowEvent; + [ FieldOffset(0) ] internal XDestroyWindowEvent DestroyWindowEvent; + [ FieldOffset(0) ] internal XUnmapEvent UnmapEvent; + [ FieldOffset(0) ] internal XMapEvent MapEvent; + [ FieldOffset(0) ] internal XMapRequestEvent MapRequestEvent; + [ FieldOffset(0) ] internal XReparentEvent ReparentEvent; + [ FieldOffset(0) ] internal XConfigureEvent ConfigureEvent; + [ FieldOffset(0) ] internal XGravityEvent GravityEvent; + [ FieldOffset(0) ] internal XResizeRequestEvent ResizeRequestEvent; + [ FieldOffset(0) ] internal XConfigureRequestEvent ConfigureRequestEvent; + [ FieldOffset(0) ] internal XCirculateEvent CirculateEvent; + [ FieldOffset(0) ] internal XCirculateRequestEvent CirculateRequestEvent; + [ FieldOffset(0) ] internal XPropertyEvent PropertyEvent; + [ FieldOffset(0) ] internal XSelectionClearEvent SelectionClearEvent; + [ FieldOffset(0) ] internal XSelectionRequestEvent SelectionRequestEvent; + [ FieldOffset(0) ] internal XSelectionEvent SelectionEvent; + [ FieldOffset(0) ] internal XColormapEvent ColormapEvent; + [ FieldOffset(0) ] internal XClientMessageEvent ClientMessageEvent; + [ FieldOffset(0) ] internal XMappingEvent MappingEvent; + [ FieldOffset(0) ] internal XErrorEvent ErrorEvent; + [ FieldOffset(0) ] internal XKeymapEvent KeymapEvent; + + //[MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst=24)] + //[ FieldOffset(0) ] internal int[] pad; + [ FieldOffset(0) ] internal XEventPad Pad; + public override string ToString() { + switch (type) + { + case XEventName.ButtonPress: + case XEventName.ButtonRelease: + return ToString (ButtonEvent); + case XEventName.CirculateNotify: + case XEventName.CirculateRequest: + return ToString (CirculateEvent); + case XEventName.ClientMessage: + return ToString (ClientMessageEvent); + case XEventName.ColormapNotify: + return ToString (ColormapEvent); + case XEventName.ConfigureNotify: + return ToString (ConfigureEvent); + case XEventName.ConfigureRequest: + return ToString (ConfigureRequestEvent); + case XEventName.CreateNotify: + return ToString (CreateWindowEvent); + case XEventName.DestroyNotify: + return ToString (DestroyWindowEvent); + case XEventName.Expose: + return ToString (ExposeEvent); + case XEventName.FocusIn: + case XEventName.FocusOut: + return ToString (FocusChangeEvent); + case XEventName.GraphicsExpose: + return ToString (GraphicsExposeEvent); + case XEventName.GravityNotify: + return ToString (GravityEvent); + case XEventName.KeymapNotify: + return ToString (KeymapEvent); + case XEventName.MapNotify: + return ToString (MapEvent); + case XEventName.MappingNotify: + return ToString (MappingEvent); + case XEventName.MapRequest: + return ToString (MapRequestEvent); + case XEventName.MotionNotify: + return ToString (MotionEvent); + case XEventName.NoExpose: + return ToString (NoExposeEvent); + case XEventName.PropertyNotify: + return ToString (PropertyEvent); + case XEventName.ReparentNotify: + return ToString (ReparentEvent); + case XEventName.ResizeRequest: + return ToString (ResizeRequestEvent); + case XEventName.SelectionClear: + return ToString (SelectionClearEvent); + case XEventName.SelectionNotify: + return ToString (SelectionEvent); + case XEventName.SelectionRequest: + return ToString (SelectionRequestEvent); + case XEventName.UnmapNotify: + return ToString (UnmapEvent); + case XEventName.VisibilityNotify: + return ToString (VisibilityEvent); + case XEventName.EnterNotify: + case XEventName.LeaveNotify: + return ToString (CrossingEvent); + default: + return type.ToString (); + } + } + + public static string ToString (object ev) + { + string result = string.Empty; + Type type = ev.GetType (); + FieldInfo [] fields = type.GetFields (System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance); + for (int i = 0; i < fields.Length; i++) { + if (result != string.Empty) { + result += ", "; + } + object value = fields [i].GetValue (ev); + result += fields [i].Name + "=" + (value == null ? "" : value.ToString ()); + } + return type.Name + " (" + result + ")"; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSetWindowAttributes { + internal IntPtr background_pixmap; + internal IntPtr background_pixel; + internal IntPtr border_pixmap; + internal IntPtr border_pixel; + internal Gravity bit_gravity; + internal Gravity win_gravity; + internal int backing_store; + internal IntPtr backing_planes; + internal IntPtr backing_pixel; + internal bool save_under; + internal IntPtr event_mask; + internal IntPtr do_not_propagate_mask; + internal bool override_redirect; + internal IntPtr colormap; + internal IntPtr cursor; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XWindowAttributes { + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal int depth; + internal IntPtr visual; + internal IntPtr root; + internal int c_class; + internal Gravity bit_gravity; + internal Gravity win_gravity; + internal int backing_store; + internal IntPtr backing_planes; + internal IntPtr backing_pixel; + internal bool save_under; + internal IntPtr colormap; + internal bool map_installed; + internal MapState map_state; + internal IntPtr all_event_masks; + internal IntPtr your_event_mask; + internal IntPtr do_not_propagate_mask; + internal bool override_direct; + internal IntPtr screen; + + public override string ToString () + { + return XEvent.ToString (this); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XTextProperty { + internal string value; + internal IntPtr encoding; + internal int format; + internal IntPtr nitems; + } + + internal enum XWindowClass { + InputOutput = 1, + InputOnly = 2 + } + + internal enum XEventName { + KeyPress = 2, + KeyRelease = 3, + ButtonPress = 4, + ButtonRelease = 5, + MotionNotify = 6, + EnterNotify = 7, + LeaveNotify = 8, + FocusIn = 9, + FocusOut = 10, + KeymapNotify = 11, + Expose = 12, + GraphicsExpose = 13, + NoExpose = 14, + VisibilityNotify = 15, + CreateNotify = 16, + DestroyNotify = 17, + UnmapNotify = 18, + MapNotify = 19, + MapRequest = 20, + ReparentNotify = 21, + ConfigureNotify = 22, + ConfigureRequest = 23, + GravityNotify = 24, + ResizeRequest = 25, + CirculateNotify = 26, + CirculateRequest = 27, + PropertyNotify = 28, + SelectionClear = 29, + SelectionRequest = 30, + SelectionNotify = 31, + ColormapNotify = 32, + ClientMessage = 33, + MappingNotify = 34, + + LASTEvent + } + + [Flags] + internal enum SetWindowValuemask { + Nothing = 0, + BackPixmap = 1, + BackPixel = 2, + BorderPixmap = 4, + BorderPixel = 8, + BitGravity = 16, + WinGravity = 32, + BackingStore = 64, + BackingPlanes = 128, + BackingPixel = 256, + OverrideRedirect = 512, + SaveUnder = 1024, + EventMask = 2048, + DontPropagate = 4096, + ColorMap = 8192, + Cursor = 16384 + } + + internal enum SendEventValues { + PointerWindow = 0, + InputFocus = 1 + } + + internal enum CreateWindowArgs { + CopyFromParent = 0, + ParentRelative = 1, + InputOutput = 1, + InputOnly = 2 + } + + internal enum Gravity { + ForgetGravity = 0, + NorthWestGravity= 1, + NorthGravity = 2, + NorthEastGravity= 3, + WestGravity = 4, + CenterGravity = 5, + EastGravity = 6, + SouthWestGravity= 7, + SouthGravity = 8, + SouthEastGravity= 9, + StaticGravity = 10 + } + + internal enum XKeySym : uint { + XK_BackSpace = 0xFF08, + XK_Tab = 0xFF09, + XK_Clear = 0xFF0B, + XK_Return = 0xFF0D, + XK_Home = 0xFF50, + XK_Left = 0xFF51, + XK_Up = 0xFF52, + XK_Right = 0xFF53, + XK_Down = 0xFF54, + XK_Page_Up = 0xFF55, + XK_Page_Down = 0xFF56, + XK_End = 0xFF57, + XK_Begin = 0xFF58, + XK_Menu = 0xFF67, + XK_Shift_L = 0xFFE1, + XK_Shift_R = 0xFFE2, + XK_Control_L = 0xFFE3, + XK_Control_R = 0xFFE4, + XK_Caps_Lock = 0xFFE5, + XK_Shift_Lock = 0xFFE6, + XK_Meta_L = 0xFFE7, + XK_Meta_R = 0xFFE8, + XK_Alt_L = 0xFFE9, + XK_Alt_R = 0xFFEA, + XK_Super_L = 0xFFEB, + XK_Super_R = 0xFFEC, + XK_Hyper_L = 0xFFED, + XK_Hyper_R = 0xFFEE, + } + + [Flags] + internal enum EventMask { + NoEventMask = 0, + KeyPressMask = 1<<0, + KeyReleaseMask = 1<<1, + ButtonPressMask = 1<<2, + ButtonReleaseMask = 1<<3, + EnterWindowMask = 1<<4, + LeaveWindowMask = 1<<5, + PointerMotionMask = 1<<6, + PointerMotionHintMask = 1<<7, + Button1MotionMask = 1<<8, + Button2MotionMask = 1<<9, + Button3MotionMask = 1<<10, + Button4MotionMask = 1<<11, + Button5MotionMask = 1<<12, + ButtonMotionMask = 1<<13, + KeymapStateMask = 1<<14, + ExposureMask = 1<<15, + VisibilityChangeMask = 1<<16, + StructureNotifyMask = 1<<17, + ResizeRedirectMask = 1<<18, + SubstructureNotifyMask = 1<<19, + SubstructureRedirectMask= 1<<20, + FocusChangeMask = 1<<21, + PropertyChangeMask = 1<<22, + ColormapChangeMask = 1<<23, + OwnerGrabButtonMask = 1<<24 + } + + internal enum GrabMode { + GrabModeSync = 0, + GrabModeAsync = 1 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XStandardColormap { + internal IntPtr colormap; + internal IntPtr red_max; + internal IntPtr red_mult; + internal IntPtr green_max; + internal IntPtr green_mult; + internal IntPtr blue_max; + internal IntPtr blue_mult; + internal IntPtr base_pixel; + internal IntPtr visualid; + internal IntPtr killid; + } + + [StructLayout(LayoutKind.Sequential, Pack=2)] + internal struct XColor { + internal IntPtr pixel; + internal ushort red; + internal ushort green; + internal ushort blue; + internal byte flags; + internal byte pad; + } + + internal enum Atom { + AnyPropertyType = 0, + XA_PRIMARY = 1, + XA_SECONDARY = 2, + XA_ARC = 3, + XA_ATOM = 4, + XA_BITMAP = 5, + XA_CARDINAL = 6, + XA_COLORMAP = 7, + XA_CURSOR = 8, + XA_CUT_BUFFER0 = 9, + XA_CUT_BUFFER1 = 10, + XA_CUT_BUFFER2 = 11, + XA_CUT_BUFFER3 = 12, + XA_CUT_BUFFER4 = 13, + XA_CUT_BUFFER5 = 14, + XA_CUT_BUFFER6 = 15, + XA_CUT_BUFFER7 = 16, + XA_DRAWABLE = 17, + XA_FONT = 18, + XA_INTEGER = 19, + XA_PIXMAP = 20, + XA_POINT = 21, + XA_RECTANGLE = 22, + XA_RESOURCE_MANAGER = 23, + XA_RGB_COLOR_MAP = 24, + XA_RGB_BEST_MAP = 25, + XA_RGB_BLUE_MAP = 26, + XA_RGB_DEFAULT_MAP = 27, + XA_RGB_GRAY_MAP = 28, + XA_RGB_GREEN_MAP = 29, + XA_RGB_RED_MAP = 30, + XA_STRING = 31, + XA_VISUALID = 32, + XA_WINDOW = 33, + XA_WM_COMMAND = 34, + XA_WM_HINTS = 35, + XA_WM_CLIENT_MACHINE = 36, + XA_WM_ICON_NAME = 37, + XA_WM_ICON_SIZE = 38, + XA_WM_NAME = 39, + XA_WM_NORMAL_HINTS = 40, + XA_WM_SIZE_HINTS = 41, + XA_WM_ZOOM_HINTS = 42, + XA_MIN_SPACE = 43, + XA_NORM_SPACE = 44, + XA_MAX_SPACE = 45, + XA_END_SPACE = 46, + XA_SUPERSCRIPT_X = 47, + XA_SUPERSCRIPT_Y = 48, + XA_SUBSCRIPT_X = 49, + XA_SUBSCRIPT_Y = 50, + XA_UNDERLINE_POSITION = 51, + XA_UNDERLINE_THICKNESS = 52, + XA_STRIKEOUT_ASCENT = 53, + XA_STRIKEOUT_DESCENT = 54, + XA_ITALIC_ANGLE = 55, + XA_X_HEIGHT = 56, + XA_QUAD_WIDTH = 57, + XA_WEIGHT = 58, + XA_POINT_SIZE = 59, + XA_RESOLUTION = 60, + XA_COPYRIGHT = 61, + XA_NOTICE = 62, + XA_FONT_NAME = 63, + XA_FAMILY_NAME = 64, + XA_FULL_NAME = 65, + XA_CAP_HEIGHT = 66, + XA_WM_CLASS = 67, + XA_WM_TRANSIENT_FOR = 68, + + XA_LAST_PREDEFINED = 68 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XScreen { + internal IntPtr ext_data; + internal IntPtr display; + internal IntPtr root; + internal int width; + internal int height; + internal int mwidth; + internal int mheight; + internal int ndepths; + internal IntPtr depths; + internal int root_depth; + internal IntPtr root_visual; + internal IntPtr default_gc; + internal IntPtr cmap; + internal IntPtr white_pixel; + internal IntPtr black_pixel; + internal int max_maps; + internal int min_maps; + internal int backing_store; + internal bool save_unders; + internal IntPtr root_input_mask; + } + + [Flags] + internal enum ChangeWindowFlags { + CWX = 1<<0, + CWY = 1<<1, + CWWidth = 1<<2, + CWHeight = 1<<3, + CWBorderWidth = 1<<4, + CWSibling = 1<<5, + CWStackMode = 1<<6 + } + + internal enum StackMode { + Above = 0, + Below = 1, + TopIf = 2, + BottomIf = 3, + Opposite = 4 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XWindowChanges { + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr sibling; + internal StackMode stack_mode; + } + + [Flags] + internal enum ColorFlags { + DoRed = 1<<0, + DoGreen = 1<<1, + DoBlue = 1<<2 + } + + internal enum NotifyMode { + NotifyNormal = 0, + NotifyGrab = 1, + NotifyUngrab = 2 + } + + internal enum NotifyDetail { + NotifyAncestor = 0, + NotifyVirtual = 1, + NotifyInferior = 2, + NotifyNonlinear = 3, + NotifyNonlinearVirtual = 4, + NotifyPointer = 5, + NotifyPointerRoot = 6, + NotifyDetailNone = 7 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MotifWmHints { + internal IntPtr flags; + internal IntPtr functions; + internal IntPtr decorations; + internal IntPtr input_mode; + internal IntPtr status; + + public override string ToString () + { + return string.Format("MotifWmHints _eventHandler; + private bool _invalidated; + private XConfigureEvent? _configure; + private IInputRoot _inputRoot; + private IMouseDevice _mouse; + private Point _position; + private IntPtr _handle; + private bool _mapped; + + class InputEventContainer + { + public RawInputEventArgs Event; + } + private Queue _inputQueue = new Queue(); + private InputEventContainer _lastEvent; + + public X11Window(AvaloniaX11Platform platform, bool popup) + { + _platform = platform; + _popup = popup; + _x11 = platform.Info; + _mouse = platform.MouseDevice; + + + XSetWindowAttributes attr = new XSetWindowAttributes(); + var valueMask = default(SetWindowValuemask); + /* + _handle = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 10, 10, 100, 100, 0, IntPtr.Zero, + IntPtr.Zero);*/ + var vinfo = _x11.MatchedVisual; + attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, vinfo.visual, 0); + attr.backing_store = 2; + attr.bit_gravity = Gravity.NorthWestGravity; + attr.win_gravity = Gravity.NorthWestGravity; + valueMask |= SetWindowValuemask.ColorMap | SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel + | SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore + | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity; + + if (popup) + { + attr.override_redirect = true; + valueMask |= SetWindowValuemask.OverrideRedirect; + } + + _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0, + (int)vinfo.depth, + (int)CreateWindowArgs.InputOutput, vinfo.visual, + new UIntPtr((uint)valueMask), ref attr); + + + Handle = new PlatformHandle(_handle, "XID"); + ClientSize = new Size(400, 400); + _eventHandler = OnEvent; + platform.Windows[_handle] = _eventHandler; + XSelectInput(_x11.Display, _handle, + new IntPtr(0xffffff + ^ (int)XEventMask.SubstructureRedirectMask + ^ (int)XEventMask.ResizeRedirectMask + ^ (int)XEventMask.PointerMotionHintMask)); + var protocols = new[] + { + _x11.Atoms.WM_DELETE_WINDOW + }; + XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length); + var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService(); + var surfaces = new List + { + new X11FramebufferSurface(_x11.DeferredDisplay, _handle) + }; + if (feature != null) + surfaces.Insert(0, + new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, + new SurfaceInfo(_x11.DeferredDisplay, _handle))); + Surfaces = surfaces.ToArray(); + UpdateWmHits(); + XFlush(_x11.Display); + } + + class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo + { + private readonly IntPtr _display; + + public SurfaceInfo(IntPtr display, IntPtr xid) + { + _display = display; + Handle = xid; + } + public IntPtr Handle { get; } + + public System.Drawing.Size PixelSize + { + get + { + XLockDisplay(_display); + + XGetGeometry(_display, Handle, out var geo); + XUnlockDisplay(_display); + return new System.Drawing.Size(geo.width, geo.height); + } + } + + public double Scaling { get; } = 1; + } + + void UpdateWmHits() + { + var functions = MotifFunctions.All; + var decorations = MotifDecorations.All; + + if (_popup || !_systemDecorations) + { + functions = 0; + decorations = 0; + } + + if (!_canResize) + { + functions ^= MotifFunctions.Resize | MotifFunctions.Maximize; + decorations ^= MotifDecorations.Maximize | MotifDecorations.ResizeH; + } + + var hints = new MotifWmHints(); + hints.flags = new IntPtr((int)(MotifFlags.Decorations | MotifFlags.Functions)); + + hints.decorations = new IntPtr((int)decorations); + hints.functions = new IntPtr((int)functions); + + XChangeProperty(_x11.Display, _handle, + _x11.Atoms._MOTIF_WM_HINTS, _x11.Atoms._MOTIF_WM_HINTS, 32, + PropertyMode.Replace, ref hints, 5); + } + + public Size ClientSize { get; private set; } + public double Scaling { get; } = 1; + public IEnumerable Surfaces { get; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public Func Closing { get; set; } + public WindowState WindowState { get; set; } + public Action WindowStateChanged { get; set; } + public Action Closed { get; set; } + public Action PositionChanged { get; set; } + + public IRenderer CreateRenderer(IRenderRoot root) => + new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); + + unsafe void OnEvent(XEvent ev) + { + if (ev.type == XEventName.MapNotify) + _mapped = true; + else if (ev.type == XEventName.UnmapNotify) + _mapped = false; + else if (ev.type == XEventName.Expose) + DoPaint(); + else if (ev.type == XEventName.FocusIn) + Activated?.Invoke(); + else if (ev.type == XEventName.FocusOut) + Deactivated?.Invoke(); + else if (ev.type == XEventName.MotionNotify) + MouseEvent(RawMouseEventType.Move, ev, ev.MotionEvent.state); + + else if (ev.type == XEventName.ButtonPress) + { + if (ev.ButtonEvent.button < 4) + MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown + : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown + : RawMouseEventType.RightButtonDown, ev, ev.ButtonEvent.state); + else + { + var delta = ev.ButtonEvent.button == 4 + ? new Vector(0, 1) + : ev.ButtonEvent.button == 5 + ? new Vector(0, -1) + : ev.ButtonEvent.button == 6 + ? new Vector(1, 0) + : new Vector(-1, 0); + ScheduleInput(new RawMouseWheelEventArgs(_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), + _inputRoot, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), delta, + TranslateModifiers(ev.ButtonEvent.state))); + } + + } + else if (ev.type == XEventName.ButtonRelease) + { + if (ev.ButtonEvent.button < 4) + MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonUp + : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonUp + : RawMouseEventType.RightButtonUp, ev, ev.ButtonEvent.state); + } + else if (ev.type == XEventName.ConfigureNotify) + { + var needEnqueue = (_configure == null); + _configure = ev.ConfigureEvent; + if (needEnqueue) + Dispatcher.UIThread.Post(() => + { + if (_configure == null) + return; + var cev = _configure.Value; + _configure = null; + var nsize = new Size(cev.width, cev.height); + var npos = new Point(cev.x, cev.y); + var changedSize = ClientSize != nsize; + var changedPos = npos != _position; + ClientSize = nsize; + _position = npos; + if (changedSize) + Resized?.Invoke(nsize); + if (changedPos) + PositionChanged?.Invoke(npos); + }); + } + else if (ev.type == XEventName.DestroyNotify) + { + if (_handle != IntPtr.Zero) + { + _platform.Windows.Remove(_handle); + _handle = IntPtr.Zero; + Closed?.Invoke(); + } + } + else if (ev.type == XEventName.ClientMessage) + { + if (ev.ClientMessageEvent.message_type == _x11.Atoms.WM_PROTOCOLS) + { + if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_DELETE_WINDOW) + { + if (Closing?.Invoke() != true) + Dispose(); + } + + } + } + } + + + + InputModifiers TranslateModifiers(XModifierMask state) + { + var rv = default(InputModifiers); + if (state.HasFlag(XModifierMask.Button1Mask)) + rv |= InputModifiers.LeftMouseButton; + if (state.HasFlag(XModifierMask.Button2Mask)) + rv |= InputModifiers.RightMouseButton; + if (state.HasFlag(XModifierMask.Button2Mask)) + rv |= InputModifiers.MiddleMouseButton; + if (state.HasFlag(XModifierMask.ShiftMask)) + rv |= InputModifiers.Shift; + if (state.HasFlag(XModifierMask.ControlMask)) + rv |= InputModifiers.Control; + if (state.HasFlag(XModifierMask.Mod1Mask)) + rv |= InputModifiers.Alt; + if (state.HasFlag(XModifierMask.Mod4Mask)) + rv |= InputModifiers.Windows; + return rv; + } + static Stopwatch St = Stopwatch.StartNew(); + private bool _systemDecorations = true; + private bool _canResize = true; + + void ScheduleInput(RawInputEventArgs args) + { + _lastEvent = new InputEventContainer() {Event = args}; + _inputQueue.Enqueue(_lastEvent); + if (_inputQueue.Count == 1) + { + Dispatcher.UIThread.Post(() => + { + while (_inputQueue.Count > 0) + { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + var ev = _inputQueue.Dequeue(); + Input?.Invoke(ev.Event); + } + }, DispatcherPriority.Input); + } + } + + void MouseEvent(RawMouseEventType type, XEvent ev, XModifierMask mods) + { + _x11.LastActivityTimestamp = ev.ButtonEvent.time; + var mev = new RawMouseEventArgs( + _mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot, + type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods)); + if(type == RawMouseEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawMouseEventArgs ma) + if (ma.Type == RawMouseEventType.Move) + { + _lastEvent.Event = mev; + return; + } + ScheduleInput(mev); + } + + void DoPaint() + { + _invalidated = false; + Paint?.Invoke(new Rect()); + } + + public void Invalidate(Rect rect) + { + if(_invalidated) + return; + _invalidated = true; + Dispatcher.UIThread.InvokeAsync(() => + { + if (_mapped) + DoPaint(); + }); + } + + public void SetInputRoot(IInputRoot inputRoot) + { + _inputRoot = inputRoot; + } + + public void Dispose() + { + if (_handle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _handle); + _platform.Windows.Remove(_handle); + _handle = IntPtr.Zero; + Closed?.Invoke(); + + } + } + + public void Show() => XMapWindow(_x11.Display, _handle); + + public void Hide() => XUnmapWindow(_x11.Display, _handle); + + + public Point PointToClient(Point point) => new Point(point.X - _position.X, point.Y - _position.Y); + + public Point PointToScreen(Point point) => new Point(point.X + _position.X, point.Y + _position.Y); + + public void SetSystemDecorations(bool enabled) + { + _systemDecorations = enabled; + UpdateWmHits(); + } + + + public void Resize(Size clientSize) + { + if (clientSize == ClientSize) + return; + var changes = new XWindowChanges(); + changes.width = (int)clientSize.Width; + changes.height = (int)clientSize.Height; + var needResize = clientSize != ClientSize; + XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWHeight | ChangeWindowFlags.CWWidth, + ref changes); + XFlush(_x11.Display); + + if (_popup && needResize) + { + ClientSize = clientSize; + Resized?.Invoke(clientSize); + } + } + + public void CanResize(bool value) + { + _canResize = value; + UpdateWmHits(); + } + + public void SetCursor(IPlatformHandle cursor) + { + } + + public IPlatformHandle Handle { get; } + + public Point Position + { + get => _position; + set + { + var changes = new XWindowChanges(); + changes.x = (int)value.X; + changes.y = (int)value.Y; + XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWX | ChangeWindowFlags.CWY, + ref changes); + XFlush(_x11.Display); + + } + } + + public IMouseDevice MouseDevice => _mouse; + + public unsafe void Activate() + { + if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) + { + + var ev = new XEvent + { + AnyEvent = + { + type = XEventName.ClientMessage, + window = _handle, + }, + ClientMessageEvent = { + message_type = _x11.Atoms._NET_ACTIVE_WINDOW, + format = 32, + ptr1 = new IntPtr(1), + ptr2 = _x11.LastActivityTimestamp + + } + }; + + XSendEvent(_x11.Display, _x11.RootWindow, false, + new IntPtr((int)(XEventMask.SubstructureRedirectMask | XEventMask.StructureNotifyMask)), ref ev); + } + else + { + XRaiseWindow(_x11.Display, _handle); + XSetInputFocus(_x11.Display, _handle, 0, IntPtr.Zero); + } + } + + + public IScreenImpl Screen { get; } = new ScreenStub(); + public Size MaxClientSize { get; } = new Size(1920, 1280); + + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public void SetTitle(string title) + { + } + + public void SetMinMaxSize(Size minSize, Size maxSize) + { + + } + + public void SetTopmost(bool value) + { + + } + + public IDisposable ShowDialog() + { + return Disposable.Empty; + } + + public void SetIcon(IWindowIconImpl icon) + { + } + + public void ShowTaskbarIcon(bool value) + { + } + + + + + } +} diff --git a/src/Avalonia.X11/XError.cs b/src/Avalonia.X11/XError.cs new file mode 100644 index 0000000000..2cc8f63c96 --- /dev/null +++ b/src/Avalonia.X11/XError.cs @@ -0,0 +1,30 @@ +using System; + +namespace Avalonia.X11 +{ + static class XError + { + private static readonly XErrorHandler s_errorHandlerDelegate = Handler; + public static XErrorEvent LastError; + static int Handler(IntPtr display, ref XErrorEvent error) + { + LastError = error; + return 0; + } + + public static void ThrowLastError(string desc) + { + var err = LastError; + LastError = new XErrorEvent(); + if (err.error_code == 0) + throw new X11Exception(desc); + throw new X11Exception(desc + ": " + err.error_code); + + } + + public static void Init() + { + XLib.XSetErrorHandler(s_errorHandlerDelegate); + } + } +} diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 88d36091ca..593e6b438f 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -1,105 +1,421 @@ using System; using System.Runtime.InteropServices; +using System.Text; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable CommentTypo +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +// ReSharper disable NotAccessedField.Global +// ReSharper disable UnusedMethodReturnValue.Global namespace Avalonia.X11 { - public static class XLib + internal static class XLib { + const string libX11 = "X11"; + + [DllImport(libX11)] + public static extern IntPtr XOpenDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern int XCloseDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XSynchronize(IntPtr display, bool onoff); + + [DllImport(libX11)] + public static extern IntPtr XCreateWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height, + int border_width, int depth, int xclass, IntPtr visual, UIntPtr valuemask, + ref XSetWindowAttributes attributes); + + [DllImport(libX11)] + public static extern IntPtr XCreateSimpleWindow(IntPtr display, IntPtr parent, int x, int y, int width, + int height, int border_width, IntPtr border, IntPtr background); + + [DllImport(libX11)] + public static extern int XMapWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XUnmapWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XMapSubindows(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XUnmapSubwindows(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern IntPtr XRootWindow(IntPtr display, int screen_number); + [DllImport(libX11)] + public static extern IntPtr XDefaultRootWindow(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XNextEvent(IntPtr display, out XEvent xevent); + + [DllImport(libX11)] + public static extern int XConnectionNumber(IntPtr diplay); + + [DllImport(libX11)] + public static extern int XPending(IntPtr diplay); + + [DllImport(libX11)] + public static extern IntPtr XSelectInput(IntPtr display, IntPtr window, IntPtr mask); + + [DllImport(libX11)] + public static extern int XDestroyWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XReparentWindow(IntPtr display, IntPtr window, IntPtr parent, int x, int y); + + [DllImport(libX11)] + public static extern int XMoveResizeWindow(IntPtr display, IntPtr window, int x, int y, int width, int height); + + [DllImport(libX11)] + public static extern int XResizeWindow(IntPtr display, IntPtr window, int width, int height); + + [DllImport(libX11)] + public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes attributes); + + [DllImport(libX11)] + public static extern int XFlush(IntPtr display); + + [DllImport(libX11)] + public static extern int XSetWMName(IntPtr display, IntPtr window, ref XTextProperty text_prop); + + [DllImport(libX11)] + public static extern int XStoreName(IntPtr display, IntPtr window, string window_name); + + [DllImport(libX11)] + public static extern int XFetchName(IntPtr display, IntPtr window, ref IntPtr window_name); + + [DllImport(libX11)] + public static extern int XSendEvent(IntPtr display, IntPtr window, bool propagate, IntPtr event_mask, + ref XEvent send_event); + + [DllImport(libX11)] + public static extern int XQueryTree(IntPtr display, IntPtr window, out IntPtr root_return, + out IntPtr parent_return, out IntPtr children_return, out int nchildren_return); + + [DllImport(libX11)] + public static extern int XFree(IntPtr data); + + [DllImport(libX11)] + public static extern int XRaiseWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern uint XLowerWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern uint XConfigureWindow(IntPtr display, IntPtr window, ChangeWindowFlags value_mask, + ref XWindowChanges values); + + [DllImport(libX11)] + public static extern IntPtr XInternAtom(IntPtr display, string atom_name, bool only_if_exists); + + [DllImport(libX11)] + public static extern int XInternAtoms(IntPtr display, string[] atom_names, int atom_count, bool only_if_exists, + IntPtr[] atoms); + + [DllImport(libX11)] + public static extern int XSetWMProtocols(IntPtr display, IntPtr window, IntPtr[] protocols, int count); + + [DllImport(libX11)] + public static extern int XGrabPointer(IntPtr display, IntPtr window, bool owner_events, EventMask event_mask, + GrabMode pointer_mode, GrabMode keyboard_mode, IntPtr confine_to, IntPtr cursor, IntPtr timestamp); + + [DllImport(libX11)] + public static extern int XUngrabPointer(IntPtr display, IntPtr timestamp); + + [DllImport(libX11)] + public static extern bool XQueryPointer(IntPtr display, IntPtr window, out IntPtr root, out IntPtr child, + out int root_x, out int root_y, out int win_x, out int win_y, out int keys_buttons); + + [DllImport(libX11)] + public static extern bool XTranslateCoordinates(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x, + int src_y, out int intdest_x_return, out int dest_y_return, out IntPtr child_return); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, out IntPtr root, out int x, out int y, + out int width, out int height, out int border_width, out int depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y, + out int width, out int height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y, + IntPtr width, IntPtr height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, IntPtr x, IntPtr y, + out int width, out int height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern uint XWarpPointer(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x, int src_y, + uint src_width, uint src_height, int dest_x, int dest_y); + + [DllImport(libX11)] + public static extern int XClearWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XClearArea(IntPtr display, IntPtr window, int x, int y, int width, int height, + bool exposures); + + // Colormaps + [DllImport(libX11)] + public static extern IntPtr XDefaultScreenOfDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern int XScreenNumberOfScreen(IntPtr display, IntPtr Screen); + + [DllImport(libX11)] + public static extern IntPtr XDefaultVisual(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern uint XDefaultDepth(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern int XDefaultScreen(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XDefaultColormap(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern int XLookupColor(IntPtr display, IntPtr Colormap, string Coloranem, + ref XColor exact_def_color, ref XColor screen_def_color); + + [DllImport(libX11)] + public static extern int XAllocColor(IntPtr display, IntPtr Colormap, ref XColor colorcell_def); + + [DllImport(libX11)] + public static extern int XSetTransientForHint(IntPtr display, IntPtr window, IntPtr prop_window); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref MotifWmHints data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref uint value, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref IntPtr value, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, uint[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, int[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, IntPtr[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, IntPtr atoms, int nelements); + + [DllImport(libX11, CharSet = CharSet.Ansi)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, string text, int text_length); + + [DllImport(libX11)] + public static extern int XDeleteProperty(IntPtr display, IntPtr window, IntPtr property); + + // Drawing + [DllImport(libX11)] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr window, IntPtr valuemask, ref XGCValues values); + + [DllImport(libX11)] + public static extern int XFreeGC(IntPtr display, IntPtr gc); + + [DllImport(libX11)] + public static extern int XSetFunction(IntPtr display, IntPtr gc, GXFunction function); + + [DllImport(libX11)] + internal static extern int XSetLineAttributes(IntPtr display, IntPtr gc, int line_width, GCLineStyle line_style, + GCCapStyle cap_style, GCJoinStyle join_style); + + [DllImport(libX11)] + public static extern int XDrawLine(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int x2, int y2); + + [DllImport(libX11)] + public static extern int XDrawRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width, + int height); + + [DllImport(libX11)] + public static extern int XFillRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width, + int height); + + [DllImport(libX11)] + public static extern int XSetWindowBackground(IntPtr display, IntPtr window, IntPtr background); + + [DllImport(libX11)] + public static extern int XCopyArea(IntPtr display, IntPtr src, IntPtr dest, IntPtr gc, int src_x, int src_y, + int width, int height, int dest_x, int dest_y); + + [DllImport(libX11)] + public static extern int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr atom, IntPtr long_offset, + IntPtr long_length, bool delete, IntPtr req_type, out IntPtr actual_type, out int actual_format, + out IntPtr nitems, out IntPtr bytes_after, ref IntPtr prop); + + [DllImport(libX11)] + public static extern int XSetInputFocus(IntPtr display, IntPtr window, RevertTo revert_to, IntPtr time); + + [DllImport(libX11)] + public static extern int XIconifyWindow(IntPtr display, IntPtr window, int screen_number); + + [DllImport(libX11)] + public static extern int XDefineCursor(IntPtr display, IntPtr window, IntPtr cursor); + + [DllImport(libX11)] + public static extern int XUndefineCursor(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XFreeCursor(IntPtr display, IntPtr cursor); + + [DllImport(libX11)] + public static extern IntPtr XCreateFontCursor(IntPtr display, CursorFontShape shape); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask, + ref XColor foreground_color, ref XColor background_color, int x_hot, int y_hot); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmapFromBitmapData(IntPtr display, IntPtr drawable, byte[] data, int width, + int height, IntPtr fg, IntPtr bg, int depth); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmap(IntPtr display, IntPtr d, int width, int height, int depth); + + [DllImport(libX11)] + public static extern IntPtr XFreePixmap(IntPtr display, IntPtr pixmap); + + [DllImport(libX11)] + public static extern int XQueryBestCursor(IntPtr display, IntPtr drawable, int width, int height, + out int best_width, out int best_height); + + [DllImport(libX11)] + public static extern IntPtr XWhitePixel(IntPtr display, int screen_no); + + [DllImport(libX11)] + public static extern IntPtr XBlackPixel(IntPtr display, int screen_no); + + [DllImport(libX11)] + public static extern void XGrabServer(IntPtr display); + + [DllImport(libX11)] + public static extern void XUngrabServer(IntPtr display); + + [DllImport(libX11)] + public static extern void XGetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints, + out IntPtr supplied_return); + + [DllImport(libX11)] + public static extern void XSetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints); + + [DllImport(libX11)] + public static extern void XSetZoomHints(IntPtr display, IntPtr window, ref XSizeHints hints); + + [DllImport(libX11)] + public static extern void XSetWMHints(IntPtr display, IntPtr window, ref XWMHints wmhints); + + [DllImport(libX11)] + public static extern int XGetIconSizes(IntPtr display, IntPtr window, out IntPtr size_list, out int count); + + [DllImport(libX11)] + public static extern IntPtr XSetErrorHandler(XErrorHandler error_handler); + + [DllImport(libX11)] + public static extern IntPtr XGetErrorText(IntPtr display, byte code, StringBuilder buffer, int length); + + [DllImport(libX11)] + public static extern int XInitThreads(); + + [DllImport(libX11)] + public static extern int XConvertSelection(IntPtr display, IntPtr selection, IntPtr target, IntPtr property, + IntPtr requestor, IntPtr time); + + [DllImport(libX11)] + public static extern IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection); + + [DllImport(libX11)] + public static extern int XSetSelectionOwner(IntPtr display, IntPtr selection, IntPtr owner, IntPtr time); + + [DllImport(libX11)] + public static extern int XSetPlaneMask(IntPtr display, IntPtr gc, IntPtr mask); + + [DllImport(libX11)] + public static extern int XSetForeground(IntPtr display, IntPtr gc, UIntPtr foreground); + + [DllImport(libX11)] + public static extern int XSetBackground(IntPtr display, IntPtr gc, UIntPtr background); + + [DllImport(libX11)] + public static extern int XBell(IntPtr display, int percent); + + [DllImport(libX11)] + public static extern int XChangeActivePointerGrab(IntPtr display, EventMask event_mask, IntPtr cursor, + IntPtr time); + + [DllImport(libX11)] + public static extern bool XFilterEvent(ref XEvent xevent, IntPtr window); + + [DllImport(libX11)] + public static extern void XkbSetDetectableAutoRepeat(IntPtr display, bool detectable, IntPtr supported); + + [DllImport(libX11)] + public static extern void XPeekEvent(IntPtr display, out XEvent xevent); - [DllImport("libX11.so.6")] - public static extern IntPtr XInitThreads(); - - [DllImport("libX11.so.6")] - public static extern IntPtr XOpenDisplay(IntPtr name); - - [DllImport("libX11.so.6")] - public static extern int XConnectionNumber(IntPtr display); + [DllImport(libX11)] + public static extern void XMatchVisualInfo(IntPtr display, int screen, int depth, int klass, out XVisualInfo info); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern IntPtr XLockDisplay(IntPtr display); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern IntPtr XUnlockDisplay(IntPtr display); - [DllImport("libX11.so.6")] - public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); - - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern int XInitImage(ref XImage image); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern int XDestroyImage(ref XImage image); - - [DllImport("libX11.so.6")] - public static extern IntPtr XSetErrorHandler(XErrorHandler handler); - [DllImport("libX11.so.6")] + [DllImport(libX11)] + public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, + int srcx, int srcy, int destx, int desty, uint width, uint height); + [DllImport(libX11)] public static extern int XSync(IntPtr display, bool discard); - [DllImport("libX11.so.6")] - public static extern int XNextEvent(IntPtr display, out XEvent ev); + [DllImport(libX11)] + public static extern IntPtr XCreateColormap(IntPtr display, IntPtr window, IntPtr visual, int create); - [DllImport("libX11.so.6")] - public static extern bool XPending(IntPtr display); - - [StructLayout(LayoutKind.Sequential)] - public struct XAnyEvent + public struct XGeometry { - - public int Type; - public ulong Serial; /* # of last request processed by server */ - public bool send_event; /* true if this came from a SendEvent request */ - public IntPtr display; /* Display the event was read from */ - public IntPtr window; /* window on which event was requested in event mask */ + public IntPtr root; + public int x; + public int y; + public int width; + public int height; + public int bw; + public int d; } - [StructLayout(LayoutKind.Explicit)] - public unsafe struct XEvent + public static bool XGetGeometry(IntPtr display, IntPtr window, out XGeometry geo) { - [FieldOffset(0)] public int Type; - [FieldOffset(0)] public XAnyEvent XAny; - [FieldOffset(0)] private fixed int pad[40]; + geo = new XGeometry(); + return XGetGeometry(display, window, out geo.root, out geo.x, out geo.y, out geo.width, out geo.height, + out geo.bw, out geo.d); } - public delegate int XErrorHandler(IntPtr display, ref XErrorEvent error); - - [DllImport("libX11.so.6")] - public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, - int srcx, int srcy, int destx, int desty, uint width, uint height); - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct XErrorEvent - { - public int type; - public IntPtr* display; /* Display the event was read from */ - public ulong serial; /* serial number of failed request */ - public byte error_code; /* error code of failed request */ - public byte request_code; /* Major op-code of failed request */ - public byte minor_code; /* Minor op-code of failed request */ - public IntPtr resourceid; /* resource id */ - } - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct XImage - { - public int width, height; /* size of image */ - public int xoffset; /* number of pixels offset in X direction */ - public int format; /* XYBitmap, XYPixmap, ZPixmap */ - public IntPtr data; /* pointer to image data */ - public int byte_order; /* data byte order, LSBFirst, MSBFirst */ - public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ - public int bitmap_bit_order; /* LSBFirst, MSBFirst */ - public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ - public int depth; /* depth of image */ - public int bytes_per_line; /* accelerator to next scanline */ - public int bits_per_pixel; /* bits per pixel (ZPixmap) */ - public ulong red_mask; /* bits in z arrangement */ - public ulong green_mask; - public ulong blue_mask; - private fixed byte funcs[128]; - } } } From 80950d0eb17136903799b56edf83b503d32aa3d3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 11:11:56 +0300 Subject: [PATCH 003/207] [X11] Use 24-bit visuals so GL drawing works on nVidia cards --- src/Avalonia.X11/X11Framebuffer.cs | 2 +- src/Avalonia.X11/X11Info.cs | 3 --- src/Avalonia.X11/X11Window.cs | 16 ++++++---------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs index ef93a7347b..2d6955f47a 100644 --- a/src/Avalonia.X11/X11Framebuffer.cs +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -34,7 +34,7 @@ namespace Avalonia.X11 image.bitmap_unit = bitsPerPixel; image.bitmap_bit_order = 0;// LSBFirst; image.bitmap_pad = bitsPerPixel; - image.depth = 32; + image.depth = 24; image.bytes_per_line = RowBytes - Width * 4; image.bits_per_pixel = bitsPerPixel; XLockDisplay(_display); diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 6ad9faffbc..ddad739dd0 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -14,7 +14,6 @@ namespace Avalonia.X11 public IntPtr BlackPixel { get; } public IntPtr RootWindow { get; } public IntPtr DefaultRootWindow { get; } - public XVisualInfo MatchedVisual { get; } public X11Atoms Atoms { get; } public IntPtr LastActivityTimestamp { get; set; } @@ -28,8 +27,6 @@ namespace Avalonia.X11 RootWindow = XRootWindow(display, DefaultScreen); DefaultRootWindow = XDefaultRootWindow(display); Atoms = new X11Atoms(display); - XMatchVisualInfo(display, DefaultScreen, 32, 4, out var info); - MatchedVisual = info; } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 944044cfa8..6bf59866f1 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -44,15 +44,11 @@ namespace Avalonia.X11 XSetWindowAttributes attr = new XSetWindowAttributes(); var valueMask = default(SetWindowValuemask); - /* - _handle = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 10, 10, 100, 100, 0, IntPtr.Zero, - IntPtr.Zero);*/ - var vinfo = _x11.MatchedVisual; - attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, vinfo.visual, 0); - attr.backing_store = 2; + + attr.backing_store = 1; attr.bit_gravity = Gravity.NorthWestGravity; attr.win_gravity = Gravity.NorthWestGravity; - valueMask |= SetWindowValuemask.ColorMap | SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel + valueMask |= SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel | SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity; @@ -63,8 +59,8 @@ namespace Avalonia.X11 } _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0, - (int)vinfo.depth, - (int)CreateWindowArgs.InputOutput, vinfo.visual, + 24, + (int)CreateWindowArgs.InputOutput, IntPtr.Zero, new UIntPtr((uint)valueMask), ref attr); @@ -278,7 +274,7 @@ namespace Avalonia.X11 rv |= InputModifiers.Windows; return rv; } - static Stopwatch St = Stopwatch.StartNew(); + private bool _systemDecorations = true; private bool _canResize = true; From 9ee771b425d3f4aa0d0a951fc861144c86096bd4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 11:12:14 +0300 Subject: [PATCH 004/207] [X11] Reference X11 backend from ControlCatalog --- samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index ec841db5b2..8410a197ab 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -8,6 +8,7 @@ + From 9b7f8b5f35a1d748158b43c55de44eea80d92e31 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 11:55:48 +0300 Subject: [PATCH 005/207] [X11] Use a child window for OpenGL rendering. Fixes tons of issues on nVidia cards --- src/Avalonia.X11/X11Window.cs | 48 +++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 6bf59866f1..f102a6abff 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -25,6 +25,7 @@ namespace Avalonia.X11 private IMouseDevice _mouse; private Point _position; private IntPtr _handle; + private IntPtr _renderHandle; private bool _mapped; class InputEventContainer @@ -62,8 +63,12 @@ namespace Avalonia.X11 24, (int)CreateWindowArgs.InputOutput, IntPtr.Zero, new UIntPtr((uint)valueMask), ref attr); - - + _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, 24, + (int)CreateWindowArgs.InputOutput, + IntPtr.Zero, + new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | + SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); + Handle = new PlatformHandle(_handle, "XID"); ClientSize = new Size(400, 400); _eventHandler = OnEvent; @@ -86,7 +91,7 @@ namespace Avalonia.X11 if (feature != null) surfaces.Insert(0, new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, - new SurfaceInfo(_x11.DeferredDisplay, _handle))); + new SurfaceInfo(_x11.DeferredDisplay, _handle, _renderHandle))); Surfaces = surfaces.ToArray(); UpdateWmHits(); XFlush(_x11.Display); @@ -95,10 +100,12 @@ namespace Avalonia.X11 class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { private readonly IntPtr _display; + private readonly IntPtr _parent; - public SurfaceInfo(IntPtr display, IntPtr xid) + public SurfaceInfo(IntPtr display, IntPtr parent, IntPtr xid) { _display = display; + _parent = parent; Handle = xid; } public IntPtr Handle { get; } @@ -108,8 +115,10 @@ namespace Avalonia.X11 get { XLockDisplay(_display); - - XGetGeometry(_display, Handle, out var geo); + XGetGeometry(_display, _parent, out var geo); + XResizeWindow(_display, Handle, geo.width, geo.height); + XFlush(_display); + XSync(_display, true); XUnlockDisplay(_display); return new System.Drawing.Size(geo.width, geo.height); } @@ -167,7 +176,10 @@ namespace Avalonia.X11 unsafe void OnEvent(XEvent ev) { if (ev.type == XEventName.MapNotify) + { _mapped = true; + XMapWindow(_x11.Display, _renderHandle); + } else if (ev.type == XEventName.UnmapNotify) _mapped = false; else if (ev.type == XEventName.Expose) @@ -232,12 +244,7 @@ namespace Avalonia.X11 } else if (ev.type == XEventName.DestroyNotify) { - if (_handle != IntPtr.Zero) - { - _platform.Windows.Remove(_handle); - _handle = IntPtr.Zero; - Closed?.Invoke(); - } + Cleanup(); } else if (ev.type == XEventName.ClientMessage) { @@ -335,6 +342,15 @@ namespace Avalonia.X11 } public void Dispose() + { + if (_handle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _handle); + Cleanup(); + } + } + + void Cleanup() { if (_handle != IntPtr.Zero) { @@ -342,10 +358,16 @@ namespace Avalonia.X11 _platform.Windows.Remove(_handle); _handle = IntPtr.Zero; Closed?.Invoke(); - + } + + if (_renderHandle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _renderHandle); + _renderHandle = IntPtr.Zero; } } + public void Show() => XMapWindow(_x11.Display, _handle); public void Hide() => XUnmapWindow(_x11.Display, _handle); From 074d8949a6900b4d8ae2cd89a7fe85dbf4483709 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 12:37:01 +0300 Subject: [PATCH 006/207] [X11] Enforce layout priority --- src/Avalonia.X11/X11Window.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index f102a6abff..deb0491168 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -240,7 +240,8 @@ namespace Avalonia.X11 Resized?.Invoke(nsize); if (changedPos) PositionChanged?.Invoke(npos); - }); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + }, DispatcherPriority.Layout); } else if (ev.type == XEventName.DestroyNotify) { From 52a0514ee112565a5e3f2d0c7e2d1e799aa7f1a0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 26 Oct 2018 22:32:21 +0300 Subject: [PATCH 007/207] [X11] BeginMove/ResizeDrag and WindowState --- src/Avalonia.X11/X11Window.cs | 157 ++++++++++++++++++++++++++++------ src/Avalonia.X11/XLib.cs | 61 ++++++++++++- 2 files changed, 189 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index deb0491168..036c7d0f34 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Linq; using System.Reactive.Disposables; using Avalonia.Controls; using Avalonia.Input; @@ -13,7 +14,7 @@ using Avalonia.Threading; using static Avalonia.X11.XLib; namespace Avalonia.X11 { - class X11Window : IWindowImpl, IPopupImpl + unsafe class X11Window : IWindowImpl, IPopupImpl { private readonly AvaloniaX11Platform _platform; private readonly bool _popup; @@ -134,7 +135,6 @@ namespace Avalonia.X11 if (_popup || !_systemDecorations) { - functions = 0; decorations = 0; } @@ -165,7 +165,6 @@ namespace Avalonia.X11 public Action Deactivated { get; set; } public Action Activated { get; set; } public Func Closing { get; set; } - public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } public Action Closed { get; set; } public Action PositionChanged { get; set; } @@ -190,7 +189,10 @@ namespace Avalonia.X11 Deactivated?.Invoke(); else if (ev.type == XEventName.MotionNotify) MouseEvent(RawMouseEventType.Move, ev, ev.MotionEvent.state); - + else if (ev.type == XEventName.PropertyNotify) + { + OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0); + } else if (ev.type == XEventName.ButtonPress) { if (ev.ButtonEvent.button < 4) @@ -260,8 +262,76 @@ namespace Avalonia.X11 } } } - - + + private WindowState _lastWindowState; + public WindowState WindowState + { + get { return _lastWindowState; } + set + { + _lastWindowState = value; + if (value == WindowState.Minimized) + { + XIconifyWindow(_x11.Display, _handle, _x11.DefaultScreen); + } + else if (value == WindowState.Maximized) + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero); + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)1, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, + _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + } + else + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero); + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, + _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + } + } + } + + private void OnPropertyChange(IntPtr atom, bool hasValue) + { + if (atom == _x11.Atoms._NET_WM_STATE) + { + WindowState state = WindowState.Normal; + if(hasValue) + { + + XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256), + false, (IntPtr)Atom.XA_ATOM, out var actualAtom, out var actualFormat, out var nitems, out var bytesAfter, + out var prop); + int maximized = 0; + var pitems = (IntPtr*)prop.ToPointer(); + for (var c = 0; c < nitems.ToInt32(); c++) + { + if (pitems[c] == _x11.Atoms._NET_WM_STATE_HIDDEN) + { + state = WindowState.Minimized; + break; + } + + if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ || + pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT) + { + maximized++; + if (maximized == 2) + { + state = WindowState.Maximized; + break; + } + } + } + XFree(prop); + } + if (_lastWindowState != state) + { + _lastWindowState = state; + WindowStateChanged?.Invoke(state); + } + } + + } + InputModifiers TranslateModifiers(XModifierMask state) { @@ -437,25 +507,8 @@ namespace Avalonia.X11 { if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) { - - var ev = new XEvent - { - AnyEvent = - { - type = XEventName.ClientMessage, - window = _handle, - }, - ClientMessageEvent = { - message_type = _x11.Atoms._NET_ACTIVE_WINDOW, - format = 32, - ptr1 = new IntPtr(1), - ptr2 = _x11.LastActivityTimestamp - - } - }; - - XSendEvent(_x11.Display, _x11.RootWindow, false, - new IntPtr((int)(XEventMask.SubstructureRedirectMask | XEventMask.StructureNotifyMask)), ref ev); + SendNetWMMessage(_x11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, _x11.LastActivityTimestamp, + IntPtr.Zero); } else { @@ -467,14 +520,64 @@ namespace Avalonia.X11 public IScreenImpl Screen { get; } = new ScreenStub(); public Size MaxClientSize { get; } = new Size(1920, 1280); - - + + + + void SendNetWMMessage(IntPtr message_type, IntPtr l0, + IntPtr? l1 = null, IntPtr? l2 = null, IntPtr? l3 = null, IntPtr? l4 = null) + { + XEvent xev; + + xev = new XEvent(); + xev.ClientMessageEvent.type = XEventName.ClientMessage; + xev.ClientMessageEvent.send_event = true; + xev.ClientMessageEvent.window = _handle; + xev.ClientMessageEvent.message_type = message_type; + xev.ClientMessageEvent.format = 32; + xev.ClientMessageEvent.ptr1 = l0; + xev.ClientMessageEvent.ptr2 = l1 ?? IntPtr.Zero; + xev.ClientMessageEvent.ptr3 = l2 ?? IntPtr.Zero; + xev.ClientMessageEvent.ptr4 = l3 ?? IntPtr.Zero; + xev.ClientMessageEvent.ptr4 = l4 ?? IntPtr.Zero; + XSendEvent(_x11.Display, _x11.RootWindow, false, + new IntPtr((int)(EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev); + + } + + void BeginMoveResize(NetWmMoveResize side) + { + var pos = GetCursorPos(_x11); + XUngrabPointer(_x11.Display, _x11.LastActivityTimestamp); + SendNetWMMessage (_x11.Atoms._NET_WM_MOVERESIZE, (IntPtr) pos.x, (IntPtr) pos.y, + (IntPtr) side, + (IntPtr) 1, (IntPtr)1); // left button + } + public void BeginMoveDrag() { + BeginMoveResize(NetWmMoveResize._NET_WM_MOVERESIZE_MOVE); } public void BeginResizeDrag(WindowEdge edge) { + var side = NetWmMoveResize._NET_WM_MOVERESIZE_CANCEL; + if (edge == WindowEdge.East) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_RIGHT; + if (edge == WindowEdge.North) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOP; + if (edge == WindowEdge.South) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOM; + if (edge == WindowEdge.West) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_LEFT; + if (edge == WindowEdge.NorthEast) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + if (edge == WindowEdge.NorthWest) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPLEFT; + if (edge == WindowEdge.SouthEast) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + if (edge == WindowEdge.SouthWest) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + BeginMoveResize(side); } public void SetTitle(string title) diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 593e6b438f..d6c4ec6593 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -12,7 +12,7 @@ using System.Text; namespace Avalonia.X11 { - internal static class XLib + internal unsafe static class XLib { const string libX11 = "X11"; @@ -262,7 +262,7 @@ namespace Avalonia.X11 [DllImport(libX11)] public static extern int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr atom, IntPtr long_offset, IntPtr long_length, bool delete, IntPtr req_type, out IntPtr actual_type, out int actual_format, - out IntPtr nitems, out IntPtr bytes_after, ref IntPtr prop); + out IntPtr nitems, out IntPtr bytes_after, out IntPtr prop); [DllImport(libX11)] public static extern int XSetInputFocus(IntPtr display, IntPtr window, RevertTo revert_to, IntPtr time); @@ -416,6 +416,63 @@ namespace Avalonia.X11 return XGetGeometry(display, window, out geo.root, out geo.x, out geo.y, out geo.width, out geo.height, out geo.bw, out geo.d); } + + public static void QueryPointer (IntPtr display, IntPtr w, out IntPtr root, out IntPtr child, + out int root_x, out int root_y, out int child_x, out int child_y, + out int mask) + { + + IntPtr c; + + XGrabServer (display); + + XQueryPointer(display, w, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + + if (root != w) + c = root; + + IntPtr child_last = IntPtr.Zero; + while (c != IntPtr.Zero) { + child_last = c; + XQueryPointer(display, c, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + } + XUngrabServer (display); + XFlush (display); + + child = child_last; + } + + public static (int x, int y) GetCursorPos(X11Info x11, IntPtr? handle = null) + { + IntPtr root; + IntPtr child; + int root_x; + int root_y; + int win_x; + int win_y; + int keys_buttons; + + + + QueryPointer(x11.Display, handle ?? x11.RootWindow, out root, out child, out root_x, out root_y, out win_x, out win_y, + out keys_buttons); + + + if (handle != null) + { + return (win_x, win_y); + } + else + { + return (root_x, root_y); + } + } + + } } From 8f80351bcce3229ebdf6b1730fa4bce1440700de Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 26 Oct 2018 22:54:01 +0300 Subject: [PATCH 008/207] [X11] Implemented cursor support --- src/Avalonia.X11/Stubs.cs | 8 ---- src/Avalonia.X11/X11Atoms.cs | 11 +----- src/Avalonia.X11/X11CursorFactory.cs | 57 ++++++++++++++++++++++++++++ src/Avalonia.X11/X11Info.cs | 2 + src/Avalonia.X11/X11Platform.cs | 2 +- src/Avalonia.X11/X11Window.cs | 8 ++++ 6 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 src/Avalonia.X11/X11CursorFactory.cs diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs index ed14ab7878..6dda7b81ca 100644 --- a/src/Avalonia.X11/Stubs.cs +++ b/src/Avalonia.X11/Stubs.cs @@ -9,14 +9,6 @@ using Avalonia.Platform; namespace Avalonia.X11 { - class CursorFactoryStub : IStandardCursorFactory - { - public IPlatformHandle GetCursor(StandardCursorType cursorType) - { - return new PlatformHandle(IntPtr.Zero, "FAKE"); - } - } - class ClipboardStub : IClipboard { private string _text; diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index e7b83f59d3..50a735b2ef 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -176,9 +176,6 @@ namespace Avalonia.X11 { public readonly IntPtr OEMTEXT; public readonly IntPtr UNICODETEXT; public readonly IntPtr TARGETS; - public readonly IntPtr PostAtom; - public readonly IntPtr HoverState; - public readonly IntPtr AsyncAtom; public X11Atoms (IntPtr display) { @@ -249,10 +246,7 @@ namespace Avalonia.X11 { "PRIMARY", "COMPOUND_TEXT", "UTF8_STRING", - "TARGETS", - "_SWF_AsyncAtom", - "_SWF_PostMessageAtom", - "_SWF_HoverAtom" }; + "TARGETS"}; IntPtr[] atoms = new IntPtr [atom_names.Length];; @@ -324,9 +318,6 @@ namespace Avalonia.X11 { OEMTEXT = atoms [off++]; UNICODETEXT = atoms [off++]; TARGETS = atoms [off++]; - AsyncAtom = atoms [off++]; - PostAtom = atoms [off++]; - HoverState = atoms [off++]; DIB = XA_PIXMAP; diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs new file mode 100644 index 0000000000..d25a0da0a2 --- /dev/null +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.X11 +{ + class X11CursorFactory : IStandardCursorFactory + { + private readonly IntPtr _display; + private Dictionary _cursors; + + private static readonly Dictionary s_mapping = + new Dictionary + { + {StandardCursorType.Arrow, CursorFontShape.XC_arrow}, + {StandardCursorType.Cross, CursorFontShape.XC_cross}, + {StandardCursorType.Hand, CursorFontShape.XC_hand1}, + {StandardCursorType.Help, CursorFontShape.XC_question_arrow}, + {StandardCursorType.Ibeam, CursorFontShape.XC_xterm}, + {StandardCursorType.No, CursorFontShape.XC_X_cursor}, + {StandardCursorType.Wait, CursorFontShape.XC_watch}, + {StandardCursorType.AppStarting, CursorFontShape.XC_watch}, + {StandardCursorType.BottomSize, CursorFontShape.XC_bottom_side}, + {StandardCursorType.DragCopy, CursorFontShape.XC_center_ptr}, + {StandardCursorType.DragLink, CursorFontShape.XC_fleur}, + {StandardCursorType.DragMove, CursorFontShape.XC_diamond_cross}, + {StandardCursorType.LeftSide, CursorFontShape.XC_left_side}, + {StandardCursorType.RightSide, CursorFontShape.XC_right_side}, + {StandardCursorType.SizeAll, CursorFontShape.XC_sizing}, + {StandardCursorType.TopSide, CursorFontShape.XC_top_side}, + {StandardCursorType.UpArrow, CursorFontShape.XC_sb_up_arrow}, + {StandardCursorType.BottomLeftCorner, CursorFontShape.XC_bottom_left_corner}, + {StandardCursorType.BottomRightCorner, CursorFontShape.XC_bottom_right_corner}, + {StandardCursorType.SizeNorthSouth, CursorFontShape.XC_sb_v_double_arrow}, + {StandardCursorType.SizeWestEast, CursorFontShape.XC_sb_h_double_arrow}, + {StandardCursorType.TopLeftCorner, CursorFontShape.XC_top_left_corner}, + {StandardCursorType.TopRightCorner, CursorFontShape.XC_top_right_corner}, + }; + + public X11CursorFactory(IntPtr display) + { + _display = display; + _cursors = Enum.GetValues(typeof(CursorFontShape)).Cast() + .ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id)); + } + + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + var handle = s_mapping.TryGetValue(cursorType, out var shape) + ? _cursors[shape] + : _cursors[CursorFontShape.XC_arrow]; + return new PlatformHandle(handle, "XCURSOR"); + } + } +} diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index ddad739dd0..527cdc31ef 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -14,6 +14,7 @@ namespace Avalonia.X11 public IntPtr BlackPixel { get; } public IntPtr RootWindow { get; } public IntPtr DefaultRootWindow { get; } + public IntPtr DefaultCursor { get; } public X11Atoms Atoms { get; } public IntPtr LastActivityTimestamp { get; set; } @@ -25,6 +26,7 @@ namespace Avalonia.X11 DefaultScreen = XDefaultScreen(display); BlackPixel = XBlackPixel(display, DefaultScreen); RootWindow = XRootWindow(display, DefaultScreen); + DefaultCursor = XCreateFontCursor(display, CursorFontShape.XC_arrow); DefaultRootWindow = XDefaultRootWindow(display); Atoms = new X11Atoms(display); } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 33497be411..b3ffc2a30b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -36,7 +36,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) .Bind().ToFunc(() => KeyboardDevice) - .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToConstant(new X11CursorFactory(Display)) .Bind().ToSingleton() .Bind().ToConstant(new PlatformSettingsStub()) .Bind().ToConstant(new SystemDialogsStub()) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 036c7d0f34..82564e98e0 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -482,6 +482,14 @@ namespace Avalonia.X11 public void SetCursor(IPlatformHandle cursor) { + if (cursor == null) + XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor); + else + { + if (cursor.HandleDescriptor != "XCURSOR") + throw new ArgumentException("Expected XCURSOR handle type"); + XDefineCursor(_x11.Display, _handle, cursor.Handle); + } } public IPlatformHandle Handle { get; } From 2ad8e3162bd671ad02b0dff4700f83df995e8846 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 27 Oct 2018 13:34:11 +0300 Subject: [PATCH 009/207] [X11] MinMax size/SetTopmost/ShowTaskbarIcon/Title --- src/Avalonia.X11/X11Atoms.cs | 150 +++------------------------------- src/Avalonia.X11/X11Window.cs | 56 ++++++++++--- src/Avalonia.X11/XLib.cs | 3 + 3 files changed, 58 insertions(+), 151 deletions(-) diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index 50a735b2ef..51a4fc52f0 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -22,6 +22,7 @@ // using System; +using System.Linq; using static Avalonia.X11.XLib; // ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable IdentifierTypo @@ -172,158 +173,27 @@ namespace Avalonia.X11 { public readonly IntPtr _NET_WM_WINDOW_TYPE_NORMAL; public readonly IntPtr CLIPBOARD; public readonly IntPtr PRIMARY; - public readonly IntPtr DIB; public readonly IntPtr OEMTEXT; public readonly IntPtr UNICODETEXT; public readonly IntPtr TARGETS; + public readonly IntPtr UTF8_STRING; public X11Atoms (IntPtr display) { // make sure this array stays in sync with the statements below - string [] atom_names = new string[] { - "WM_PROTOCOLS", - "WM_DELETE_WINDOW", - "WM_TAKE_FOCUS", - "_NET_SUPPORTED", - "_NET_CLIENT_LIST", - "_NET_NUMBER_OF_DESKTOPS", - "_NET_DESKTOP_GEOMETRY", - "_NET_DESKTOP_VIEWPORT", - "_NET_CURRENT_DESKTOP", - "_NET_DESKTOP_NAMES", - "_NET_ACTIVE_WINDOW", - "_NET_WORKAREA", - "_NET_SUPPORTING_WM_CHECK", - "_NET_VIRTUAL_ROOTS", - "_NET_DESKTOP_LAYOUT", - "_NET_SHOWING_DESKTOP", - "_NET_CLOSE_WINDOW", - "_NET_MOVERESIZE_WINDOW", - "_NET_WM_MOVERESIZE", - "_NET_RESTACK_WINDOW", - "_NET_REQUEST_FRAME_EXTENTS", - "_NET_WM_NAME", - "_NET_WM_VISIBLE_NAME", - "_NET_WM_ICON_NAME", - "_NET_WM_VISIBLE_ICON_NAME", - "_NET_WM_DESKTOP", - "_NET_WM_WINDOW_TYPE", - "_NET_WM_STATE", - "_NET_WM_ALLOWED_ACTIONS", - "_NET_WM_STRUT", - "_NET_WM_STRUT_PARTIAL", - "_NET_WM_ICON_GEOMETRY", - "_NET_WM_ICON", - "_NET_WM_PID", - "_NET_WM_HANDLED_ICONS", - "_NET_WM_USER_TIME", - "_NET_FRAME_EXTENTS", - "_NET_WM_PING", - "_NET_WM_SYNC_REQUEST", - "_NET_SYSTEM_TRAY_OPCODE", - "_NET_SYSTEM_TRAY_ORIENTATION", - "_NET_WM_STATE_MAXIMIZED_HORZ", - "_NET_WM_STATE_MAXIMIZED_VERT", - "_NET_WM_STATE_HIDDEN", - "_XEMBED", - "_XEMBED_INFO", - "_MOTIF_WM_HINTS", - "_NET_WM_STATE_SKIP_TASKBAR", - "_NET_WM_STATE_ABOVE", - "_NET_WM_STATE_MODAL", - "_NET_WM_CONTEXT_HELP", - "_NET_WM_WINDOW_OPACITY", - "_NET_WM_WINDOW_TYPE_DESKTOP", - "_NET_WM_WINDOW_TYPE_DOCK", - "_NET_WM_WINDOW_TYPE_TOOLBAR", - "_NET_WM_WINDOW_TYPE_MENU", - "_NET_WM_WINDOW_TYPE_UTILITY", - "_NET_WM_WINDOW_TYPE_DIALOG", - "_NET_WM_WINDOW_TYPE_SPLASH", - "_NET_WM_WINDOW_TYPE_NORMAL", - "CLIPBOARD", - "PRIMARY", - "COMPOUND_TEXT", - "UTF8_STRING", - "TARGETS"}; - IntPtr[] atoms = new IntPtr [atom_names.Length];; + var fields = typeof(X11Atoms).GetFields() + .Where(f => f.FieldType == typeof(IntPtr) && (IntPtr)f.GetValue(this) == IntPtr.Zero).ToArray(); + var atomNames = fields.Select(f => f.Name).ToArray(); + + IntPtr[] atoms = new IntPtr [atomNames.Length];; - XInternAtoms (display, atom_names, atom_names.Length, false, atoms); + XInternAtoms (display, atomNames, atomNames.Length, true, atoms); - int off = 0; - WM_PROTOCOLS = atoms [off++]; - WM_DELETE_WINDOW = atoms [off++]; - WM_TAKE_FOCUS = atoms [off++]; - _NET_SUPPORTED = atoms [off++]; - _NET_CLIENT_LIST = atoms [off++]; - _NET_NUMBER_OF_DESKTOPS = atoms [off++]; - _NET_DESKTOP_GEOMETRY = atoms [off++]; - _NET_DESKTOP_VIEWPORT = atoms [off++]; - _NET_CURRENT_DESKTOP = atoms [off++]; - _NET_DESKTOP_NAMES = atoms [off++]; - _NET_ACTIVE_WINDOW = atoms [off++]; - _NET_WORKAREA = atoms [off++]; - _NET_SUPPORTING_WM_CHECK = atoms [off++]; - _NET_VIRTUAL_ROOTS = atoms [off++]; - _NET_DESKTOP_LAYOUT = atoms [off++]; - _NET_SHOWING_DESKTOP = atoms [off++]; - _NET_CLOSE_WINDOW = atoms [off++]; - _NET_MOVERESIZE_WINDOW = atoms [off++]; - _NET_WM_MOVERESIZE = atoms [off++]; - _NET_RESTACK_WINDOW = atoms [off++]; - _NET_REQUEST_FRAME_EXTENTS = atoms [off++]; - _NET_WM_NAME = atoms [off++]; - _NET_WM_VISIBLE_NAME = atoms [off++]; - _NET_WM_ICON_NAME = atoms [off++]; - _NET_WM_VISIBLE_ICON_NAME = atoms [off++]; - _NET_WM_DESKTOP = atoms [off++]; - _NET_WM_WINDOW_TYPE = atoms [off++]; - _NET_WM_STATE = atoms [off++]; - _NET_WM_ALLOWED_ACTIONS = atoms [off++]; - _NET_WM_STRUT = atoms [off++]; - _NET_WM_STRUT_PARTIAL = atoms [off++]; - _NET_WM_ICON_GEOMETRY = atoms [off++]; - _NET_WM_ICON = atoms [off++]; - _NET_WM_PID = atoms [off++]; - _NET_WM_HANDLED_ICONS = atoms [off++]; - _NET_WM_USER_TIME = atoms [off++]; - _NET_FRAME_EXTENTS = atoms [off++]; - _NET_WM_PING = atoms [off++]; - _NET_WM_SYNC_REQUEST = atoms [off++]; - _NET_SYSTEM_TRAY_OPCODE = atoms [off++]; - _NET_SYSTEM_TRAY_ORIENTATION = atoms [off++]; - _NET_WM_STATE_MAXIMIZED_HORZ = atoms [off++]; - _NET_WM_STATE_MAXIMIZED_VERT = atoms [off++]; - _NET_WM_STATE_HIDDEN = atoms [off++]; - _XEMBED = atoms [off++]; - _XEMBED_INFO = atoms [off++]; - _MOTIF_WM_HINTS = atoms [off++]; - _NET_WM_STATE_SKIP_TASKBAR = atoms [off++]; - _NET_WM_STATE_ABOVE = atoms [off++]; - _NET_WM_STATE_MODAL = atoms [off++]; - _NET_WM_CONTEXT_HELP = atoms [off++]; - _NET_WM_WINDOW_OPACITY = atoms [off++]; - _NET_WM_WINDOW_TYPE_DESKTOP = atoms [off++]; - _NET_WM_WINDOW_TYPE_DOCK = atoms [off++]; - _NET_WM_WINDOW_TYPE_TOOLBAR = atoms [off++]; - _NET_WM_WINDOW_TYPE_MENU = atoms [off++]; - _NET_WM_WINDOW_TYPE_UTILITY = atoms [off++]; - _NET_WM_WINDOW_TYPE_DIALOG = atoms [off++]; - _NET_WM_WINDOW_TYPE_SPLASH = atoms [off++]; - _NET_WM_WINDOW_TYPE_NORMAL = atoms [off++]; - CLIPBOARD = atoms [off++]; - PRIMARY = atoms [off++]; - OEMTEXT = atoms [off++]; - UNICODETEXT = atoms [off++]; - TARGETS = atoms [off++]; + for (var c = 0; c < fields.Length; c++) + fields[c].SetValue(this, atoms[c]); - DIB = XA_PIXMAP; - - var defScreen = XDefaultScreen(display); - // XXX multi screen stuff here - _NET_SYSTEM_TRAY_S = XInternAtom (display, "_NET_SYSTEM_TRAY_S" + defScreen.ToString(), false); } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 82564e98e0..523d4dae29 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; +using System.Text; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; @@ -94,7 +95,7 @@ namespace Avalonia.X11 new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, new SurfaceInfo(_x11.DeferredDisplay, _handle, _renderHandle))); Surfaces = surfaces.ToArray(); - UpdateWmHits(); + UpdateMotifHits(); XFlush(_x11.Display); } @@ -128,7 +129,7 @@ namespace Avalonia.X11 public double Scaling { get; } = 1; } - void UpdateWmHits() + void UpdateMotifHits() { var functions = MotifFunctions.All; var decorations = MotifDecorations.All; @@ -154,13 +155,36 @@ namespace Avalonia.X11 _x11.Atoms._MOTIF_WM_HINTS, _x11.Atoms._MOTIF_WM_HINTS, 32, PropertyMode.Replace, ref hints, 5); } + + void UpdateSizeHits() + { + var hints = new XSizeHints(); + hints.min_width = (int)_minMaxSize.minSize.Width; + hints.min_height = (int)_minMaxSize.minSize.Height; + hints.height_inc = hints.width_inc = 1; + var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc; + // People might be passing double.MaxValue + if (_minMaxSize.maxSize.Width < 100000 && _minMaxSize.maxSize.Height < 100000) + { + + hints.max_width = (int)Math.Max(100000, _minMaxSize.maxSize.Width); + hints.max_height = (int)Math.Max(100000, _minMaxSize.maxSize.Height); + flags |= XSizeHintsFlags.PMaxSize; + } + + hints.flags = (IntPtr)flags; + + XSetWMNormalHints(_x11.Display, _handle, ref hints); + } public Size ClientSize { get; private set; } + //TODO public double Scaling { get; } = 1; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } public Action Resized { get; set; } + //TODO public Action ScalingChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } @@ -355,6 +379,7 @@ namespace Avalonia.X11 private bool _systemDecorations = true; private bool _canResize = true; + private (Size minSize, Size maxSize) _minMaxSize; void ScheduleInput(RawInputEventArgs args) { @@ -451,7 +476,7 @@ namespace Avalonia.X11 public void SetSystemDecorations(bool enabled) { _systemDecorations = enabled; - UpdateWmHits(); + UpdateMotifHits(); } @@ -477,7 +502,7 @@ namespace Avalonia.X11 public void CanResize(bool value) { _canResize = value; - UpdateWmHits(); + UpdateMotifHits(); } public void SetCursor(IPlatformHandle cursor) @@ -590,33 +615,42 @@ namespace Avalonia.X11 public void SetTitle(string title) { + var data = Encoding.UTF8.GetBytes(title); + fixed (void* pdata = data) + { + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8, + PropertyMode.Replace, pdata, data.Length); + XStoreName(_x11.Display, _handle, title); + } } public void SetMinMaxSize(Size minSize, Size maxSize) { - + _minMaxSize = (minSize, maxSize); + UpdateSizeHits(); } public void SetTopmost(bool value) { - + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, + (IntPtr)(value ? 1 : 0), _x11.Atoms._NET_WM_STATE_ABOVE, IntPtr.Zero); } public IDisposable ShowDialog() { + // TODO return Disposable.Empty; } public void SetIcon(IWindowIconImpl icon) { + //TODO } public void ShowTaskbarIcon(bool value) { - } - - - - + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, + (IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero); + } } } diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index d6c4ec6593..0f27191f88 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -215,6 +215,9 @@ namespace Avalonia.X11 [DllImport(libX11)] public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, int format, PropertyMode mode, IntPtr[] data, int nelements); + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, void* data, int nelements); [DllImport(libX11)] public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, From b515f7a30e6901c54b4e4e1aa1fcbef9f917ca9e Mon Sep 17 00:00:00 2001 From: ahopper Date: Fri, 2 Nov 2018 17:09:08 +0000 Subject: [PATCH 010/207] allow for pointerover control being removed from visual tree --- src/Avalonia.Input/MouseDevice.cs | 54 +++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index e581772978..893ffe491f 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -298,21 +298,48 @@ namespace Avalonia.Input Contract.Requires(device != null); Contract.Requires(root != null); - var element = root.PointerOverElement; + var element = root.PointerOverElement; var e = new PointerEventArgs { RoutedEvent = InputElement.PointerLeaveEvent, Device = device, }; - while (element != null) + if (element!=null && !element.IsAttachedToVisualTree) + { + // element has been removed from visual tree so do top down cleanup + if (root.IsPointerOver) + ClearChildrenPointerOver(e, root,true); + } + else + { + while (element != null) + { + e.Source = element; + e.Handled = false; + element.RaiseEvent(e); + element = (IInputElement)element.VisualParent; + } + } + root.PointerOverElement = null; + } + + private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element,bool clearRoot) + { + foreach (IInputElement el in element.VisualChildren) + { + if (el.IsPointerOver) + { + ClearChildrenPointerOver(e, el, true); + break; + } + } + if(clearRoot) { e.Source = element; + e.Handled = false; element.RaiseEvent(e); - element = (IInputElement)element.VisualParent; } - - root.PointerOverElement = null; } private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p) @@ -361,12 +388,19 @@ namespace Avalonia.Input el = root.PointerOverElement; e.RoutedEvent = InputElement.PointerLeaveEvent; - while (el != null && el != branch) + if (el!=null && branch!=null && !el.IsAttachedToVisualTree) { - e.Source = el; - e.Handled = false; - el.RaiseEvent(e); - el = (IInputElement)el.VisualParent; + ClearChildrenPointerOver(e,branch,false); + } + else + { + while (el != null && el != branch) + { + e.Source = el; + e.Handled = false; + el.RaiseEvent(e); + el = (IInputElement)el.VisualParent; + } } el = root.PointerOverElement = element; From b15996a091f06094b49a0fb6c2d140957f8ac96e Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 7 Nov 2018 08:00:09 +0000 Subject: [PATCH 011/207] fix hittest exception during scene changes --- src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index ffa0b0bcc5..ad4c475d89 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph private IEnumerable HitTest(IVisualNode node, Point p, Rect? clip, Func filter) { - if (filter?.Invoke(node.Visual) != false) + if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree) { var clipped = false; @@ -186,7 +186,7 @@ namespace Avalonia.Rendering.SceneGraph } } - if (node.HitTest(p) && node.Visual.IsAttachedToVisualTree) + if (node.HitTest(p)) { yield return node.Visual; } From 3f7d7b2a6542cc16279625e496d9bc0f180a9c0e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Oct 2018 10:38:03 +0200 Subject: [PATCH 012/207] Don't call IsRegistered in PropertyEqualsSelector. Was showing up in the profiling and it's not even correct anymore, as any property can be set on any object now, registered or not. --- src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index 25f12ffa57..541e8646b1 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -72,11 +72,7 @@ namespace Avalonia.Styling /// protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) { - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(control, _property)) - { - return SelectorMatch.False; - } - else if (subscribe) + if (subscribe) { return new SelectorMatch(control.GetObservable(_property).Select(v => Equals(v ?? string.Empty, _value))); } From 94b2e2becadb44054b755a8627cdfc45bf6a28d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Oct 2018 00:28:06 +0200 Subject: [PATCH 013/207] Cache the styles for control types. A lot of time was being spent calling `Attach` on styles that will never match a control. On the first pass, cache the styles that can match a particular control type and use this cache to bypass styles that will never match on subsequent passes. --- src/Avalonia.Styling/Styling/ChildSelector.cs | 20 ++++- .../Styling/DescendentSelector.cs | 16 ++-- src/Avalonia.Styling/Styling/IStyle.cs | 7 +- .../Styling/PropertyEqualsSelector.cs | 6 +- src/Avalonia.Styling/Styling/Selector.cs | 39 ++++++--- src/Avalonia.Styling/Styling/SelectorMatch.cs | 86 ++++++++++++++----- src/Avalonia.Styling/Styling/Style.cs | 24 +++--- src/Avalonia.Styling/Styling/Styles.cs | 45 +++++++++- .../Styling/TemplateSelector.cs | 5 +- .../Styling/TypeNameAndClassSelector.cs | 19 ++-- .../Styling/StyleInclude.cs | 6 +- .../SelectorTests_Child.cs | 8 +- .../SelectorTests_Class.cs | 23 ++--- .../SelectorTests_Descendent.cs | 10 +-- .../SelectorTests_Multiple.cs | 40 ++++++++- .../SelectorTests_Name.cs | 6 +- .../SelectorTests_OfType.cs | 15 +++- .../SelectorTests_PropertyEquals.cs | 11 ++- .../SelectorTests_Template.cs | 31 +++++-- 19 files changed, 313 insertions(+), 104 deletions(-) diff --git a/src/Avalonia.Styling/Styling/ChildSelector.cs b/src/Avalonia.Styling/Styling/ChildSelector.cs index bcbf358721..8b1d2a8553 100644 --- a/src/Avalonia.Styling/Styling/ChildSelector.cs +++ b/src/Avalonia.Styling/Styling/ChildSelector.cs @@ -24,6 +24,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _parent.InTemplate; + /// + public override bool IsCombinator => true; + /// public override Type TargetType => null; @@ -43,11 +46,24 @@ namespace Avalonia.Styling if (controlParent != null) { - return _parent.Match((IStyleable)controlParent, subscribe); + var parentMatch = _parent.Match((IStyleable)controlParent, subscribe); + + if (parentMatch.Result == SelectorMatchResult.Sometimes) + { + return parentMatch; + } + else if (parentMatch.IsMatch) + { + return SelectorMatch.AlwaysThisInstance; + } + else + { + return SelectorMatch.NeverThisInstance; + } } else { - return SelectorMatch.False; + return SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/DescendentSelector.cs b/src/Avalonia.Styling/Styling/DescendentSelector.cs index 943b3f0161..a81908f23d 100644 --- a/src/Avalonia.Styling/Styling/DescendentSelector.cs +++ b/src/Avalonia.Styling/Styling/DescendentSelector.cs @@ -22,6 +22,9 @@ namespace Avalonia.Styling _parent = parent; } + /// + public override bool IsCombinator => true; + /// public override bool InTemplate => _parent.InTemplate; @@ -51,16 +54,13 @@ namespace Avalonia.Styling { var match = _parent.Match((IStyleable)c, subscribe); - if (match.ImmediateResult != null) + if (match.Result == SelectorMatchResult.Sometimes) { - if (match.ImmediateResult == true) - { - return SelectorMatch.True; - } + descendantMatches.Add(match.Activator); } - else + else if (match.IsMatch) { - descendantMatches.Add(match.ObservableResult); + return SelectorMatch.AlwaysThisInstance; } } } @@ -71,7 +71,7 @@ namespace Avalonia.Styling } else { - return SelectorMatch.False; + return SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index 3cf8491ae3..da2a08f04d 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -17,7 +17,12 @@ namespace Avalonia.Styling /// /// The control that contains this style. May be null. /// - void Attach(IStyleable control, IStyleHost container); + /// + /// True if the style can match a control of type + /// (even if it does not match this control specifically); false if the style + /// can never match. + /// + bool Attach(IStyleable control, IStyleHost container); void Detach(); } diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index 541e8646b1..cfc0998fe0 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -30,6 +30,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _previous?.InTemplate ?? false; + /// + public override bool IsCombinator => false; + /// /// Gets the name of the control to match. /// @@ -78,7 +81,8 @@ namespace Avalonia.Styling } else { - return new SelectorMatch((control.GetValue(_property) ?? string.Empty).Equals(_value)); + var result = (control.GetValue(_property) ?? string.Empty).Equals(_value); + return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index 24c6be9b7a..af209ea970 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -17,6 +17,14 @@ namespace Avalonia.Styling /// public abstract bool InTemplate { get; } + /// + /// Gets a value indicating whether this selector is a combinator. + /// + /// + /// A combinator is a selector such as Child or Descendent which links simple selectors. + /// + public abstract bool IsCombinator { get; } + /// /// Gets the target type of the selector, if available. /// @@ -33,25 +41,32 @@ namespace Avalonia.Styling /// A . public SelectorMatch Match(IStyleable control, bool subscribe = true) { - List> inputs = new List>(); - Selector selector = this; + var inputs = new List>(); + var selector = this; + var alwaysThisType = true; + var hitCombinator = false; while (selector != null) { - if (selector.InTemplate && control.TemplatedParent == null) - { - return SelectorMatch.False; - } + hitCombinator |= selector.IsCombinator; var match = selector.Evaluate(control, subscribe); - if (match.ImmediateResult == false) + if (!match.IsMatch) + { + return hitCombinator ? SelectorMatch.NeverThisInstance : match; + } + else if (selector.InTemplate && control.TemplatedParent == null) + { + return SelectorMatch.NeverThisInstance; + } + else if (match.Result == SelectorMatchResult.AlwaysThisInstance) { - return match; + alwaysThisType = false; } - else if (match.ObservableResult != null) + else if (match.Result == SelectorMatchResult.Sometimes) { - inputs.Add(match.ObservableResult); + inputs.Add(match.Activator); } selector = selector.MovePrevious(); @@ -63,7 +78,9 @@ namespace Avalonia.Styling } else { - return SelectorMatch.True; + return alwaysThisType && !hitCombinator ? + SelectorMatch.AlwaysThisType : + SelectorMatch.AlwaysThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/SelectorMatch.cs b/src/Avalonia.Styling/Styling/SelectorMatch.cs index f325578389..ca73c1f727 100644 --- a/src/Avalonia.Styling/Styling/SelectorMatch.cs +++ b/src/Avalonia.Styling/Styling/SelectorMatch.cs @@ -5,51 +5,95 @@ using System; namespace Avalonia.Styling { + /// + /// Describes how a matches a control and its type. + /// + public enum SelectorMatchResult + { + /// + /// The selector never matches this type. + /// + NeverThisType, + + /// + /// The selector never matches this instance, but can match this type. + /// + NeverThisInstance, + + /// + /// The selector always matches this type. + /// + AlwaysThisType, + + /// + /// The selector always matches this instance, but doesn't always match this type. + /// + AlwaysThisInstance, + + /// + /// The selector matches this instance based on the . + /// + Sometimes, + } + /// /// Holds the result of a match. /// /// - /// There are two types of selectors - ones whose match can never change for a particular - /// control (such as ) and ones whose result can - /// change over time (such as . For the first - /// category of selectors, the value of will be set but for the - /// second, will be null and will - /// hold an observable which tracks the match. + /// A selector match describes whether and how a matches a control, and + /// in addition whether the selector can ever match a control of the same type. /// public class SelectorMatch { - public static readonly SelectorMatch False = new SelectorMatch(false); + /// + /// A selector match with the result of / + /// + public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType); - public static readonly SelectorMatch True = new SelectorMatch(true); + /// + /// A selector match with the result of / + /// + public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance); /// - /// Initializes a new instance of the class. + /// A selector match with the result of / /// - /// The immediate match value. - public SelectorMatch(bool match) - { - ImmediateResult = match; - } + public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType); /// - /// Initializes a new instance of the class. + /// Gets a selector match with the result of / /// - /// The observable match value. + public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance); + + /// + /// Initializes a new instance of the class with a + /// result. + /// + /// The match activator. public SelectorMatch(IObservable match) { - ObservableResult = match; + Contract.Requires(match != null); + + Result = SelectorMatchResult.Sometimes; + Activator = match; } + private SelectorMatch(SelectorMatchResult result) => Result = result; + /// - /// Gets the immediate result of the selector match, in the case of selectors that cannot - /// change over time. + /// Gets a value indicating whether the match was positive. + /// + public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType; + + /// + /// Gets the result of the match. /// - public bool? ImmediateResult { get; } + public SelectorMatchResult Result { get; } /// /// Gets an observable which tracks the selector match, in the case of selectors that can /// change over time. /// - public IObservable ObservableResult { get; } + public IObservable Activator { get; } } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 23c318a809..27fad58346 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -106,20 +106,14 @@ namespace Avalonia.Styling /// bool IResourceProvider.HasResources => _resources?.Count > 0; - /// - /// Attaches the style to a control if the style's selector matches. - /// - /// The control to attach to. - /// - /// The control that contains this style. May be null. - /// - public void Attach(IStyleable control, IStyleHost container) + /// + public bool Attach(IStyleable control, IStyleHost container) { if (Selector != null) { var match = Selector.Match(control); - if (match.ImmediateResult != false) + if (match.IsMatch) { var controlSubscriptions = GetSubscriptions(control); @@ -129,9 +123,10 @@ namespace Avalonia.Styling { foreach (var animation in Animations) { - IObservable obsMatch = match.ObservableResult; + var obsMatch = match.Activator; - if (match.ImmediateResult == true) + if (match.Result == SelectorMatchResult.AlwaysThisType || + match.Result == SelectorMatchResult.AlwaysThisInstance) { obsMatch = Observable.Return(true); } @@ -143,13 +138,15 @@ namespace Avalonia.Styling foreach (var setter in Setters) { - var sub = setter.Apply(this, control, match.ObservableResult); + var sub = setter.Apply(this, control, match.Activator); subs.Add(sub); } controlSubscriptions.Add(subs); Subscriptions.Add(subs); } + + return match.Result != SelectorMatchResult.NeverThisType; } else if (control == container) { @@ -165,7 +162,10 @@ namespace Avalonia.Styling controlSubscriptions.Add(subs); Subscriptions.Add(subs); + return true; } + + return false; } /// diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 22ed8bb241..51499b737a 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.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 System.Linq; using Avalonia.Collections; using Avalonia.Controls; @@ -15,6 +16,7 @@ namespace Avalonia.Styling { private IResourceNode _parent; private IResourceDictionary _resources; + private Dictionary> _cache; public Styles() { @@ -34,6 +36,7 @@ namespace Avalonia.Styling } x.ResourcesChanged += SubResourceChanged; + _cache = null; }, x => { @@ -49,6 +52,7 @@ namespace Avalonia.Styling } x.ResourcesChanged -= SubResourceChanged; + _cache = null; }, () => { }); } @@ -97,11 +101,46 @@ namespace Avalonia.Styling /// /// The control that contains this style. May be null. /// - public void Attach(IStyleable control, IStyleHost container) + public bool Attach(IStyleable control, IStyleHost container) { - foreach (IStyle style in this) + if (_cache == null) + { + _cache = new Dictionary>(); + } + + if (_cache.TryGetValue(control.StyleKey, out var cached)) + { + if (cached != null) + { + foreach (var style in cached) + { + style.Attach(control, container); + } + + return true; + } + + return false; + } + else { - style.Attach(control, container); + List result = null; + + foreach (var style in this) + { + if (style.Attach(control, container)) + { + if (result == null) + { + result = new List(); + } + + result.Add(style); + } + } + + _cache.Add(control.StyleKey, result); + return result != null; } } diff --git a/src/Avalonia.Styling/Styling/TemplateSelector.cs b/src/Avalonia.Styling/Styling/TemplateSelector.cs index 6919b4ad27..7530339883 100644 --- a/src/Avalonia.Styling/Styling/TemplateSelector.cs +++ b/src/Avalonia.Styling/Styling/TemplateSelector.cs @@ -23,6 +23,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => true; + /// + public override bool IsCombinator => true; + /// public override Type TargetType => null; @@ -46,7 +49,7 @@ namespace Avalonia.Styling "Cannot call Template selector on control with null TemplatedParent."); } - return _parent.Match(templatedParent, subscribe) ?? SelectorMatch.True; + return _parent.Match(templatedParent, subscribe); } protected override Selector MovePrevious() => null; diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs index 94c0b75c6e..cd019e6535 100644 --- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs @@ -68,6 +68,9 @@ namespace Avalonia.Styling /// public override Type TargetType => _targetType ?? _previous?.TargetType; + /// + public override bool IsCombinator => false; + /// /// Whether the selector matches the concrete or any object which /// implements . @@ -101,21 +104,23 @@ namespace Avalonia.Styling { if (controlType != TargetType) { - return SelectorMatch.False; + return SelectorMatch.NeverThisType; } } else { if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo())) { - return SelectorMatch.False; + return SelectorMatch.NeverThisType; } } } - if (Name != null && control.Name != Name) + if (Name != null) { - return SelectorMatch.False; + return control.Name == Name ? + SelectorMatch.AlwaysThisInstance : + SelectorMatch.NeverThisInstance; } if (_classes.IsValueCreated && _classes.Value.Count > 0) @@ -127,12 +132,14 @@ namespace Avalonia.Styling } else { - return new SelectorMatch(Matches(control.Classes)); + return Matches(control.Classes) ? + SelectorMatch.AlwaysThisInstance : + SelectorMatch.NeverThisInstance; } } else { - return SelectorMatch.True; + return SelectorMatch.AlwaysThisType; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index fc2656f236..9273011cb9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -62,12 +62,14 @@ namespace Avalonia.Markup.Xaml.Styling IResourceNode IResourceNode.ResourceParent => _parent; /// - public void Attach(IStyleable control, IStyleHost container) + public bool Attach(IStyleable control, IStyleHost container) { if (Source != null) { - Loaded.Attach(control, container); + return Loaded.Attach(control, container); } + + return false; } public void Detach() diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index d837f2cb2f..0561837ad1 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -27,7 +27,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Child().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -42,7 +42,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Child().OfType(); - Assert.False(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(child).Result); } [Fact] @@ -54,7 +54,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Child().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; var result = new List(); Assert.False(await activator.Take(1)); @@ -70,7 +70,7 @@ namespace Avalonia.Styling.UnitTests var control = new TestLogical3(); var selector = default(Selector).OfType().Child().OfType(); - Assert.False(selector.Match(control).ImmediateResult); + Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(control).Result); } [Fact] diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs index 75599925b7..496998ecd9 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs @@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.True(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.True(await match.Activator.Take(1)); } [Fact] @@ -54,9 +55,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.False(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.False(await match.Activator.Take(1)); } [Fact] @@ -69,9 +71,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.True(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.True(await match.Activator.Take(1)); } [Fact] @@ -80,7 +83,7 @@ namespace Avalonia.Styling.UnitTests var control = new Control1(); var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.False(await activator.Take(1)); control.Classes.Add("foo"); @@ -96,7 +99,7 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.True(await activator.Take(1)); control.Classes.Remove("foo"); @@ -108,7 +111,7 @@ namespace Avalonia.Styling.UnitTests { var control = new Control1(); var target = default(Selector).Class("foo").Class("bar"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.False(await activator.Take(1)); control.Classes.Add("foo"); @@ -129,7 +132,7 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; var result = new List(); using (activator.Subscribe(x => result.Add(x))) diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index b75b59c212..56dad13186 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -26,7 +26,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Descendant().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -41,7 +41,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Descendant().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -56,7 +56,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.True(await activator.Take(1)); } @@ -74,7 +74,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.False(await activator.Take(1)); } @@ -90,7 +90,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.False(await activator.Take(1)); parent.Classes.Add("foo"); diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs index 067b08b296..04b29376b0 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests public class SelectorTests_Multiple { [Fact] - public void Template_Child_Of_Control_With_Two_Classes() + public void Named_Template_Child_Of_Control_With_Two_Classes() { var template = new FuncControlTemplate(parent => { @@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests var border = (Border)((IVisual)control).VisualChildren.Single(); var values = new List(); - var activator = selector.Match(border).ObservableResult; + var match = selector.Match(border); - activator.Subscribe(x => values.Add(x)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + match.Activator.Subscribe(x => values.Add(x)); Assert.Equal(new[] { false }, values); control.Classes.AddRange(new[] { "foo", "bar" }); @@ -51,6 +52,39 @@ namespace Avalonia.Styling.UnitTests Assert.Equal(new[] { false, true, false }, values); } + [Fact] + public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type() + { + var template = new FuncControlTemplate(parent => + { + return new Border + { + Name = "border", + }; + }); + + var control = new Button + { + Template = template, + }; + + control.ApplyTemplate(); + + var selector = default(Selector) + .OfType public double Bottom => _y + _height; + /// + /// Gets the top position of the rectangle. + /// + public double Top => _y; + /// /// Gets the top left point of the rectangle. /// diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9e8ff20e44..98eb6f1e30 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -907,6 +907,18 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] public static extern bool SetWindowText(IntPtr hwnd, string lpString); + [StructLayout(LayoutKind.Sequential)] + public struct MARGINS + { + public int leftWidth; + public int rightWidth; + public int topHeight; + public int bottomHeight; + } + + [DllImport("dwmapi.dll")] + public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + public enum ClassLongIndex : int { GCL_HCURSOR = -12, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 18f0696cd8..395ea43999 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -455,6 +455,54 @@ namespace Avalonia.Win32 IntPtr.Zero); } + + protected int BorderTop { get; private set; } + protected int BorderLeft { get; private set; } + protected int BorderRight { get; private set; } + protected int BorderBottom { get; private set; } + + private HitTestValues NCHitText(uint m, IntPtr LParam, IntPtr wParam) + { + int lParam = (int)LParam; + Point pt = new Point(lParam & 0xffff, lParam >> 16); + + var rc = new Rect(Position, ClientSize); + + int row = 1; + int col = 1; + bool onResizeBorder = false; + + // Determine if we are on the top or bottom border + if (pt.Y >= rc.Top && pt.Y < rc.Top + BorderTop) + { + onResizeBorder = pt.Y < (rc.Top + BorderBottom); + row = 0; + } + else if (pt.Y < rc.Bottom && pt.Y > rc.Bottom - BorderBottom) + { + row = 2; + } + + // Determine if we are on the left border or the right border + if (pt.X >= rc.Left && pt.X < rc.Left + BorderLeft) + { + col = 0; + } + else if (pt.X < rc.Right && pt.X >= rc.Right - BorderRight) + { + col = 2; + } + + HitTestValues[,] hitTests = new HitTestValues[,] + { + {HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT}, + {HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT}, + {HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT} + }; + + return hitTests[row, col]; + } + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -465,6 +513,8 @@ namespace Avalonia.Win32 RawInputEventArgs e = null; + bool callDWP = false; + WindowsMouseDevice.Instance.CurrentWindow = this; switch ((UnmanagedMethods.WindowsMessage)msg) @@ -484,8 +534,54 @@ namespace Avalonia.Win32 break; } + MARGINS margins = new MARGINS(); + + margins.leftWidth = Math.Abs(BorderLeft); + margins.rightWidth = Math.Abs(BorderRight); + margins.bottomHeight = Math.Abs(BorderBottom); + margins.topHeight = Math.Abs(BorderTop); + + // int hr = UnmanagedMethods.DwmExtendFrameIntoClientArea(hWnd, ref margins); + return IntPtr.Zero; + case WindowsMessage.WM_NCCALCSIZE: + return IntPtr.Zero; + //break; + + case WindowsMessage.WM_CREATE: + RECT rcClient; + UnmanagedMethods.GetWindowRect(hWnd, out rcClient); + + + RECT rc = new RECT(); + uint style = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + uint styleEx = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + UnmanagedMethods.AdjustWindowRectEx(ref rc, style, false, styleEx); + + BorderTop = Math.Abs(rc.top); + BorderLeft = Math.Abs(0); + BorderRight = Math.Abs(0); + BorderBottom = Math.Abs(0); + break; + + case WindowsMessage.WM_NCHITTEST: + var ht = NCHitText(msg, lParam, wParam); + + + callDWP = (ht == HitTestValues.HTNOWHERE); + + if (callDWP) + { + + } + else + { + return new IntPtr((int)ht); + } + break; + + case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); if (preventClosing == true) From 6f540cf2609931004b92080fb1208600584972a0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:43:27 +0000 Subject: [PATCH 026/207] fix removing decorations --- samples/ControlCatalog/MainWindow.xaml | 6 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 131 +++-------------------- 2 files changed, 19 insertions(+), 118 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 4be11f13b3..e722206fd0 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -2,7 +2,5 @@ Title="Avalonia Control Gallery" Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog" xmlns:local="clr-namespace:ControlCatalog" Background="Transparent"> - - - - + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 395ea43999..17b72955bc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -269,11 +269,6 @@ namespace Avalonia.Win32 style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - if (!value) - { - style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - } - UnmanagedMethods.RECT windowRect; UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); @@ -282,32 +277,29 @@ namespace Avalonia.Win32 var oldThickness = BorderThickness; UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + + var thickness = BorderThickness; + + _decorated = value; - if (value) - { - var thickness = BorderThickness; + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else + if(!value) { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + RECT rc = new RECT(); + uint style1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + uint styleEx1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + UnmanagedMethods.AdjustWindowRectEx(ref rc, style1, false, styleEx1); } UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); - _decorated = value; - if(_decorated) { if (_resizable) @@ -455,54 +447,6 @@ namespace Avalonia.Win32 IntPtr.Zero); } - - protected int BorderTop { get; private set; } - protected int BorderLeft { get; private set; } - protected int BorderRight { get; private set; } - protected int BorderBottom { get; private set; } - - private HitTestValues NCHitText(uint m, IntPtr LParam, IntPtr wParam) - { - int lParam = (int)LParam; - Point pt = new Point(lParam & 0xffff, lParam >> 16); - - var rc = new Rect(Position, ClientSize); - - int row = 1; - int col = 1; - bool onResizeBorder = false; - - // Determine if we are on the top or bottom border - if (pt.Y >= rc.Top && pt.Y < rc.Top + BorderTop) - { - onResizeBorder = pt.Y < (rc.Top + BorderBottom); - row = 0; - } - else if (pt.Y < rc.Bottom && pt.Y > rc.Bottom - BorderBottom) - { - row = 2; - } - - // Determine if we are on the left border or the right border - if (pt.X >= rc.Left && pt.X < rc.Left + BorderLeft) - { - col = 0; - } - else if (pt.X < rc.Right && pt.X >= rc.Right - BorderRight) - { - col = 2; - } - - HitTestValues[,] hitTests = new HitTestValues[,] - { - {HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT}, - {HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT}, - {HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT} - }; - - return hitTests[row, col]; - } - [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -513,8 +457,6 @@ namespace Avalonia.Win32 RawInputEventArgs e = null; - bool callDWP = false; - WindowsMouseDevice.Instance.CurrentWindow = this; switch ((UnmanagedMethods.WindowsMessage)msg) @@ -534,53 +476,14 @@ namespace Avalonia.Win32 break; } - MARGINS margins = new MARGINS(); - - margins.leftWidth = Math.Abs(BorderLeft); - margins.rightWidth = Math.Abs(BorderRight); - margins.bottomHeight = Math.Abs(BorderBottom); - margins.topHeight = Math.Abs(BorderTop); - - // int hr = UnmanagedMethods.DwmExtendFrameIntoClientArea(hWnd, ref margins); - return IntPtr.Zero; case WindowsMessage.WM_NCCALCSIZE: - return IntPtr.Zero; - //break; - - case WindowsMessage.WM_CREATE: - RECT rcClient; - UnmanagedMethods.GetWindowRect(hWnd, out rcClient); - - - RECT rc = new RECT(); - uint style = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - uint styleEx = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - UnmanagedMethods.AdjustWindowRectEx(ref rc, style, false, styleEx); - - BorderTop = Math.Abs(rc.top); - BorderLeft = Math.Abs(0); - BorderRight = Math.Abs(0); - BorderBottom = Math.Abs(0); - break; - - case WindowsMessage.WM_NCHITTEST: - var ht = NCHitText(msg, lParam, wParam); - - - callDWP = (ht == HitTestValues.HTNOWHERE); - - if (callDWP) + if (!_decorated) { - - } - else - { - return new IntPtr((int)ht); + return IntPtr.Zero; } - break; - + break; case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); From 7483b1cb4cfd4b30017453ba3b3e5ab6eedfa035 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:44:47 +0000 Subject: [PATCH 027/207] revert changes. --- samples/ControlCatalog/MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index e722206fd0..4aa16aabfa 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -1,6 +1,6 @@  - + xmlns:local="clr-namespace:ControlCatalog"> + From 6a76c00f3ee5bfb09269cf879d956edaa92d2a0f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:46:29 +0000 Subject: [PATCH 028/207] remove unused changes. --- src/Avalonia.Visuals/Rect.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 37c320ab39..86597627b6 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -120,8 +120,6 @@ namespace Avalonia /// public Size Size => new Size(_width, _height); - public double Left => _x; - /// /// Gets the right position of the rectangle. /// @@ -131,12 +129,7 @@ namespace Avalonia /// Gets the bottom position of the rectangle. /// public double Bottom => _y + _height; - - /// - /// Gets the top position of the rectangle. - /// - public double Top => _y; - + /// /// Gets the top left point of the rectangle. /// From 607703427da97fcdcdbed7cbde93a7aa5666e30e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:47:13 +0000 Subject: [PATCH 029/207] whitespace. --- src/Avalonia.Visuals/Rect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 86597627b6..63d34b474b 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -129,7 +129,7 @@ namespace Avalonia /// Gets the bottom position of the rectangle. /// public double Bottom => _y + _height; - + /// /// Gets the top left point of the rectangle. /// From 9f02b7f7c12449fafa8b97fd2ab0b3fb6c41815c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:48:13 +0000 Subject: [PATCH 030/207] remove unused method. --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 98eb6f1e30..9e8ff20e44 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -907,18 +907,6 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] public static extern bool SetWindowText(IntPtr hwnd, string lpString); - [StructLayout(LayoutKind.Sequential)] - public struct MARGINS - { - public int leftWidth; - public int rightWidth; - public int topHeight; - public int bottomHeight; - } - - [DllImport("dwmapi.dll")] - public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); - public enum ClassLongIndex : int { GCL_HCURSOR = -12, From 6c249f375c02c2861bd8f320a4abfd6736309886 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 26 Nov 2018 02:28:59 +0200 Subject: [PATCH 031/207] add failing test for #2106 scroll to last in listbox not working sometimes --- ...emsPresenterTests_Virtualization_Simple.cs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index b992453fb0..7187ea16da 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -717,6 +717,45 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(10, target.Panel.Children.Count); } + [Fact] + public void Scroll_To_Last_Should_Work() + { + var target = CreateTarget(itemCount: 11); + var scroller = (TestScroller)target.Parent; + + scroller.Width = scroller.Height = 100; + scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + + var last = (target.Items as IList)[10]; + + target.ScrollIntoView(last); + + Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset); + Assert.Same(target.Panel.Children[9].DataContext, last); + } + + [Fact] + public void Second_Scroll_To_Last_Should_Work() + { + var target = CreateTarget(itemCount: 11); + var scroller = (TestScroller)target.Parent; + + scroller.Width = scroller.Height = 100; + scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + + var last = (target.Items as IList)[10]; + + target.ScrollIntoView(last); + + Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset); + Assert.Same(target.Panel.Children[9].DataContext, last); + + target.ScrollIntoView(last); + + Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset); + Assert.Same(target.Panel.Children[9].DataContext, last); + } + public class Vertical { [Fact] @@ -1090,4 +1129,4 @@ namespace Avalonia.Controls.UnitTests.Presenters } } } -} \ No newline at end of file +} From ea274841033a98e656ade0d5c8b5fe7b1b16e6c4 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 26 Nov 2018 02:43:04 +0200 Subject: [PATCH 032/207] fix #2106 scroll to last in listbox not working sometimes --- src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index b177dc50cf..670150dd30 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -517,7 +517,7 @@ namespace Avalonia.Controls.Presenters if (index >= 0 && index < ItemCount) { - if (index < FirstIndex) + if (index <= FirstIndex) { newOffset = index; } @@ -525,7 +525,7 @@ namespace Avalonia.Controls.Presenters { newOffset = index - Math.Ceiling(ViewportValue - 1); } - else if (OffsetValue + ViewportValue >= ItemCount) + else if (OffsetValue + ViewportValue > ItemCount) { newOffset = OffsetValue - 1; } From f090bd0dfb46a240c8e06c559a170cb60d54f9a4 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 26 Nov 2018 03:06:10 +0200 Subject: [PATCH 033/207] remove not needed condition in virtualizer scroll --- src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 670150dd30..d3ac45a224 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -525,10 +525,6 @@ namespace Avalonia.Controls.Presenters { newOffset = index - Math.Ceiling(ViewportValue - 1); } - else if (OffsetValue + ViewportValue > ItemCount) - { - newOffset = OffsetValue - 1; - } if (newOffset != -1) { From f260ec1df43dd227a23ed69fa473833f54100d63 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 26 Nov 2018 23:40:00 +0100 Subject: [PATCH 034/207] Support relative paths for embedded fonts --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 8 +- src/Avalonia.Visuals/Media/FontFamily.cs | 16 +++- .../Media/Fonts/FontFamilyKey.cs | 66 +++++--------- .../Media/Fonts/FontFamilyLoader.cs | 88 +++++++++++++++---- .../Avalonia.Markup.Xaml.csproj | 1 + .../AvaloniaTypeConverters.cs | 9 +- .../Converters/FontFamilyTypeConverter.cs | 28 ++++++ .../Media/Fonts/FontFamilyKeyTests.cs | 4 +- 8 files changed, 148 insertions(+), 72 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 2c292b1478..67b60568e5 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -44,10 +44,10 @@ res fonts - - - - + + + + diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 16dba573fd..d32db4ef28 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -40,9 +40,10 @@ namespace Avalonia.Media /// /// The name of the . /// The source of font resources. - public FontFamily(string name, Uri source) : this(name) + /// + public FontFamily(string name, Uri source, Uri baseUri = null) : this(name) { - Key = new FontFamilyKey(source); + Key = new FontFamilyKey(source, baseUri); } /// @@ -87,11 +88,12 @@ namespace Avalonia.Media /// Parses a string. /// /// The string. + /// /// /// /// Specified family is not supported. /// - public static FontFamily Parse(string s) + public static FontFamily Parse(string s, Uri baseUri = null) { if (string.IsNullOrEmpty(s)) { @@ -112,7 +114,13 @@ namespace Avalonia.Media case 2: { - return new FontFamily(segments[1], new Uri(segments[0], UriKind.RelativeOrAbsolute)); + var uri = s.StartsWith("/") + ? new Uri(s, UriKind.Relative) + : new Uri(s, UriKind.RelativeOrAbsolute); + + return uri.IsAbsoluteUri + ? new FontFamily(segments[1], uri) + : new FontFamily(segments[1], uri, baseUri); } default: diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 90ccac0e46..67b53cf8b4 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; namespace Avalonia.Media.Fonts { @@ -12,48 +11,26 @@ namespace Avalonia.Media.Fonts public class FontFamilyKey { /// - /// Creates a new instance of and extracts and from given + /// Creates a new instance of /// /// - public FontFamilyKey(Uri source) + /// + public FontFamilyKey(Uri source, Uri baseUri = null) { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } + Source = source ?? throw new ArgumentNullException(nameof(source)); - if (source.AbsolutePath.Contains(".ttf")) - { - var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", string.Empty); - var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last(); - FileName = fileNameWithoutExtension + ".ttf"; - Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute); - } - else - { - if (source.AbsolutePath.Contains(".otf")) - { - var filePathWithoutExtension = source.AbsolutePath.Replace(".otf", string.Empty); - var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last(); - FileName = fileNameWithoutExtension + ".otf"; - Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute); - } - else - { - Location = source; - } - } + BaseUri = baseUri; } /// - /// Location of stored font asset that belongs to a + /// Source of stored font asset that belongs to a /// - public Uri Location { get; } + public Uri Source { get; } /// - /// Optional filename for a font asset that belongs to a + /// /// - public string FileName { get; } + public Uri BaseUri { get; } /// /// Returns a hash code for this instance. @@ -67,14 +44,14 @@ namespace Avalonia.Media.Fonts { var hash = (int)2166136261; - if (Location != null) + if (Source != null) { - hash = (hash * 16777619) ^ Location.GetHashCode(); + hash = (hash * 16777619) ^ Source.GetHashCode(); } - if (FileName != null) + if (BaseUri != null) { - hash = (hash * 16777619) ^ FileName.GetHashCode(); + hash = (hash * 16777619) ^ BaseUri.GetHashCode(); } return hash; @@ -95,12 +72,12 @@ namespace Avalonia.Media.Fonts return false; } - if (Location != other.Location) + if (Source != other.Source) { return false; } - if (FileName != other.FileName) + if (BaseUri != other.BaseUri) { return false; } @@ -116,16 +93,17 @@ namespace Avalonia.Media.Fonts /// public override string ToString() { - if (FileName == null) + if (Source.IsAbsoluteUri) { - return Location.PathAndQuery; + return Source.ToString(); } - var builder = new UriBuilder(Location); - - builder.Path += "." + FileName; + if (BaseUri != null) + { + return BaseUri + "/" + Source; + } - return builder.ToString(); + return Source.ToString(); } } } diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 166eb4a661..e1eabf7237 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Avalonia.Platform; namespace Avalonia.Media.Fonts @@ -20,21 +19,26 @@ namespace Avalonia.Media.Fonts public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { - return fontFamilyKey.FileName != null - ? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) - : GetFontAssetsByLocation(fontFamilyKey.Location); + if (fontFamilyKey.Source.OriginalString.Contains(".ttf") + || fontFamilyKey.Source.OriginalString.Contains(".otf")) + { + return GetFontAssetsByExpression(fontFamilyKey); + } + + return GetFontAssetsBySource(fontFamilyKey); } /// /// Searches for font assets at a given location and returns a quantity of found assets /// - /// + /// /// - private static IEnumerable GetFontAssetsByLocation(Uri location) + private static IEnumerable GetFontAssetsBySource(FontFamilyKey fontFamilyKey) { - var availableAssets = s_assetLoader.GetAssets(location, null); + var availableAssets = s_assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri); - var matchingAssets = availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")); + var matchingAssets = + availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")); return matchingAssets; } @@ -43,20 +47,74 @@ namespace Avalonia.Media.Fonts /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression. /// File names can target multiple files with * wildcard. For example "FontFile*.ttf" /// - /// - /// + /// /// - private static IEnumerable GetFontAssetsByFileName(Uri location, string fileName) + private static IEnumerable GetFontAssetsByExpression(FontFamilyKey fontFamilyKey) { - var availableResources = s_assetLoader.GetAssets(location, null); + var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location); + + var availableResources = s_assetLoader.GetAssets(location, fontFamilyKey.BaseUri); - var compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); + string compareTo; - var matchingResources = - availableResources.Where(x => x.AbsolutePath.Contains(compareTo) && (x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"))); + if (fontFamilyKey.Source.IsAbsoluteUri) + { + if (fontFamilyKey.Source.Scheme == "resm") + { + compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); + } + else + { + compareTo = location.AbsolutePath + fileName.Split('*').First(); + } + } + else + { + compareTo = location.AbsolutePath + fileName.Split('*').First(); + } + + var matchingResources = availableResources.Where( + x => x.AbsolutePath.Contains(compareTo) + && x.AbsolutePath.EndsWith(fileExtension)); return matchingResources; } + private static string GetFileName(FontFamilyKey fontFamilyKey, out string fileExtension, out Uri location) + { + if (fontFamilyKey.Source.IsAbsoluteUri && fontFamilyKey.Source.Scheme == "resm") + { + fileExtension = "." + fontFamilyKey.Source.AbsolutePath.Split('.').LastOrDefault(); + + var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').LastOrDefault(); + + location = new Uri(fontFamilyKey.Source.AbsoluteUri.Replace("." + fileName + fileExtension, string.Empty), UriKind.RelativeOrAbsolute); + + return fileName; + } + + var pathSegments = fontFamilyKey.Source.OriginalString.Split('/'); + + var fileNameWithExtension = pathSegments.Last().Split('#').First(); + + var fileNameSegments = fileNameWithExtension.Split('.'); + + fileExtension = "." + fileNameSegments.Last(); + + if (fontFamilyKey.BaseUri != null) + { + location = new Uri( + fontFamilyKey.BaseUri, + fontFamilyKey.Source.OriginalString.Split('#') + .First() + .Replace(fileNameWithExtension, string.Empty)); + } + else + { + location = new Uri(fontFamilyKey.Source.AbsolutePath.Replace(fileNameWithExtension, string.Empty)); + } + + return fileNameSegments.First(); + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index ec73859a0a..bb3883285a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index 93db025343..fa9d364fc0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -11,6 +11,8 @@ using Avalonia.Controls.Templates; namespace Avalonia.Markup.Xaml { + using Avalonia.Media; + /// /// Maintains a repository of s for XAML parsing on top of those /// maintained by . @@ -37,8 +39,9 @@ namespace Avalonia.Markup.Xaml { typeof(Selector), typeof(SelectorTypeConverter) }, { typeof(TimeSpan), typeof(TimeSpanTypeConverter) }, { typeof(WindowIcon), typeof(IconTypeConverter) }, - { typeof(CultureInfo), typeof(CultureInfoConverter)}, - { typeof(Uri), typeof(AvaloniaUriTypeConverter)} + { typeof(CultureInfo), typeof(CultureInfoConverter) }, + { typeof(Uri), typeof(AvaloniaUriTypeConverter) }, + { typeof(FontFamily), typeof(FontFamilyTypeConverter) } }; /// @@ -84,4 +87,4 @@ namespace Avalonia.Markup.Xaml /// The converter type. Maybe be a non-constructed generic type. public static void Register(Type type, Type converterType) => _converters[type] = converterType; } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs new file mode 100644 index 0000000000..9b0e7a2b6b --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs @@ -0,0 +1,28 @@ +// 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.ComponentModel; +using System.Globalization; + +using Avalonia.Media; + +using Portable.Xaml.ComponentModel; + +namespace Avalonia.Markup.Xaml.Converters +{ + public class FontFamilyTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + var s = (string)value; + + return FontFamily.Parse(s, context.GetBaseUri()); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs index 96464b5784..38ad9e1866 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); - Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location); + Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); Assert.Null(fontFamilyKey.FileName); } @@ -34,7 +34,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); - Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location); + Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); Assert.Equal("MyFont.ttf", fontFamilyKey.FileName); } From 54627eeff114f98cc231d7c788492ed40f0e4f16 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 27 Nov 2018 14:29:10 +0200 Subject: [PATCH 035/207] add failing unittest for immediaterenderer not rendering sometimes controls issue #2141 --- .../Rendering/ImmediateRendererTests.cs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs index 82294246b1..73e4a14539 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs @@ -1,8 +1,11 @@ using System.Collections.Generic; using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; using Xunit; @@ -94,5 +97,86 @@ namespace Avalonia.Visuals.UnitTests.Rendering //then new position Assert.Equal(new Rect(100, 100, 100, 100), invalidationCalls[2]); } + + [Fact] + public void Should_Render_Child_In_Parent_With_RenderTransform() + { + var targetMock = new Mock() { CallBase = true }; + var target = targetMock.Object; + target.Width = 100; + target.Height = 50; + var child = new Panel() + { + RenderTransform = new RotateTransform() { Angle = 90 }, + Children = + { + new Panel() + { + Children = + { + target + } + } + } + }; + + var visualTarget = targetMock.As(); + int rendered = 0; + visualTarget.Setup(v => v.Render(It.IsAny())).Callback(() => rendered++); + + var root = new TestRoot(child); + root.Renderer = new ImmediateRenderer(root); + + root.LayoutManager.ExecuteInitialLayoutPass(root); + + root.Measure(new Size(50, 100)); + root.Arrange(new Rect(new Size(50, 100))); + + root.Renderer.Paint(root.Bounds); + + Assert.Equal(1, rendered); + } + + [Fact] + public void Should_Render_Child_In_Parent_With_RenderTransform2() + { + var targetMock = new Mock() { CallBase = true }; + var target = targetMock.Object; + + target.Width = 100; + target.Height = 50; + target.HorizontalAlignment = HorizontalAlignment.Center; + target.VerticalAlignment = VerticalAlignment.Center; + + var child = new Panel() + { + RenderTransform = new RotateTransform() { Angle = 90 }, + Children = + { + new Panel() + { + Children = + { + target + } + } + } + }; + + var visualTarget = targetMock.As(); + int rendered = 0; + visualTarget.Setup(v => v.Render(It.IsAny())).Callback(() => rendered++); + + var root = new TestRoot(child); + root.Renderer = new ImmediateRenderer(root); + + root.LayoutManager.ExecuteInitialLayoutPass(root); + + root.Measure(new Size(300, 100)); + root.Arrange(new Rect(new Size(300, 100))); + root.Renderer.Paint(root.Bounds); + + Assert.Equal(1, rendered); + } } } From 55600239a6f64ff2850a3afbc96b00b6750e00f5 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 27 Nov 2018 21:03:00 +0200 Subject: [PATCH 036/207] fix for immediaterenderer not rendering sometimes controls issue #2141 --- src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 08f3803e9b..03ac004fd5 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -266,7 +266,14 @@ namespace Avalonia.Rendering if (clipToBounds) { - clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + if (visual.RenderTransform != null) + { + clipRect = new Rect(visual.Bounds.Size); + } + else + { + clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + } } using (context.PushPostTransform(m)) From 1d19e28e9b839faaa63f6e9bba0748227a168188 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 28 Nov 2018 14:17:50 +0100 Subject: [PATCH 037/207] Full support for relative uris --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 4 ++-- src/Avalonia.Visuals/Media/FontFamily.cs | 6 +++--- src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs | 11 +++-------- .../Media/Fonts/FontFamilyLoader.cs | 13 ++++++------- .../Media/Fonts/FontFamilyKeyTests.cs | 6 +----- 5 files changed, 15 insertions(+), 25 deletions(-) diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 67b60568e5..0c0a4d705b 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -44,8 +44,8 @@ res fonts - - + + diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index d32db4ef28..0ba25e6c0d 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -114,9 +114,9 @@ namespace Avalonia.Media case 2: { - var uri = s.StartsWith("/") - ? new Uri(s, UriKind.Relative) - : new Uri(s, UriKind.RelativeOrAbsolute); + var uri = segments[0].StartsWith("/") + ? new Uri(segments[0], UriKind.Relative) + : new Uri(segments[0], UriKind.RelativeOrAbsolute); return uri.IsAbsoluteUri ? new FontFamily(segments[1], uri) diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 67b53cf8b4..2803b942ad 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -28,7 +28,7 @@ namespace Avalonia.Media.Fonts public Uri Source { get; } /// - /// + /// A base URI to use if is relative /// public Uri BaseUri { get; } @@ -93,14 +93,9 @@ namespace Avalonia.Media.Fonts /// public override string ToString() { - if (Source.IsAbsoluteUri) + if (!Source.IsAbsoluteUri && BaseUri != null) { - return Source.ToString(); - } - - if (BaseUri != null) - { - return BaseUri + "/" + Source; + return string.Empty + BaseUri + Source; } return Source.ToString(); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index e1eabf7237..623125164c 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -71,7 +71,7 @@ namespace Avalonia.Media.Fonts else { compareTo = location.AbsolutePath + fileName.Split('*').First(); - } + } var matchingResources = availableResources.Where( x => x.AbsolutePath.Contains(compareTo) @@ -95,7 +95,7 @@ namespace Avalonia.Media.Fonts var pathSegments = fontFamilyKey.Source.OriginalString.Split('/'); - var fileNameWithExtension = pathSegments.Last().Split('#').First(); + var fileNameWithExtension = pathSegments.Last(); var fileNameSegments = fileNameWithExtension.Split('.'); @@ -103,11 +103,10 @@ namespace Avalonia.Media.Fonts if (fontFamilyKey.BaseUri != null) { - location = new Uri( - fontFamilyKey.BaseUri, - fontFamilyKey.Source.OriginalString.Split('#') - .First() - .Replace(fileNameWithExtension, string.Empty)); + var relativePath = fontFamilyKey.Source.OriginalString + .Replace(fileNameWithExtension, string.Empty); + + location = new Uri(fontFamilyKey.BaseUri, relativePath); } else { diff --git a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs index 38ad9e1866..b57ba781ed 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs @@ -23,8 +23,6 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); - - Assert.Null(fontFamilyKey.FileName); } [Fact] @@ -34,9 +32,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); - Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); - - Assert.Equal("MyFont.ttf", fontFamilyKey.FileName); + Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests.MyFont.ttf"), fontFamilyKey.Source); } } } From d6c9ce3277cefa3754b8cc66ec44cb9da5af93aa Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 28 Nov 2018 17:15:40 +0100 Subject: [PATCH 038/207] Make sure only font files are compared --- src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs | 2 +- src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 2803b942ad..76ee2b7aad 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -95,7 +95,7 @@ namespace Avalonia.Media.Fonts { if (!Source.IsAbsoluteUri && BaseUri != null) { - return string.Empty + BaseUri + Source; + return BaseUri.Authority + Source; } return Source.ToString(); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 623125164c..1e1049d017 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -19,8 +19,10 @@ namespace Avalonia.Media.Fonts public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { - if (fontFamilyKey.Source.OriginalString.Contains(".ttf") - || fontFamilyKey.Source.OriginalString.Contains(".otf")) + var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First(); + + if (sourceWithoutArguments.EndsWith(".ttf") + || sourceWithoutArguments.EndsWith(".otf")) { return GetFontAssetsByExpression(fontFamilyKey); } From bd97346b97ad76c00742b7dd6805c3d00105fe57 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 28 Nov 2018 17:18:06 +0100 Subject: [PATCH 039/207] Fix missing comment --- src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 1e1049d017..063fe8f20d 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -17,6 +17,11 @@ namespace Avalonia.Media.Fonts s_assetLoader = AvaloniaLocator.Current.GetService(); } + /// + /// Loads all font assets that belong to the specified + /// + /// + /// public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First(); From c4cc3514a8c35f36230f8902bbddbf7bffaf624f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Nov 2018 16:57:49 +0000 Subject: [PATCH 040/207] Set win32 WindowStyle flags individually. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 17b72955bc..bdf58e51ee 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -265,13 +265,18 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - - UnmanagedMethods.RECT windowRect; - - UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); + if (value) + { + style |= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); + } + else + { + style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); + } + + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); Rect newRect; var oldThickness = BorderThickness; @@ -436,7 +441,7 @@ namespace Avalonia.Win32 0, atom, null, - (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW, + (int)(WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME), UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, From 743e61cfde37e608820e1d6c7e0671a5e22e781e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Nov 2018 16:57:49 +0000 Subject: [PATCH 041/207] Revert "Set win32 WindowStyle flags individually." This reverts commit c4cc3514a8c35f36230f8902bbddbf7bffaf624f. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bdf58e51ee..17b72955bc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -265,18 +265,13 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - if (value) - { - style |= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); - } - else - { - style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); - } - - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + + UnmanagedMethods.RECT windowRect; + + UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); Rect newRect; var oldThickness = BorderThickness; @@ -441,7 +436,7 @@ namespace Avalonia.Win32 0, atom, null, - (int)(WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME), + (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW, UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, From c8515ebea094c0798fd59404957dc7611ff34aaf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Nov 2018 17:17:46 +0000 Subject: [PATCH 042/207] handle window styles so that it obeys can resize. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 17b72955bc..e8f9f37087 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -269,6 +269,17 @@ namespace Avalonia.Win32 style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + if (!value) + { + style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); + + if (!_resizable) + { + style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + } + } + + UnmanagedMethods.RECT windowRect; UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); @@ -916,17 +927,14 @@ namespace Avalonia.Win32 return; } - if (_decorated) - { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - if (value) - style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; - else - style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + if (value) + style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + else + style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); - } + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); _resizable = value; } From ea8bde211b57bfdfabfdd0ddf7452f6e3ce52c4c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 29 Nov 2018 11:31:07 +0300 Subject: [PATCH 043/207] Additional `Start` method overload --- samples/ControlCatalog.NetCore/Program.cs | 10 ++++++++-- src/Avalonia.Controls/AppBuilderBase.cs | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 1f53dedc14..57c8b700df 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -23,6 +23,7 @@ namespace ControlCatalog.NetCore break; } } + if (args.Contains("--fbdev")) AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => { @@ -30,7 +31,12 @@ namespace ControlCatalog.NetCore System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(AppMain, args); + } + + static void AppMain(Application app, string[] args) + { + app.Run(new MainWindow()); } /// @@ -46,4 +52,4 @@ namespace ControlCatalog.NetCore Console.ReadKey(true); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 376714b20b..c5dd072d8a 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -145,6 +145,15 @@ namespace Avalonia.Controls Instance.Run(mainWindow); } + public delegate void AppMainDelegate(Application app, string[] args); + + public void Start(AppMainDelegate main, string[] args) + { + Setup(); + BeforeStartCallback(Self); + main(Instance, args); + } + /// /// Sets up the platform-specific services for the application, but does not run it. /// From 053e6a2be57006efd9be9aea783bd1d311cc40a6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 13:02:40 +0000 Subject: [PATCH 044/207] update wmstyles in single place, and implement nccalc size message. --- .../Interop/UnmanagedMethods.cs | 20 ++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 113 +++++++++--------- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9e8ff20e44..adfbf0cb52 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1181,6 +1181,26 @@ namespace Avalonia.Win32.Interop } } + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndInsertAfter; + public int x; + public int y; + public int cx; + public int cy; + public uint flags; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + public struct TRACKMOUSEEVENT { public int cbSize; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e8f9f37087..fbd0752a19 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -35,6 +35,7 @@ namespace Avalonia.Win32 private bool _decorated = true; private bool _resizable = true; private bool _topmost = false; + private bool _taskbarIcon = true; private double _scaling = 1; private WindowState _showWindowState; private WindowState _lastWindowState; @@ -96,7 +97,8 @@ namespace Avalonia.Win32 { var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - var padding = new UnmanagedMethods.RECT(); + + var padding = new RECT(); if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) { @@ -265,66 +267,27 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - - if (!value) - { - style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); - - if (!_resizable) - { - style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); - } - } - - - UnmanagedMethods.RECT windowRect; - - UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); + _decorated = value; - Rect newRect; + UpdateWMStyles(); + + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + var oldThickness = BorderThickness; - - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); var thickness = BorderThickness; _decorated = value; - newRect = new Rect( + var newRect = new Rect( windowRect.left - thickness.Left, windowRect.top - thickness.Top, (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - if(!value) - { - RECT rc = new RECT(); - uint style1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - uint styleEx1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - UnmanagedMethods.AdjustWindowRectEx(ref rc, style1, false, styleEx1); - } - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); - - if(_decorated) - { - if (_resizable) - { - // If we switch decorations back on we need to restore WS_SizeFrame. - _resizable = false; - CanResize(true); - } - else - { - _resizable = true; - CanResize(false); - } - } } public void Invalidate(Rect rect) @@ -490,8 +453,23 @@ namespace Avalonia.Win32 return IntPtr.Zero; case WindowsMessage.WM_NCCALCSIZE: - if (!_decorated) + if (ToInt32(wParam) == 1 && !_decorated) { + + // Calculate new NCCALCSIZE_PARAMS based on custom NCA inset. + var pncsp = Marshal.PtrToStructure(lParam); + + pncsp.rgrc[0].left = pncsp.rgrc[0].left + 0; + pncsp.rgrc[0].top = pncsp.rgrc[0].top + 0; + pncsp.rgrc[0].right = pncsp.rgrc[0].right - 0; + pncsp.rgrc[0].bottom = pncsp.rgrc[0].bottom - 0; + + //lRet = 0; + + // No need to pass the message on to the DefWindowProc. + //fCallDWP = false; + + return IntPtr.Zero; } break; @@ -897,14 +875,23 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } + public void ShowTaskbarIcon(bool value) { + if(_taskbarIcon == value) + { + return; + } + + _taskbarIcon = value; + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; + if (value) style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW; else @@ -920,23 +907,41 @@ namespace Avalonia.Win32 } } - public void CanResize(bool value) + private void UpdateWMStyles() { - if (value == _resizable) + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + + style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + + if (!_decorated) { - return; + style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - - if (value) + if (_resizable) + { style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + } else - style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + { + style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + } UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + var thickness = BorderThickness; + } + + public void CanResize(bool value) + { + if (value == _resizable) + { + return; + } + _resizable = value; + + UpdateWMStyles(); } public void SetTopmost(bool value) From 1c871bf423db1d2d777cc5ca53de229e5c5787bb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 13:42:22 +0000 Subject: [PATCH 045/207] [win32] fix maximize borders and wm window styles for non-decorated windows. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index fbd0752a19..d32d2be1a6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -455,21 +455,6 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCCALCSIZE: if (ToInt32(wParam) == 1 && !_decorated) { - - // Calculate new NCCALCSIZE_PARAMS based on custom NCA inset. - var pncsp = Marshal.PtrToStructure(lParam); - - pncsp.rgrc[0].left = pncsp.rgrc[0].left + 0; - pncsp.rgrc[0].top = pncsp.rgrc[0].top + 0; - pncsp.rgrc[0].right = pncsp.rgrc[0].right - 0; - pncsp.rgrc[0].bottom = pncsp.rgrc[0].bottom - 0; - - //lRet = 0; - - // No need to pass the message on to the DefWindowProc. - //fCallDWP = false; - - return IntPtr.Zero; } break; @@ -915,7 +900,7 @@ namespace Avalonia.Win32 if (!_decorated) { - style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); } if (_resizable) From 2572e0d0656cb230472fe1052b20234c5c588edb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 14:02:41 +0000 Subject: [PATCH 046/207] fix rendering issues when clicking on modal dialogs parent and dialog is non-decorated. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index d32d2be1a6..e49eb5be9f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -619,6 +619,14 @@ namespace Avalonia.Win32 new Point(0, 0), GetMouseModifiers(wParam)); break; + case WindowsMessage.WM_NCPAINT: + case WindowsMessage.WM_NCACTIVATE: + if (!_decorated) + { + return IntPtr.Zero; + } + break; + case UnmanagedMethods.WindowsMessage.WM_PAINT: UnmanagedMethods.PAINTSTRUCT ps; if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) From 658ff21d0ff97cac034ff416b4f73d948fc71aff Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 14:03:00 +0000 Subject: [PATCH 047/207] fix border thickness being added to non-decorated windows. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e49eb5be9f..932cb4a9d0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -149,7 +149,11 @@ namespace Avalonia.Win32 if (value != ClientSize) { value *= Scaling; - value += BorderThickness; + + if (_decorated) + { + value += BorderThickness; + } UnmanagedMethods.SetWindowPos( _hwnd, From 374d6a5f0d06412e0b194915aefdafea22079375 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 14:15:24 +0000 Subject: [PATCH 048/207] fix smooth toggling between decorated and non-decorated. --- samples/ControlCatalog/DecoratedWindow.xaml | 6 +++- src/Windows/Avalonia.Win32/WindowImpl.cs | 31 +++++++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 37014255cc..fdb18fcca2 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,7 +3,7 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" Icon="/Assets/test_icon.ico" - xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False"> + xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> @@ -20,7 +20,11 @@ + Hello world! + + Decorated + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 932cb4a9d0..5fa399e2a9 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -279,23 +279,36 @@ namespace Avalonia.Win32 var oldThickness = BorderThickness; - var thickness = BorderThickness; - _decorated = value; - var newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + Rect newRect; + + if (value) + { + var thickness = BorderThickness; + + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); } public void Invalidate(Rect rect) - { + { var f = Scaling; var r = new UnmanagedMethods.RECT { From 9050409afcfa20fb00c3b2b26b8c5e588e02dd9c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:28:03 +0000 Subject: [PATCH 049/207] [win32] windowimpl update style calculations done in one place. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 111 ++++++++++++----------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5fa399e2a9..621de88019 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,44 +271,11 @@ namespace Avalonia.Win32 return; } - _decorated = value; - - UpdateWMStyles(); - - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - var oldThickness = BorderThickness; - - _decorated = value; - - Rect newRect; - - if (value) - { - var thickness = BorderThickness; - - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } - - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + UpdateWMStyles(() => _decorated = value); } public void Invalidate(Rect rect) - { + { var f = Scaling; var r = new UnmanagedMethods.RECT { @@ -474,7 +441,7 @@ namespace Avalonia.Win32 { return IntPtr.Zero; } - break; + break; case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); @@ -686,7 +653,7 @@ namespace Avalonia.Win32 MINMAXINFO mmi = Marshal.PtrToStructure(lParam); - if (_minSize.Width > 0) + if (_minSize.Width > 0) mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); if (_minSize.Height > 0) @@ -857,7 +824,7 @@ namespace Avalonia.Win32 { MONITORINFO monitorInfo = MONITORINFO.Create(); - if (GetMonitorInfo(monitor,ref monitorInfo)) + if (GetMonitorInfo(monitor, ref monitorInfo)) { RECT rcMonitorArea = monitorInfo.rcMonitor; @@ -885,17 +852,17 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } - + public void ShowTaskbarIcon(bool value) { - if(_taskbarIcon == value) + if (_taskbarIcon == value) { return; } _taskbarIcon = value; - + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); @@ -917,29 +884,69 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles() + private void UpdateWMStyles(Action changer) { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var decorated = _decorated; + + var resizable = _resizable; - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + changer(); - if (!_decorated) + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); + + if (decorated != _decorated) { - style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); + style |= WindowStyles.WS_OVERLAPPEDWINDOW; + + if (!_decorated) + { + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); + } } if (_resizable) { - style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + style |= WindowStyles.WS_SIZEFRAME; } else { - style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + style ^= (WindowStyles.WS_SIZEFRAME); } - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); + + if (decorated != _decorated) + { + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + + var oldThickness = BorderThickness; + + Rect newRect; + + if (_decorated) + { + var thickness = BorderThickness; - var thickness = BorderThickness; + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } + + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + + } } public void CanResize(bool value) @@ -949,9 +956,7 @@ namespace Avalonia.Win32 return; } - _resizable = value; - - UpdateWMStyles(); + UpdateWMStyles(() => _resizable = value); } public void SetTopmost(bool value) From c9b069ffb74a44536059bf88db1922d637a2ca6f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:34:42 +0000 Subject: [PATCH 050/207] simplify style flag calculation. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 621de88019..8343ccbd44 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -892,23 +892,14 @@ namespace Avalonia.Win32 changer(); - var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE) | WindowStyles.WS_OVERLAPPEDWINDOW; - if (decorated != _decorated) + if (!_decorated) { - style |= WindowStyles.WS_OVERLAPPEDWINDOW; - - if (!_decorated) - { - style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); - } + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); } - if (_resizable) - { - style |= WindowStyles.WS_SIZEFRAME; - } - else + if (!_resizable) { style ^= (WindowStyles.WS_SIZEFRAME); } From dbacf80511f5dd0ee18cb97b3b81e656fb4fc339 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:53:19 +0000 Subject: [PATCH 051/207] tidy decorated window. --- samples/ControlCatalog/DecoratedWindow.xaml | 65 ++++++++++----------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index fdb18fcca2..9f05cd1f57 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -1,39 +1,38 @@ - - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - - - - - - - - - - - - + Decorated + + + + + + + + + + + + From f8d3046cb59d09107d112aa801810a6bfffb9893 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Dec 2018 17:54:32 +0300 Subject: [PATCH 052/207] Switched build to Nuke --- .nuke | 0 .travis.yml | 2 +- Avalonia.sln | 6 + appveyor.yml | 2 +- azure-pipelines.yml | 25 ++- build.cake | 312 ---------------------------- build.ps1 | 226 +++++--------------- build.sh | 139 +++++-------- cake.config | 15 -- nukebuild/Build.cs | 278 +++++++++++++++++++++++++ nukebuild/BuildParameters.cs | 186 +++++++++++++++++ nukebuild/Shims.cs | 80 +++++++ nukebuild/_build.csproj | 35 ++++ nukebuild/_build.csproj.DotSettings | 24 +++ 14 files changed, 723 insertions(+), 607 deletions(-) create mode 100644 .nuke delete mode 100644 build.cake delete mode 100644 cake.config create mode 100644 nukebuild/Build.cs create mode 100644 nukebuild/BuildParameters.cs create mode 100644 nukebuild/Shims.cs create mode 100644 nukebuild/_build.csproj create mode 100644 nukebuild/_build.csproj.DotSettings diff --git a/.nuke b/.nuke new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.travis.yml b/.travis.yml index b0c0c807cb..d60323b418 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ dotnet: 2.1.200 script: - sudo apt-get update - sudo apt-get install castxml - - ./build.sh --target "Travis" --configuration "Release" + - ./build.sh --target "CiTravis" --configuration "Release" notifications: email: false webhooks: diff --git a/Avalonia.sln b/Avalonia.sln index df60ff4a75..e006cadb5a 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -191,6 +191,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Ava EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -216,6 +218,10 @@ Global Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU diff --git a/appveyor.yml b/appveyor.yml index 484fb4586f..8694495e66 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,7 @@ init: before_build: - git submodule update --init build_script: -- ps: .\build.ps1 -Target "AppVeyor" -Configuration "$env:configuration" +- ps: .\build.ps1 --target "CiAppVeyor" --configuration "$env:configuration" test: off artifacts: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6929f8dee..dec361affe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,19 +11,18 @@ jobs: sudo apt-get install castxml - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 - + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - dotnet cake build.cake -target="Azure-Linux" -configuration="Release" + nuke --target="CiAzureLinux" --configuration="Release" - task: PublishTestResults@2 inputs: @@ -55,13 +54,13 @@ jobs: script: brew install castxml - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | export COREHOST_TRACE=0 @@ -72,7 +71,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - dotnet cake build.cake -target="Azure-OSX" -configuration="Release" + nuke --target="CiAzureOSX" --configuration="Release" - task: PublishTestResults@2 inputs: @@ -97,17 +96,17 @@ jobs: vmImage: 'vs2017-win2016' steps: - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | set PATH=%PATH%;%USERPROFILE%\.dotnet\tools - dotnet cake build.cake -target="Azure-Windows" -configuration="Release" + nuke --target="CiAzureWindows" --configuration="Release" - task: PublishTestResults@2 inputs: diff --git a/build.cake b/build.cake deleted file mode 100644 index f10a12c4e6..0000000000 --- a/build.cake +++ /dev/null @@ -1,312 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// TOOLS -/////////////////////////////////////////////////////////////////////////////// - -#tool "nuget:?package=NuGet.CommandLine&version=4.7.1" -#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3" -#tool "nuget:?package=xunit.runner.console&version=2.3.1" -#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559" - -/////////////////////////////////////////////////////////////////////////////// -// USINGS -/////////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -/////////////////////////////////////////////////////////////////////////////// -// SCRIPTS -/////////////////////////////////////////////////////////////////////////////// - -#load "./parameters.cake" - -/////////////////////////////////////////////////////////////////////////////// -// SETUP -/////////////////////////////////////////////////////////////////////////////// - -Setup(context => -{ - var parameters = new Parameters(context); - - Information("Building version {0} of Avalonia ({1}) using version {2} of Cake.", - parameters.Version, - parameters.Configuration, - typeof(ICakeContext).Assembly.GetName().Version.ToString()); - - if (parameters.IsRunningOnAppVeyor) - { - Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name); - Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch); - } - Information("Target:" + context.TargetTask.Name); - Information("Configuration: " + parameters.Configuration); - Information("IsLocalBuild: " + parameters.IsLocalBuild); - Information("IsRunningOnUnix: " + parameters.IsRunningOnUnix); - Information("IsRunningOnWindows: " + parameters.IsRunningOnWindows); - Information("IsRunningOnAppVeyor: " + parameters.IsRunningOnAppVeyor); - Information("IsRunnongOnAzure:" + parameters.IsRunningOnAzure); - Information("IsPullRequest: " + parameters.IsPullRequest); - Information("IsMainRepo: " + parameters.IsMainRepo); - Information("IsMasterBranch: " + parameters.IsMasterBranch); - Information("IsReleaseBranch: " + parameters.IsReleaseBranch); - Information("IsTagged: " + parameters.IsTagged); - Information("IsReleasable: " + parameters.IsReleasable); - Information("IsMyGetRelease: " + parameters.IsMyGetRelease); - Information("IsNuGetRelease: " + parameters.IsNuGetRelease); - - return parameters; -}); - -/////////////////////////////////////////////////////////////////////////////// -// TEARDOWN -/////////////////////////////////////////////////////////////////////////////// - -Teardown((context, buildContext) => -{ - Information("Finished running tasks."); -}); - -/////////////////////////////////////////////////////////////////////////////// -// TASKS -/////////////////////////////////////////////////////////////////////////////// - -Task("Clean-Impl") - .Does(data => -{ - CleanDirectories(data.BuildDirs); - CleanDirectory(data.ArtifactsDir); - CleanDirectory(data.NugetRoot); - CleanDirectory(data.ZipRoot); - CleanDirectory(data.TestResultsRoot); -}); - -void DotNetCoreBuild(Parameters parameters) -{ - var settings = new DotNetCoreBuildSettings - { - Configuration = parameters.Configuration, - MSBuildSettings = new DotNetCoreMSBuildSettings - { - Properties = - { - { "PackageVersion", new [] { parameters.Version } } - } - } - }; - - DotNetCoreBuild(parameters.MSBuildSolution, settings); -} - -Task("Build-Impl") - .Does(data => -{ - if(data.IsRunningOnWindows) - { - MSBuild(data.MSBuildSolution, settings => { - settings.SetConfiguration(data.Configuration); - settings.SetVerbosity(Verbosity.Minimal); - settings.WithProperty("iOSRoslynPathHackRequired", "true"); - settings.WithProperty("PackageVersion", data.Version); - settings.UseToolVersion(MSBuildToolVersion.VS2017); - settings.WithRestore(); - }); - } - else - { - DotNetCoreBuild(data); - } -}); - -void RunCoreTest(string project, Parameters parameters, bool coreOnly = false) -{ - if(!project.EndsWith(".csproj")) - project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); - Information("Running tests from " + project); - var frameworks = new List(){"netcoreapp2.0"}; - foreach(var fw in frameworks) - { - if(!fw.StartsWith("netcoreapp") && coreOnly) - continue; - Information("Running for " + fw); - - var settings = new DotNetCoreTestSettings { - Configuration = parameters.Configuration, - Framework = fw, - NoBuild = true, - NoRestore = true - }; - - if (parameters.PublishTestResults) - { - settings.Logger = "trx"; - settings.ResultsDirectory = parameters.TestResultsRoot; - } - - DotNetCoreTest(project, settings); - } -} - -Task("Run-Unit-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.Base.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Controls.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Input.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Layout.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Markup.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Styling.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Skia.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data, false); - if (data.IsRunningOnWindows) - { - RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data, false); - } -}); - -Task("Run-Designer-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data, false); -}); - -Task("Run-Render-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .WithCriteria((context, data) => data.IsRunningOnWindows) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data, true); - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data, true); -}); - -Task("Run-Leak-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .WithCriteria((context, data) => data.IsRunningOnWindows) - .Does(() => -{ - var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe"); - var leakTestsExitCode = StartProcess(dotMemoryUnit, new ProcessSettings - { - Arguments = new ProcessArgumentBuilder() - .Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath) - .Append("--propagate-exit-code") - .Append("--") - .Append("tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"), - Timeout = 120000 - }); - - if (leakTestsExitCode != 0) - { - throw new Exception("Leak Tests failed"); - } -}); - -Task("Zip-Files-Impl") - .Does(data => -{ - Zip(data.BinRoot, data.ZipCoreArtifacts); - - Zip(data.NugetRoot, data.ZipNuGetArtifacts); - - Zip(data.ZipSourceControlCatalogDesktopDirs, - data.ZipTargetControlCatalogDesktopDirs, - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe")); -}); - -void DotNetCorePack(Parameters parameters) -{ - var settings = new DotNetCorePackSettings - { - Configuration = parameters.Configuration, - MSBuildSettings = new DotNetCoreMSBuildSettings - { - Properties = - { - { "PackageVersion", new [] { parameters.Version } } - } - } - }; - - DotNetCorePack(parameters.MSBuildSolution, settings); -} - -Task("Create-NuGet-Packages-Impl") - .Does(data => -{ - if(data.IsRunningOnWindows) - { - MSBuild(data.MSBuildSolution, settings => { - settings.SetConfiguration(data.Configuration); - settings.SetVerbosity(Verbosity.Minimal); - settings.WithProperty("iOSRoslynPathHackRequired", "true"); - settings.WithProperty("PackageVersion", data.Version); - settings.UseToolVersion(MSBuildToolVersion.VS2017); - settings.WithRestore(); - settings.WithTarget("Pack"); - }); - } - else - { - DotNetCorePack(data); - } -}); - -/////////////////////////////////////////////////////////////////////////////// -// TARGETS -/////////////////////////////////////////////////////////////////////////////// - -Task("Build") - .IsDependentOn("Clean-Impl") - .IsDependentOn("Build-Impl"); - -Task("Run-Tests") - .IsDependentOn("Build") - .IsDependentOn("Run-Unit-Tests-Impl") - .IsDependentOn("Run-Render-Tests-Impl") - .IsDependentOn("Run-Designer-Tests-Impl") - .IsDependentOn("Run-Leak-Tests-Impl"); - -Task("Package") - .IsDependentOn("Run-Tests") - .IsDependentOn("Create-NuGet-Packages-Impl"); - -Task("AppVeyor") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -Task("Travis") - .IsDependentOn("Run-Tests"); - -Task("Azure-Linux") - .IsDependentOn("Run-Tests"); - -Task("Azure-OSX") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -Task("Azure-Windows") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -/////////////////////////////////////////////////////////////////////////////// -// EXECUTE -/////////////////////////////////////////////////////////////////////////////// - -var target = Context.Argument("target", "Default"); - -if (target == "Default") -{ - target = Context.IsRunningOnWindows() ? "Package" : "Run-Tests"; -} - -RunTarget(target); diff --git a/build.ps1 b/build.ps1 index 46696db2b2..57e2f80075 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,201 +1,69 @@ -########################################################################## -# This is the Cake bootstrapper script for PowerShell. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## - -<# - -.SYNOPSIS -This is a Powershell script to bootstrap a Cake build. - -.DESCRIPTION -This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) -and execute your Cake build script with the parameters you provide. - -.PARAMETER Script -The build script to execute. -.PARAMETER Target -The build script target to run. -.PARAMETER Platform -The build platform to use. -.PARAMETER Configuration -The build configuration to use. -.PARAMETER Verbosity -Specifies the amount of information to be displayed. -.PARAMETER Experimental -Tells Cake to use the latest Roslyn release. -.PARAMETER WhatIf -Performs a dry run of the build script. -No tasks will be executed. -.PARAMETER Mono -Tells Cake to use the Mono scripting engine. -.PARAMETER SkipToolPackageRestore -Skips restoring of packages. -.PARAMETER SkipTests -Skips unit tests -.PARAMETER ScriptArgs -Remaining arguments are added here. - -.LINK -http://cakebuild.net - -#> - [CmdletBinding()] Param( - [string]$Script = "build.cake", - [string]$Target = "Default", - [ValidateSet("Any CPU", "x86", "x64", "NetCoreOnly", "iPhone")] - [string]$Platform = "Any CPU", - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity = "Verbose", - [switch]$Experimental, - [Alias("DryRun","Noop")] - [switch]$WhatIf, - [switch]$Mono, - [switch]$SkipToolPackageRestore, + #[switch]$CustomParam, [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] - [string[]]$ScriptArgs + [string[]]$BuildArguments ) -[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null -function MD5HashFile([string] $filePath) -{ - if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) - { - return $null - } - - [System.IO.Stream] $file = $null; - [System.Security.Cryptography.MD5] $md5 = $null; - try - { - $md5 = [System.Security.Cryptography.MD5]::Create() - $file = [System.IO.File]::OpenRead($filePath) - return [System.BitConverter]::ToString($md5.ComputeHash($file)) - } - finally - { - if ($file -ne $null) - { - $file.Dispose() - } - } -} - -Write-Host "Preparing to run build script..." - -if(!$PSScriptRoot){ - $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -} +Write-Output "Windows PowerShell $($Host.Version)" -$TOOLS_DIR = Join-Path $PSScriptRoot "tools" -$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" -$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" -$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" -$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" +Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 } +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -# Should we use mono? -$UseMono = ""; -if($Mono.IsPresent) { - Write-Verbose -Message "Using the Mono based scripting engine." - $UseMono = "-mono" -} +########################################################################### +# CONFIGURATION +########################################################################### -# Should we use the new Roslyn? -$UseExperimental = ""; -if($Experimental.IsPresent -and !($Mono.IsPresent)) { - Write-Verbose -Message "Using experimental version of Roslyn." - $UseExperimental = "-experimental" -} +$BuildProjectFile = "$PSScriptRoot\nukebuild\_build.csproj" +$TempDirectory = "$PSScriptRoot\\.tmp" -# Is this a dry run? -$UseDryRun = ""; -if($WhatIf.IsPresent) { - $UseDryRun = "-dryrun" -} +$DotNetGlobalFile = "$PSScriptRoot\\global.json" +$DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1" +$DotNetChannel = "Current" -# Is this a dry run? -$UseSkipTests = ""; -if($SkipTests.IsPresent) { - $UseSkipTests = "-skip-tests" -} +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 +$env:NUGET_XMLDOC_MODE = "skip" -# Make sure tools folder exists -if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { - Write-Verbose -Message "Creating tools directory..." - New-Item -Path $TOOLS_DIR -Type directory | out-null -} +########################################################################### +# EXECUTION +########################################################################### -# Make sure that packages.config exist. -if (!(Test-Path $PACKAGES_CONFIG)) { - Write-Verbose -Message "Downloading packages.config..." - try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { - Throw "Could not download packages.config." - } +function ExecSafe([scriptblock] $cmd) { + & $cmd + if ($LASTEXITCODE) { exit $LASTEXITCODE } } -# Try find NuGet.exe in path if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Trying to find nuget.exe in PATH..." - $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } - $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 - if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { - Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." - $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName +# If global.json exists, load expected version +if (Test-Path $DotNetGlobalFile) { + $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) + if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { + $DotNetVersion = $DotNetGlobal.sdk.version } } -# Try download NuGet.exe if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Downloading NuGet.exe..." - try { - (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) - } catch { - Throw "Could not download NuGet.exe." - } +# If dotnet is installed locally, and expected version is not set or installation matches the expected version +if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and ` + (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) { + $env:DOTNET_EXE = (Get-Command "dotnet").Path } - -# Save nuget.exe path to environment to be available to child processed -$ENV:NUGET_EXE = $NUGET_EXE - -# Restore tools from NuGet? -if(-Not $SkipToolPackageRestore.IsPresent) { - Push-Location - Set-Location $TOOLS_DIR - - # Check for changes in packages.config and remove installed tools if true. - [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) - if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or - ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { - Write-Verbose -Message "Missing or changed package.config hash..." - Remove-Item * -Recurse -Exclude packages.config,nuget.exe +else { + $DotNetDirectory = "$TempDirectory\dotnet-win" + $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" + + # Download install script + $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" + md -force $TempDirectory > $null + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) + + # Install by channel or version + if (!(Test-Path variable:DotNetVersion)) { + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } + } else { + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } } - - Write-Verbose -Message "Restoring tools from NuGet..." - $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" - - if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet tools." - } - else - { - $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" - } - Write-Verbose -Message ($NuGetOutput | out-string) - Pop-Location } -# Make sure that Cake has been installed. -if (!(Test-Path $CAKE_EXE)) { - Throw "Could not find Cake.exe at $CAKE_EXE" -} +Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" -# Start Cake -Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -platform=`"$Platform`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseSkipTests $UseMono $UseDryRun $UseExperimental $ScriptArgs" -exit $LASTEXITCODE \ No newline at end of file +ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments } diff --git a/build.sh b/build.sh index 206a55d171..40b1c225a6 100755 --- a/build.sh +++ b/build.sh @@ -1,105 +1,72 @@ #!/usr/bin/env bash -########################################################################## -# This is the Cake bootstrapper script for Linux and OS X. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## +echo $(bash --version 2>&1 | head -n 1) -# Define directories. -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -TOOLS_DIR=$SCRIPT_DIR/tools -NUGET_EXE=$TOOLS_DIR/nuget.exe -CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe -PACKAGES_CONFIG=$TOOLS_DIR/packages.config -PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum - -# Define md5sum or md5 depending on Linux/OSX -MD5_EXE= -if [[ "$(uname -s)" == "Darwin" ]]; then - MD5_EXE="md5 -r" -else - MD5_EXE="md5sum" -fi - -# Define default arguments. -SCRIPT="build.cake" -TARGET="Default" -CONFIGURATION="Release" -PLATFORM="Any CPU" -VERBOSITY="verbose" -DRYRUN= -SKIP_TESTS= -SHOW_VERSION=false -SCRIPT_ARGUMENTS=() - -# Parse arguments. +#CUSTOMPARAM=0 +BUILD_ARGUMENTS=() for i in "$@"; do - case $1 in - -s|--script) SCRIPT="$2"; shift ;; - -t|--target) TARGET="$2"; shift ;; - -p|--platform) PLATFORM="$2"; shift ;; - -c|--configuration) CONFIGURATION="$2"; shift ;; - --skip-tests) SKIP_TESTS="-skip-tests"; shift ;; - -v|--verbosity) VERBOSITY="$2"; shift ;; - -d|--dryrun) DRYRUN="-dryrun" ;; - --version) SHOW_VERSION=true ;; - --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; - *) SCRIPT_ARGUMENTS+=("$1") ;; + case $(echo $1 | awk '{print tolower($0)}') in + # -custom-param) CUSTOMPARAM=1;; + *) BUILD_ARGUMENTS+=("$1") ;; esac shift done -# Make sure the tools folder exist. -if [ ! -d "$TOOLS_DIR" ]; then - mkdir "$TOOLS_DIR" -fi +set -eo pipefail +SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) -# Make sure that packages.config exist. -if [ ! -f "$TOOLS_DIR/packages.config" ]; then - echo "Downloading packages.config..." - curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages - if [ $? -ne 0 ]; then - echo "An error occured while downloading packages.config." - exit 1 - fi -fi +########################################################################### +# CONFIGURATION +########################################################################### -# Download NuGet if it does not exist. -if [ ! -f "$NUGET_EXE" ]; then - echo "Downloading NuGet..." - curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe - if [ $? -ne 0 ]; then - echo "An error occured while downloading nuget.exe." - exit 1 - fi -fi +BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj" +TEMP_DIRECTORY="$SCRIPT_DIR//.tmp" -# Restore tools from NuGet. -pushd "$TOOLS_DIR" >/dev/null -if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then - find . -type d ! -name . | xargs rm -rf -fi +DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" +DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh" +DOTNET_CHANNEL="Current" -mono "$NUGET_EXE" install -ExcludeVersion -if [ $? -ne 0 ]; then - echo "Could not restore NuGet packages." - exit 1 -fi +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export NUGET_XMLDOC_MODE="skip" -$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5 +########################################################################### +# EXECUTION +########################################################################### -popd >/dev/null +function FirstJsonValue { + perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2} +} -# Make sure that Cake has been installed. -if [ ! -f "$CAKE_EXE" ]; then - echo "Could not find Cake.exe at '$CAKE_EXE'." - exit 1 +# If global.json exists, load expected version +if [ -f "$DOTNET_GLOBAL_FILE" ]; then + DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE")) + if [ "$DOTNET_VERSION" == "" ]; then + unset DOTNET_VERSION + fi fi -# Start Cake -if $SHOW_VERSION; then - exec mono "$CAKE_EXE" -version +# If dotnet is installed locally, and expected version is not set or installation matches the expected version +if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then + export DOTNET_EXE="$(command -v dotnet)" else - exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -platform="$PLATFORM" -configuration="$CONFIGURATION" -target=$TARGET $DRYRUN $SKIP_TESTS "${SCRIPT_ARGUMENTS[@]}" + DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" + export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" + + # Download install script + DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" + mkdir -p "$TEMP_DIRECTORY" + curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" + chmod +x "$DOTNET_INSTALL_FILE" + + # Install by channel or version + if [ -z ${DOTNET_VERSION+x} ]; then + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path + else + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path + fi fi + +echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" + +"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} diff --git a/cake.config b/cake.config deleted file mode 100644 index 8089cd4084..0000000000 --- a/cake.config +++ /dev/null @@ -1,15 +0,0 @@ -; This is the default configuration file for Cake. -; This file was downloaded from https://github.com/cake-build/resources - -[Nuget] -Source=https://api.nuget.org/v3/index.json -UseInProcessClient=true -LoadDependencies=false - -[Paths] -Tools=./tools -Addins=./tools/Addins -Modules=./tools/Modules - -[Settings] -SkipVerification=false diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs new file mode 100644 index 0000000000..c937933493 --- /dev/null +++ b/nukebuild/Build.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using Nuke.Common; +using Nuke.Common.Git; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.MSBuild; +using Nuke.Common.Utilities; +using static Nuke.Common.EnvironmentInfo; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.MSBuild.MSBuildTasks; +using static Nuke.Common.Tools.DotNet.DotNetTasks; +using static Nuke.Common.Tools.Xunit.XunitTasks; + + +/* + Before editing this file, install support plugin for your IDE, + running and debugging a particular target (optionally without deps) would be way easier + ReSharper/Rider - https://plugins.jetbrains.com/plugin/10803-nuke-support + VSCode - https://marketplace.visualstudio.com/items?itemName=nuke.support + + */ + +partial class Build : NukeBuild +{ + BuildParameters Parameters { get; set; } + protected override void OnBuildInitialized() + { + Parameters = new BuildParameters(this); + Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.", + Parameters.Version, + Parameters.Configuration, + typeof(NukeBuild).Assembly.GetName().Version.ToString()); + + if (Parameters.IsLocalBuild) + { + Information("Repository Name: " + Parameters.RepositoryName); + Information("Repository Branch: " + Parameters.RepositoryBranch); + } + Information("Configuration: " + Parameters.Configuration); + Information("IsLocalBuild: " + Parameters.IsLocalBuild); + Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix); + Information("IsRunningOnWindows: " + Parameters.IsRunningOnWindows); + Information("IsRunningOnAppVeyor: " + Parameters.IsRunningOnAppVeyor); + Information("IsRunnongOnAzure:" + Parameters.IsRunningOnAzure); + Information("IsPullRequest: " + Parameters.IsPullRequest); + Information("IsMainRepo: " + Parameters.IsMainRepo); + Information("IsMasterBranch: " + Parameters.IsMasterBranch); + Information("IsReleaseBranch: " + Parameters.IsReleaseBranch); + Information("IsTagged: " + Parameters.IsTagged); + Information("IsReleasable: " + Parameters.IsReleasable); + Information("IsMyGetRelease: " + Parameters.IsMyGetRelease); + Information("IsNuGetRelease: " + Parameters.IsNuGetRelease); + } + + Target Clean => _ => _.Executes(() => + { + var data = Parameters; + DeleteDirectories(data.BuildDirs); + EnsureCleanDirectories(data.BuildDirs); + EnsureCleanDirectory(data.ArtifactsDir); + EnsureCleanDirectory(data.NugetRoot); + EnsureCleanDirectory(data.ZipRoot); + EnsureCleanDirectory(data.TestResultsRoot); + }); + + + Target Compile => _ => _ + .DependsOn(Clean) + .Executes(() => + { + var data = Parameters; + if (data.IsRunningOnWindows) + MSBuild(data.MSBuildSolution, c => c + .SetConfiguration(data.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", "true") + .SetToolsVersion(MSBuildToolsVersion._15_0) + .AddTargets("Restore", "Build") + ); + + else + DotNetBuild(Parameters.MSBuildSolution, c => c + .AddProperty("PackageVersion", Parameters.Version) + .SetConfiguration(Parameters.Configuration) + ); + }); + + void RunCoreTest(string project, bool coreOnly = false) + { + if(!project.EndsWith(".csproj")) + project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); + Information("Running tests from " + project); + var frameworks = new List(){"netcoreapp2.0"}; + foreach(var fw in frameworks) + { + if(!fw.StartsWith("netcoreapp") && coreOnly) + continue; + Information("Running for " + fw); + DotNetTest(c => + { + c = c + .SetProjectFile(project) + .SetConfiguration(Parameters.Configuration) + .SetFramework(fw) + .EnableNoBuild() + .EnableNoRestore(); + // NOTE: I can see that we could maybe add another extension method "Switch" or "If" to make this more convenient + if (Parameters.PublishTestResults) + c = c.SetLogger("trx").SetResultsDirectory(Parameters.TestResultsRoot); + return c; + }); + } + } + + Target RunCoreLibsTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests) + .DependsOn(Compile) + .Executes(() => + { + + RunCoreTest("./tests/Avalonia.Base.UnitTests", false); + RunCoreTest("./tests/Avalonia.Controls.UnitTests", false); + RunCoreTest("./tests/Avalonia.Input.UnitTests", false); + RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", false); + RunCoreTest("./tests/Avalonia.Layout.UnitTests", false); + RunCoreTest("./tests/Avalonia.Markup.UnitTests", false); + RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", false); + RunCoreTest("./tests/Avalonia.Styling.UnitTests", false); + RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false); + RunCoreTest("./tests/Avalonia.Skia.UnitTests", false); + RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false); + + }); + + Target RunRenderTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true); + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); + }); + + Target RunDesignerTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", false); + }); + + [PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit; + + Target RunLeakTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + + var dotMemoryUnitPath = + ToolPathResolver.GetPackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe"); + var xunitRunnerPath = + ToolPathResolver.GetPackageExecutable("xunit.runner.console", "xunit.console.x86.exe"); + var args = new[] + { + Path.GetFullPath(xunitRunnerPath), + "--propagate-exit-code", + "--", + "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll" + }; + var cargs = string.Join(" ", args.Select(a => '"' + a + '"')); + + var proc = Process.Start(new ProcessStartInfo(dotMemoryUnitPath, cargs) + { + UseShellExecute = false + }); + + if (!proc.WaitForExit(120000)) + { + proc.Kill(); + throw new Exception("Leak tests timed out"); + } + + var leakTestsExitCode = proc.ExitCode; + + if (leakTestsExitCode != 0) + { + throw new Exception("Leak Tests failed"); + } + + + var testAssembly = "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"; + DotMemoryUnit( + $"{XunitPath.DoubleQuoteIfNeeded()} --propagate-exit-code -- {testAssembly}", + timeout: 120_000); + }); + + Target ZipFiles => _ => _ + .After(CreateNugetPackages, Compile, RunCoreLibsTests, Package) + .Executes(() => + { + var data = Parameters; + Zip(data.ZipCoreArtifacts, data.BinRoot); + + Zip(data.ZipNuGetArtifacts, data.NugetRoot); + + Zip(data.ZipTargetControlCatalogDesktopDir, + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.so")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dylib")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.exe"))); + }); + + Target CreateNugetPackages => _ => _ + .DependsOn(Compile) + .After(RunTests) + .Executes(() => + { + if (Parameters.IsRunningOnWindows) + + MSBuild(Parameters.MSBuildSolution, c => c + .SetConfiguration(Parameters.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", "true") + .SetToolsVersion(MSBuildToolsVersion._15_0) + .AddTargets("Restore", "Pack")); + else + DotNetPack(Parameters.MSBuildSolution, c => + c.SetConfiguration(Parameters.Configuration) + .AddProperty("PackageVersion", Parameters.Version)); + }); + + Target RunTests => _ => _ + .DependsOn(RunCoreLibsTests) + .DependsOn(RunRenderTests) + .DependsOn(RunDesignerTests) + .DependsOn(RunLeakTests); + + Target Package => _ => _ + .DependsOn(RunTests) + .DependsOn(CreateNugetPackages); + + Target CiAppVeyor => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + Target CiTravis => _ => _ + .DependsOn(RunTests); + + Target CiAsuzeLinux => _ => _ + .DependsOn(RunTests); + + Target CiAsuzeOSX => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + Target CiAsuzeWindows => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + + public static int Main() => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Execute(x => x.Package) + : Execute(x => x.RunTests); + +} diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs new file mode 100644 index 0000000000..322871e5db --- /dev/null +++ b/nukebuild/BuildParameters.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using Nuke.Common; +using Nuke.Common.BuildServers; +using Nuke.Common.Execution; +using Nuke.Common.IO; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.MSBuild.MSBuildTasks; + +public partial class Build +{ + [Parameter("configuration")] + public string NukeArgConfiguration { get; set; } + + [Parameter("skip-tests")] + public bool NukeArgSkipTests { get; set; } + + [Parameter("force-nuget-version")] + public string NukeArgForceNugetVersion { get; set; } + + public class BuildParameters + { + public string Configuration { get; } + public bool SkipTests { get; } + public string MainRepo { get; } + public string MasterBranch { get; } + public string RepositoryName { get; } + public string RepositoryBranch { get; } + public string ReleaseConfiguration { get; } + public string ReleaseBranchPrefix { get; } + public string MSBuildSolution { get; } + public bool IsLocalBuild { get; } + public bool IsRunningOnUnix { get; } + public bool IsRunningOnWindows { get; } + public bool IsRunningOnAppVeyor { get; } + public bool IsRunningOnAzure { get; } + public bool IsPullRequest { get; } + public bool IsMainRepo { get; } + public bool IsMasterBranch { get; } + public bool IsReleaseBranch { get; } + public bool IsTagged { get; } + public bool IsReleasable { get; } + public bool IsMyGetRelease { get; } + public bool IsNuGetRelease { get; } + public bool PublishTestResults { get; } + public string Version { get; } + public AbsolutePath ArtifactsDir { get; } + public AbsolutePath NugetRoot { get; } + public AbsolutePath ZipRoot { get; } + public AbsolutePath BinRoot { get; } + public AbsolutePath TestResultsRoot { get; } + public string DirSuffix { get; } + public List BuildDirs { get; } + public string FileZipSuffix { get; } + public AbsolutePath ZipCoreArtifacts { get; } + public AbsolutePath ZipNuGetArtifacts { get; } + public AbsolutePath ZipSourceControlCatalogDesktopDir { get; } + public AbsolutePath ZipTargetControlCatalogDesktopDir { get; } + + + public BuildParameters(Build b) + { + var buildSystem = Host; + + + // ARGUMENTS + Configuration = b.NukeArgConfiguration ?? "Release"; + SkipTests = b.NukeArgSkipTests; + + // CONFIGURATION + MainRepo = "https://github.com/AvaloniaUI/Avalonia"; + MasterBranch = "refs/heads/master"; + ReleaseBranchPrefix = "refs/heads/release/"; + ReleaseConfiguration = "Release"; + MSBuildSolution = RootDirectory / "dirs.proj"; + + // PARAMETERS + IsLocalBuild = buildSystem == HostType.Console; + IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix || + Environment.OSVersion.Platform == PlatformID.MacOSX; + IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsRunningOnAppVeyor = buildSystem == HostType.AppVeyor; + IsRunningOnAzure = buildSystem == HostType.TeamServices || + Environment.GetEnvironmentVariable("LOGNAME") == "vsts"; + + string tagName = null; + if (IsRunningOnAppVeyor) + { + IsPullRequest = AppVeyor.Instance.PullRequestNumber != 0; + RepositoryName = Environment.GetEnvironmentVariable("BUILD_REPOSITORY_URI"); + RepositoryBranch = Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH"); + + IsReleaseBranch = + (Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH") ?? "").StartsWith(ReleaseBranchPrefix, + StringComparison.OrdinalIgnoreCase); + IsTagged = AppVeyor.Instance.RepositoryTag + && !string.IsNullOrWhiteSpace(AppVeyor.Instance.RepositoryTagName); + + tagName = AppVeyor.Instance.RepositoryTagName; + } + else if (IsRunningOnAzure) + { + RepositoryName = TeamServices.Instance.RepositoryUri; + RepositoryBranch = TeamServices.Instance.SourceBranch; + IsPullRequest = TeamServices.Instance.PullRequestId.HasValue; + IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri); + + // TODO??? + IsTagged = false; + tagName = null; + } + IsMainRepo = + StringComparer.OrdinalIgnoreCase.Equals(MainRepo, + RepositoryName); + IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, + RepositoryBranch); + + + IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); + IsMyGetRelease = !IsTagged && IsReleasable; + IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; + + // VERSION + Version = b.NukeArgForceNugetVersion ?? GetVersion(); + + if (IsRunningOnAppVeyor) + { + string tagVersion = null; + if (IsTagged) + { + var tag = tagName; + var nugetReleasePrefix = "nuget-release-"; + IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix); + if (IsNuGetRelease) + tagVersion = tag.Substring(nugetReleasePrefix.Length); + } + + if (tagVersion != null) + { + Version = tagVersion; + } + else + { + // Use AssemblyVersion with Build as version + Version += "-build" + Environment.GetEnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta"; + } + } + else if (IsRunningOnAzure) + { + if (!IsNuGetRelease) + { + // Use AssemblyVersion with Build as version + Version += "-build" + Environment.GetEnvironmentVariable("BUILD_BUILDID") + "-beta"; + } + + PublishTestResults = true; + } + + // DIRECTORIES + ArtifactsDir = RootDirectory / "artifacts"; + NugetRoot = ArtifactsDir / "nuget"; + ZipRoot = ArtifactsDir / "zip"; + BinRoot = ArtifactsDir / "bin"; + TestResultsRoot = ArtifactsDir / "test-results"; + BuildDirs = GlobDirectories(RootDirectory, "**bin").Concat(GlobDirectories(RootDirectory, "**obj")).ToList(); + DirSuffix = Configuration; + FileZipSuffix = Version + ".zip"; + ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix); + ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix); + ZipSourceControlCatalogDesktopDir = + RootDirectory / ("samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461"); + ZipTargetControlCatalogDesktopDir = ZipRoot / ("ControlCatalog.Desktop-" + FileZipSuffix); + } + + private static string GetVersion() + { + var xdoc = XDocument.Load("./build/SharedVersion.props"); + return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value; + } + } + +} diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs new file mode 100644 index 0000000000..7f26490493 --- /dev/null +++ b/nukebuild/Shims.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Nuke.Common; +using Nuke.Common.IO; + +public partial class Build +{ + static void Information(string info) + { + Logger.Info(info); + } + + static void Information(string info, params object[] args) + { + Logger.Info(info, args); + } + + private void Zip(PathConstruction.AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable()); + + private void Zip(PathConstruction.AbsolutePath target, IEnumerable paths) + { + var targetPath = target.ToString(); + bool finished = false, atLeastOneFileAdded = false; + try + { + using (var targetStream = File.Create(targetPath)) + using(var archive = new System.IO.Compression.ZipArchive(targetStream, ZipArchiveMode.Create)) + { + void AddFile(string path, string relativePath) + { + var e = archive.CreateEntry(relativePath.Replace("\\", "/"), CompressionLevel.Optimal); + using (var entryStream = e.Open()) + using (var fileStream = File.OpenRead(path)) + fileStream.CopyTo(entryStream); + atLeastOneFileAdded = true; + } + + foreach (var path in paths) + { + + if (Directory.Exists(path)) + { + var dirInfo = new DirectoryInfo(path); + var rootPath = Path.GetDirectoryName(dirInfo.FullName); + foreach(var fsEntry in dirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) + { + if (fsEntry is FileInfo) + { + var relPath = Path.GetRelativePath(rootPath, fsEntry.FullName); + AddFile(fsEntry.FullName, relPath); + } + } + } + else if(File.Exists(path)) + { + var name = Path.GetFileName(path); + AddFile(path, name); + } + } + } + + finished = true; + } + finally + { + try + { + if (!finished || !atLeastOneFileAdded) + File.Delete(targetPath); + } + catch + { + //Ignore + } + } + } +} diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj new file mode 100644 index 0000000000..ed94116a93 --- /dev/null +++ b/nukebuild/_build.csproj @@ -0,0 +1,35 @@ + + + + Exe + netcoreapp2.0 + false + + False + CS0649;CS0169 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nukebuild/_build.csproj.DotSettings b/nukebuild/_build.csproj.DotSettings new file mode 100644 index 0000000000..9aac7d8e8d --- /dev/null +++ b/nukebuild/_build.csproj.DotSettings @@ -0,0 +1,24 @@ + + False + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True From 07475d12f9d973c307de4584e291def4ec009d4a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:56:07 +0000 Subject: [PATCH 053/207] simplify apply styles --- src/Windows/Avalonia.Win32/WindowImpl.cs | 69 ++++++++++++------------ 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 8343ccbd44..4316166343 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,7 +271,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _decorated = value); + _decorated = value; + + UpdateWMStyles(); } public void Invalidate(Rect rect) @@ -884,15 +886,15 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles(Action changer) + private void UpdateWMStyles() { - var decorated = _decorated; + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); - var resizable = _resizable; + const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; - changer(); + style = style | controlledFlags ^ controlledFlags; - var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE) | WindowStyles.WS_OVERLAPPEDWINDOW; + style |= WindowStyles.WS_OVERLAPPEDWINDOW; if (!_decorated) { @@ -904,40 +906,39 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } + UnmanagedMethods.GetWindowRect(_hwnd, out var oldRect); + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); - if (decorated != _decorated) - { - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - var oldThickness = BorderThickness; + var oldThickness = BorderThickness; - Rect newRect; + Rect newRect; - if (_decorated) - { - var thickness = BorderThickness; + if (_decorated) + { + var thickness = BorderThickness; - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - } } public void CanResize(bool value) @@ -947,7 +948,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _resizable = value); + _resizable = value; + + UpdateWMStyles(); } public void SetTopmost(bool value) From ca59da108ed2a35b1b49e1ab1c1a051bf31db100 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:08:29 +0000 Subject: [PATCH 054/207] [win32] smoothly toggle decorations. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4316166343..1383a81dd6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -906,14 +906,12 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } - UnmanagedMethods.GetWindowRect(_hwnd, out var oldRect); + var oldThickness = BorderThickness; SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - var oldThickness = BorderThickness; - Rect newRect; if (_decorated) From 333c0ac907d57bda332600283e6ab724d465b346 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:13:20 +0000 Subject: [PATCH 055/207] simply applywm styles. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1383a81dd6..146ab496fa 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -894,16 +894,20 @@ namespace Avalonia.Win32 style = style | controlledFlags ^ controlledFlags; - style |= WindowStyles.WS_OVERLAPPEDWINDOW; + style |= WindowStyles.WS_OVERLAPPED | + WindowStyles.WS_MINIMIZEBOX | + WindowStyles.WS_MAXIMIZEBOX; - if (!_decorated) + if (_decorated) { - style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); + style |= + WindowStyles.WS_CAPTION | + WindowStyles.WS_SYSMENU; } - if (!_resizable) + if (_resizable) { - style ^= (WindowStyles.WS_SIZEFRAME); + style |= WindowStyles.WS_SIZEFRAME; } var oldThickness = BorderThickness; @@ -936,7 +940,6 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - } public void CanResize(bool value) From 99a1392febc429eaae2dbc6234ee4893fb375de1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:16:04 +0000 Subject: [PATCH 056/207] remove flags from overlapped. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 146ab496fa..1383a81dd6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -894,20 +894,16 @@ namespace Avalonia.Win32 style = style | controlledFlags ^ controlledFlags; - style |= WindowStyles.WS_OVERLAPPED | - WindowStyles.WS_MINIMIZEBOX | - WindowStyles.WS_MAXIMIZEBOX; + style |= WindowStyles.WS_OVERLAPPEDWINDOW; - if (_decorated) + if (!_decorated) { - style |= - WindowStyles.WS_CAPTION | - WindowStyles.WS_SYSMENU; + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); } - if (_resizable) + if (!_resizable) { - style |= WindowStyles.WS_SIZEFRAME; + style ^= (WindowStyles.WS_SIZEFRAME); } var oldThickness = BorderThickness; @@ -940,6 +936,7 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + } public void CanResize(bool value) From 7d1f145f9331ba3b0cc47a3963b4324e20d38f3b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:34:19 +0000 Subject: [PATCH 057/207] add CanResize checkbox to decorated window. --- samples/ControlCatalog/DecoratedWindow.xaml | 63 ++++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 64 +++++++++++---------- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 9f05cd1f57..b2462cda26 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,36 +3,37 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - - - - - - - - - - - - + Decorated + CanResize + + + + + + + + + + + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1383a81dd6..22dd6142fc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,9 +271,7 @@ namespace Avalonia.Win32 return; } - _decorated = value; - - UpdateWMStyles(); + UpdateWMStyles(() => _decorated = value); } public void Invalidate(Rect rect) @@ -850,7 +848,8 @@ namespace Avalonia.Win32 private static int ToInt32(IntPtr ptr) { - if (IntPtr.Size == 4) return ptr.ToInt32(); + if (IntPtr.Size == 4) + return ptr.ToInt32(); return (int)(ptr.ToInt64() & 0xffffffff); } @@ -886,8 +885,13 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles() + private void UpdateWMStyles(Action change) { + var decorated = _decorated; + var resizable = _resizable; + + change(); + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; @@ -910,33 +914,35 @@ namespace Avalonia.Win32 SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - Rect newRect; - - if (_decorated) + if (decorated != _decorated) { var thickness = BorderThickness; - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + Rect newRect; + if (_decorated) + { + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } + + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + } } public void CanResize(bool value) @@ -946,9 +952,7 @@ namespace Avalonia.Win32 return; } - _resizable = value; - - UpdateWMStyles(); + UpdateWMStyles(() => _resizable = value); } public void SetTopmost(bool value) From 5d327abc65ab65da15e45febbfbe4583cd7404d0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:34:19 +0000 Subject: [PATCH 058/207] Revert "add CanResize checkbox to decorated window." This reverts commit 7d1f145f9331ba3b0cc47a3963b4324e20d38f3b. --- samples/ControlCatalog/DecoratedWindow.xaml | 63 ++++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 64 ++++++++++----------- 2 files changed, 61 insertions(+), 66 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index b2462cda26..9f05cd1f57 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,37 +3,36 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - CanResize - - - - - - - - - - - - + Decorated + + + + + + + + + + + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 22dd6142fc..1383a81dd6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,7 +271,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _decorated = value); + _decorated = value; + + UpdateWMStyles(); } public void Invalidate(Rect rect) @@ -848,8 +850,7 @@ namespace Avalonia.Win32 private static int ToInt32(IntPtr ptr) { - if (IntPtr.Size == 4) - return ptr.ToInt32(); + if (IntPtr.Size == 4) return ptr.ToInt32(); return (int)(ptr.ToInt64() & 0xffffffff); } @@ -885,13 +886,8 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles(Action change) + private void UpdateWMStyles() { - var decorated = _decorated; - var resizable = _resizable; - - change(); - var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; @@ -914,35 +910,33 @@ namespace Avalonia.Win32 SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); - if (decorated != _decorated) + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + + Rect newRect; + + if (_decorated) { var thickness = BorderThickness; - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - Rect newRect; + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } - if (_decorated) - { - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - } } public void CanResize(bool value) @@ -952,7 +946,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _resizable = value); + _resizable = value; + + UpdateWMStyles(); } public void SetTopmost(bool value) From 3ce39fe7a07eac3d111a7b261a4cde1b1400faaa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:42:56 +0000 Subject: [PATCH 059/207] use Action to set flags and recalculate wmstyles. --- samples/ControlCatalog/DecoratedWindow.xaml | 64 +++++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 59 ++++++++++--------- 2 files changed, 64 insertions(+), 59 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 9f05cd1f57..cb6016b324 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,36 +3,38 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - - - - - - - - - - - - + Decorated + + CanResize + + + + + + + + + + + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1383a81dd6..7a1802826d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,9 +271,7 @@ namespace Avalonia.Win32 return; } - _decorated = value; - - UpdateWMStyles(); + UpdateWMStyles(()=> _decorated = value); } public void Invalidate(Rect rect) @@ -886,8 +884,13 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles() + private void UpdateWMStyles(Action change) { + var decorated = _decorated; + var resizable = _resizable; + + change(); + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; @@ -912,31 +915,33 @@ namespace Avalonia.Win32 UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - Rect newRect; - - if (_decorated) + if (decorated != _decorated) { - var thickness = BorderThickness; + Rect newRect; - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + if (_decorated) + { + var thickness = BorderThickness; - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + } } public void CanResize(bool value) @@ -946,9 +951,7 @@ namespace Avalonia.Win32 return; } - _resizable = value; - - UpdateWMStyles(); + UpdateWMStyles(()=> _resizable = value); } public void SetTopmost(bool value) From b91233e3dfc0c706042cbe6c5470ae2a5711d0df Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:47:37 +0000 Subject: [PATCH 060/207] remove unused var --- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7a1802826d..5b5aeb88ab 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -887,7 +887,6 @@ namespace Avalonia.Win32 private void UpdateWMStyles(Action change) { var decorated = _decorated; - var resizable = _resizable; change(); From f0f7cf44272866ea7adf5962f7bd8caa527d8dcf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:53:50 +0000 Subject: [PATCH 061/207] [win32 window] fix BorderThickness --- src/Windows/Avalonia.Win32/WindowImpl.cs | 32 +++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5b5aeb88ab..3eeccb368c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -95,18 +95,25 @@ namespace Avalonia.Win32 { get { - var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + if (_decorated) + { + var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - var padding = new RECT(); + var padding = new RECT(); - if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) - { - return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); + if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) + { + return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); + } + else + { + throw new Win32Exception(); + } } else { - throw new Win32Exception(); + return new Thickness(); } } } @@ -149,12 +156,7 @@ namespace Avalonia.Win32 if (value != ClientSize) { value *= Scaling; - - if (_decorated) - { - value += BorderThickness; - } - + UnmanagedMethods.SetWindowPos( _hwnd, IntPtr.Zero, @@ -888,6 +890,8 @@ namespace Avalonia.Win32 { var decorated = _decorated; + var oldThickness = BorderThickness; + change(); var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); @@ -908,8 +912,6 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } - var oldThickness = BorderThickness; - SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); From a54b85bfa7507e9124f06e31bf1cfe8c00574233 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Dec 2018 20:17:06 +0300 Subject: [PATCH 062/207] A bit more clean method of new window rect calculation --- .../Interop/UnmanagedMethods.cs | 10 ++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 48 +++++++++---------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index adfbf0cb52..60b56ea580 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1172,6 +1172,8 @@ namespace Avalonia.Win32.Interop public int right; public int bottom; + public int Width => right - left; + public int Height => bottom - top; public RECT(Rect rect) { left = (int)rect.X; @@ -1179,6 +1181,14 @@ namespace Avalonia.Win32.Interop right = (int)(rect.X + rect.Width); bottom = (int)(rect.Y + rect.Height); } + + public void Offset(POINT pt) + { + left += pt.X; + right += pt.X; + top += pt.Y; + bottom += pt.Y; + } } [StructLayout(LayoutKind.Sequential)] diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3eeccb368c..1213be1bf0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -888,7 +888,7 @@ namespace Avalonia.Win32 private void UpdateWMStyles(Action change) { - var decorated = _decorated; + var oldDecorated = _decorated; var oldThickness = BorderThickness; @@ -912,37 +912,33 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } + GetClientRect(_hwnd, out var oldClientRect); + var oldClientRectOrigin = new UnmanagedMethods.POINT(); + ClientToScreen(_hwnd, ref oldClientRectOrigin); + oldClientRect.Offset(oldClientRectOrigin); + + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - if (decorated != _decorated) + bool frameUpdated = false; + if (oldDecorated != _decorated) { - Rect newRect; - + var newRect = oldClientRect; if (_decorated) - { - var thickness = BorderThickness; - - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } - - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + AdjustWindowRectEx(ref newRect, (uint)style, false, + GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE)); + _changingDecorations = true; + SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, + SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + frameUpdated = true; } + + if (!frameUpdated) + SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0, 0, + SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOZORDER | + SetWindowPosFlags.SWP_NOACTIVATE + | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE); } public void CanResize(bool value) From 6254036fb55a89bfc4dac0f8491c194e7cf0c2e0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Dec 2018 20:18:21 +0300 Subject: [PATCH 063/207] Opps --- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1213be1bf0..4f97f9a472 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -928,7 +928,6 @@ namespace Avalonia.Win32 if (_decorated) AdjustWindowRectEx(ref newRect, (uint)style, false, GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE)); - _changingDecorations = true; SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); frameUpdated = true; From 422d798108339e7c8a13f90e261654fa318278c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 20:56:10 +0000 Subject: [PATCH 064/207] fix non-client WM_Activate. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4f97f9a472..0b9d16daa0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -606,13 +606,19 @@ namespace Avalonia.Win32 break; case WindowsMessage.WM_NCPAINT: - case WindowsMessage.WM_NCACTIVATE: if (!_decorated) { return IntPtr.Zero; } break; + case WindowsMessage.WM_NCACTIVATE: + if (!_decorated) + { + return new IntPtr(1); + } + break; + case UnmanagedMethods.WindowsMessage.WM_PAINT: UnmanagedMethods.PAINTSTRUCT ps; if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) From 3c5ac8f0cc862694fc9932cc3a5f05fca9ad9da6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 2 Dec 2018 00:45:51 +0000 Subject: [PATCH 065/207] allow menu borders to be overridden. --- src/Avalonia.Themes.Default/MenuItem.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 07faf7a632..f2940227e1 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -49,7 +49,7 @@ ObeyScreenEdges="True"> + BorderThickness="{TemplateBinding BorderThickness}"> + BorderThickness="{TemplateBinding BorderThickness}"> Date: Sun, 2 Dec 2018 10:51:35 +0300 Subject: [PATCH 066/207] Fixed review comments --- .travis.yml | 24 ---------- appveyor.yml | 23 --------- azure-pipelines.yml | 6 +-- nukebuild/Build.cs | 90 +++++++++++------------------------- nukebuild/BuildParameters.cs | 60 +++--------------------- nukebuild/Shims.cs | 1 - 6 files changed, 36 insertions(+), 168 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d60323b418..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: csharp -os: - - linux -dist: trusty -osx_image: xcode8.3 -env: - global: - - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - - DOTNET_CLI_TELEMETRY_OPTOUT=1 -mono: - - 5.2.0 -dotnet: 2.1.200 -script: - - sudo apt-get update - - sudo apt-get install castxml - - ./build.sh --target "CiTravis" --configuration "Release" -notifications: - email: false - webhooks: - urls: - - https://webhooks.gitter.im/e/98f653320ef2b7506c05 - on_success: change - on_failure: always - on_start: never diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 8694495e66..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,23 +0,0 @@ -os: Visual Studio 2017 -skip_branch_with_pr: true -configuration: -- Release -environment: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - NUGET_API_URL: https://www.nuget.org/api/v2/package - MYGET_API_KEY: - secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK - MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package -init: -- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")} -before_build: -- git submodule update --init -build_script: -- ps: .\build.ps1 --target "CiAppVeyor" --configuration "$env:configuration" - -test: off -artifacts: - - path: artifacts\nuget\*.nupkg - - path: artifacts\zip\*.zip - - path: artifacts\inspectcode.xml diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dec361affe..8c5380e65e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - nuke --target="CiAzureLinux" --configuration="Release" + nuke --target CiAzureLinux --configuration=Release - task: PublishTestResults@2 inputs: @@ -71,7 +71,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - nuke --target="CiAzureOSX" --configuration="Release" + nuke --target CiAzureOSX --configuration Release - task: PublishTestResults@2 inputs: @@ -106,7 +106,7 @@ jobs: inputs: script: | set PATH=%PATH%;%USERPROFILE%\.dotnet\tools - nuke --target="CiAzureWindows" --configuration="Release" + nuke --target CiAzureWindows --configuration Release - task: PublishTestResults@2 inputs: diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index c937933493..63e889ae30 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -48,13 +48,11 @@ partial class Build : NukeBuild Information("IsLocalBuild: " + Parameters.IsLocalBuild); Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix); Information("IsRunningOnWindows: " + Parameters.IsRunningOnWindows); - Information("IsRunningOnAppVeyor: " + Parameters.IsRunningOnAppVeyor); - Information("IsRunnongOnAzure:" + Parameters.IsRunningOnAzure); + Information("IsRunningOnAzure:" + Parameters.IsRunningOnAzure); Information("IsPullRequest: " + Parameters.IsPullRequest); Information("IsMainRepo: " + Parameters.IsMainRepo); Information("IsMasterBranch: " + Parameters.IsMasterBranch); Information("IsReleaseBranch: " + Parameters.IsReleaseBranch); - Information("IsTagged: " + Parameters.IsTagged); Information("IsReleasable: " + Parameters.IsReleasable); Information("IsMyGetRelease: " + Parameters.IsMyGetRelease); Information("IsNuGetRelease: " + Parameters.IsNuGetRelease); @@ -62,29 +60,37 @@ partial class Build : NukeBuild Target Clean => _ => _.Executes(() => { - var data = Parameters; - DeleteDirectories(data.BuildDirs); - EnsureCleanDirectories(data.BuildDirs); - EnsureCleanDirectory(data.ArtifactsDir); - EnsureCleanDirectory(data.NugetRoot); - EnsureCleanDirectory(data.ZipRoot); - EnsureCleanDirectory(data.TestResultsRoot); + DeleteDirectories(Parameters.BuildDirs); + EnsureCleanDirectories(Parameters.BuildDirs); + EnsureCleanDirectory(Parameters.ArtifactsDir); + EnsureCleanDirectory(Parameters.NugetRoot); + EnsureCleanDirectory(Parameters.ZipRoot); + EnsureCleanDirectory(Parameters.TestResultsRoot); }); - + [Serializable] + class MsBuildSettingsWithRestore : MSBuildSettings + { + protected override Arguments ConfigureArguments(Arguments arguments) + { + arguments.Add("/restore"); + return base.ConfigureArguments(arguments); + } + } + Target Compile => _ => _ .DependsOn(Clean) .Executes(() => { - var data = Parameters; - if (data.IsRunningOnWindows) - MSBuild(data.MSBuildSolution, c => c - .SetConfiguration(data.Configuration) + + if (Parameters.IsRunningOnWindows) + MSBuild(Parameters.MSBuildSolution, c => new MsBuildSettingsWithRestore() + .SetConfiguration(Parameters.Configuration) .SetVerbosity(MSBuildVerbosity.Minimal) .AddProperty("PackageVersion", Parameters.Version) .AddProperty("iOSRoslynPathHackRequired", "true") .SetToolsVersion(MSBuildToolsVersion._15_0) - .AddTargets("Restore", "Build") + .AddTargets("Build") ); else @@ -126,7 +132,6 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Base.UnitTests", false); RunCoreTest("./tests/Avalonia.Controls.UnitTests", false); RunCoreTest("./tests/Avalonia.Input.UnitTests", false); @@ -138,7 +143,6 @@ partial class Build : NukeBuild RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false); RunCoreTest("./tests/Avalonia.Skia.UnitTests", false); RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false); - }); Target RunRenderTests => _ => _ @@ -165,39 +169,6 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - - var dotMemoryUnitPath = - ToolPathResolver.GetPackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe"); - var xunitRunnerPath = - ToolPathResolver.GetPackageExecutable("xunit.runner.console", "xunit.console.x86.exe"); - var args = new[] - { - Path.GetFullPath(xunitRunnerPath), - "--propagate-exit-code", - "--", - "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll" - }; - var cargs = string.Join(" ", args.Select(a => '"' + a + '"')); - - var proc = Process.Start(new ProcessStartInfo(dotMemoryUnitPath, cargs) - { - UseShellExecute = false - }); - - if (!proc.WaitForExit(120000)) - { - proc.Kill(); - throw new Exception("Leak tests timed out"); - } - - var leakTestsExitCode = proc.ExitCode; - - if (leakTestsExitCode != 0) - { - throw new Exception("Leak Tests failed"); - } - - var testAssembly = "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"; DotMemoryUnit( $"{XunitPath.DoubleQuoteIfNeeded()} --propagate-exit-code -- {testAssembly}", @@ -210,9 +181,7 @@ partial class Build : NukeBuild { var data = Parameters; Zip(data.ZipCoreArtifacts, data.BinRoot); - Zip(data.ZipNuGetArtifacts, data.NugetRoot); - Zip(data.ZipTargetControlCatalogDesktopDir, GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat( GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat( @@ -234,7 +203,7 @@ partial class Build : NukeBuild .AddProperty("PackageVersion", Parameters.Version) .AddProperty("iOSRoslynPathHackRequired", "true") .SetToolsVersion(MSBuildToolsVersion._15_0) - .AddTargets("Restore", "Pack")); + .AddTargets("Pack")); else DotNetPack(Parameters.MSBuildSolution, c => c.SetConfiguration(Parameters.Configuration) @@ -251,21 +220,14 @@ partial class Build : NukeBuild .DependsOn(RunTests) .DependsOn(CreateNugetPackages); - Target CiAppVeyor => _ => _ - .DependsOn(Package) - .DependsOn(ZipFiles); - - Target CiTravis => _ => _ - .DependsOn(RunTests); - - Target CiAsuzeLinux => _ => _ + Target CiAzureLinux => _ => _ .DependsOn(RunTests); - Target CiAsuzeOSX => _ => _ + Target CiAzureOSX => _ => _ .DependsOn(Package) .DependsOn(ZipFiles); - Target CiAsuzeWindows => _ => _ + Target CiAzureWindows => _ => _ .DependsOn(Package) .DependsOn(ZipFiles); diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index 322871e5db..9029aef60d 100644 --- a/nukebuild/BuildParameters.cs +++ b/nukebuild/BuildParameters.cs @@ -36,13 +36,11 @@ public partial class Build public bool IsLocalBuild { get; } public bool IsRunningOnUnix { get; } public bool IsRunningOnWindows { get; } - public bool IsRunningOnAppVeyor { get; } public bool IsRunningOnAzure { get; } public bool IsPullRequest { get; } public bool IsMainRepo { get; } public bool IsMasterBranch { get; } public bool IsReleaseBranch { get; } - public bool IsTagged { get; } public bool IsReleasable { get; } public bool IsMyGetRelease { get; } public bool IsNuGetRelease { get; } @@ -64,9 +62,6 @@ public partial class Build public BuildParameters(Build b) { - var buildSystem = Host; - - // ARGUMENTS Configuration = b.NukeArgConfiguration ?? "Release"; SkipTests = b.NukeArgSkipTests; @@ -79,77 +74,36 @@ public partial class Build MSBuildSolution = RootDirectory / "dirs.proj"; // PARAMETERS - IsLocalBuild = buildSystem == HostType.Console; + IsLocalBuild = Host == HostType.Console; IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX; IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - IsRunningOnAppVeyor = buildSystem == HostType.AppVeyor; - IsRunningOnAzure = buildSystem == HostType.TeamServices || + IsRunningOnAzure = Host == HostType.TeamServices || Environment.GetEnvironmentVariable("LOGNAME") == "vsts"; - string tagName = null; - if (IsRunningOnAppVeyor) - { - IsPullRequest = AppVeyor.Instance.PullRequestNumber != 0; - RepositoryName = Environment.GetEnvironmentVariable("BUILD_REPOSITORY_URI"); - RepositoryBranch = Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH"); - - IsReleaseBranch = - (Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH") ?? "").StartsWith(ReleaseBranchPrefix, - StringComparison.OrdinalIgnoreCase); - IsTagged = AppVeyor.Instance.RepositoryTag - && !string.IsNullOrWhiteSpace(AppVeyor.Instance.RepositoryTagName); - - tagName = AppVeyor.Instance.RepositoryTagName; - } - else if (IsRunningOnAzure) + if (IsRunningOnAzure) { RepositoryName = TeamServices.Instance.RepositoryUri; RepositoryBranch = TeamServices.Instance.SourceBranch; IsPullRequest = TeamServices.Instance.PullRequestId.HasValue; IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri); - - // TODO??? - IsTagged = false; - tagName = null; } IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, RepositoryName); IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, RepositoryBranch); + IsReleaseBranch = RepositoryBranch?.StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase) == + true; - IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); - IsMyGetRelease = !IsTagged && IsReleasable; + IsMyGetRelease = IsReleasable; IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; // VERSION Version = b.NukeArgForceNugetVersion ?? GetVersion(); - if (IsRunningOnAppVeyor) - { - string tagVersion = null; - if (IsTagged) - { - var tag = tagName; - var nugetReleasePrefix = "nuget-release-"; - IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix); - if (IsNuGetRelease) - tagVersion = tag.Substring(nugetReleasePrefix.Length); - } - - if (tagVersion != null) - { - Version = tagVersion; - } - else - { - // Use AssemblyVersion with Build as version - Version += "-build" + Environment.GetEnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta"; - } - } - else if (IsRunningOnAzure) + if (IsRunningOnAzure) { if (!IsNuGetRelease) { diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs index 7f26490493..1ba72494d7 100644 --- a/nukebuild/Shims.cs +++ b/nukebuild/Shims.cs @@ -40,7 +40,6 @@ public partial class Build foreach (var path in paths) { - if (Directory.Exists(path)) { var dirInfo = new DirectoryInfo(path); From 076b811f2aa5ab0a3805f47aa2570d1d7850e2fa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 2 Dec 2018 10:27:25 +0000 Subject: [PATCH 067/207] enable drop shadow on win32 popups. --- .../Interop/UnmanagedMethods.cs | 27 +++++++++++++++++-- src/Windows/Avalonia.Win32/PopupImpl.cs | 10 ++++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 60b56ea580..7d6e8fc8ce 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -909,8 +909,17 @@ namespace Avalonia.Win32.Interop public enum ClassLongIndex : int { - GCL_HCURSOR = -12, - GCL_HICON = -14 + GCLP_MENUNAME = -8, + GCLP_HBRBACKGROUND = -10, + GCLP_HCURSOR = -12, + GCLP_HICON = -14, + GCLP_HMODULE = -16, + GCL_CBWNDEXTRA = -18, + GCL_CBCLSEXTRA = -20, + GCLP_WNDPROC = -24, + GCL_STYLE = -26, + GCLP_HICONSM = -34, + GCW_ATOM = -32 } [DllImport("user32.dll", EntryPoint = "SetClassLongPtr")] @@ -929,6 +938,20 @@ namespace Avalonia.Win32.Interop return SetClassLong64(hWnd, nIndex, dwNewLong); } + public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) + { + if (IntPtr.Size > 4) + return GetClassLongPtr64(hWnd, nIndex); + else + return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); + } + + [DllImport("user32.dll", EntryPoint = "GetClassLong")] + public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] + public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); + [DllImport("user32.dll", EntryPoint = "SetCursor")] internal static extern IntPtr SetCursor(IntPtr hCursor); diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 7849bd6a9d..39f1a95466 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW | UnmanagedMethods.WindowStyles.WS_EX_TOPMOST; - return UnmanagedMethods.CreateWindowEx( + var result = UnmanagedMethods.CreateWindowEx( (int)exStyle, atom, null, @@ -37,6 +37,14 @@ namespace Avalonia.Win32 IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + var classes = (int)UnmanagedMethods.GetClassLongPtr(result, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE); + + classes |= (int)UnmanagedMethods.ClassStyles.CS_DROPSHADOW; + + UnmanagedMethods.SetClassLong(result, UnmanagedMethods.ClassLongIndex.GCL_STYLE, new IntPtr(classes)); + + return result; } protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0b9d16daa0..56bb7347ec 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -384,7 +384,7 @@ namespace Avalonia.Win32 public void SetCursor(IPlatformHandle cursor) { var hCursor = cursor?.Handle ?? DefaultCursor; - UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, hCursor); + UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCLP_HCURSOR, hCursor); if (_owner.IsPointerOver) UnmanagedMethods.SetCursor(hCursor); From 9bd4f35dad3ed4499f77279197e9b94f6b24bb86 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 2 Dec 2018 15:12:21 +0300 Subject: [PATCH 068/207] Removed MsBuildSettingsWithRestore hackery --- nukebuild/Build.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 63e889ae30..1014aa1b47 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -68,23 +68,14 @@ partial class Build : NukeBuild EnsureCleanDirectory(Parameters.TestResultsRoot); }); - [Serializable] - class MsBuildSettingsWithRestore : MSBuildSettings - { - protected override Arguments ConfigureArguments(Arguments arguments) - { - arguments.Add("/restore"); - return base.ConfigureArguments(arguments); - } - } - Target Compile => _ => _ .DependsOn(Clean) .Executes(() => { if (Parameters.IsRunningOnWindows) - MSBuild(Parameters.MSBuildSolution, c => new MsBuildSettingsWithRestore() + MSBuild(Parameters.MSBuildSolution, c => c + .SetArgumentConfigurator(a => a.Add("/r")) .SetConfiguration(Parameters.Configuration) .SetVerbosity(MSBuildVerbosity.Minimal) .AddProperty("PackageVersion", Parameters.Version) From f678b4c31d692f9aa9bc4a987e312ac4c07a4ce2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 4 Dec 2018 10:25:23 +0300 Subject: [PATCH 069/207] Removed appveyor badge --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 9d113cf2ef..9280125323 100644 --- a/readme.md +++ b/readme.md @@ -2,9 +2,9 @@ # Avalonia -| Gitter Chat | Build Status (Win, Linux, OSX) | Appveyor Build Status | Open Collective | -|---|---|---|---| -| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | +| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | +|---|---|---| +| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | ## About From dc655eee0a228ed40c7683639b6494248c46b29d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 4 Dec 2018 15:55:40 +0100 Subject: [PATCH 070/207] Tries to find the nearest match if a weight or slant combination can't be found --- .../Avalonia.Skia/SKTypefaceCollection.cs | 81 ++++++++++++++----- .../SKTypefaceCollectionCache.cs | 3 + 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs index 687fe7feca..96b1649697 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs @@ -1,72 +1,115 @@ +// 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.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + using Avalonia.Media; + using SkiaSharp; namespace Avalonia.Skia -{ +{ internal class SKTypefaceCollection { - private readonly ConcurrentDictionary _cachedTypefaces = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary> _fontFamilies = + new ConcurrentDictionary>(); public void AddTypeFace(SKTypeface typeface) { - var key = new FontKey(typeface.FamilyName, (SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant); + var key = new FontKey((SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant); - _cachedTypefaces.TryAdd(key, typeface); + if (!_fontFamilies.TryGetValue(typeface.FamilyName, out var fontFamily)) + { + fontFamily = new ConcurrentDictionary(); + + _fontFamilies.TryAdd(typeface.FamilyName, fontFamily); + } + + fontFamily.TryAdd(key, typeface); } public SKTypeface GetTypeFace(Typeface typeface) { - SKFontStyleSlant skStyle = SKFontStyleSlant.Upright; + var styleSlant = SKFontStyleSlant.Upright; switch (typeface.Style) { case FontStyle.Italic: - skStyle = SKFontStyleSlant.Italic; + styleSlant = SKFontStyleSlant.Italic; break; case FontStyle.Oblique: - skStyle = SKFontStyleSlant.Oblique; + styleSlant = SKFontStyleSlant.Oblique; break; } - var key = new FontKey(typeface.FontFamily.Name, (SKFontStyleWeight)typeface.Weight, skStyle); + if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily)) + { + return TypefaceCache.Default; + } + + var weight = (SKFontStyleWeight)typeface.Weight; + + var key = new FontKey(weight, styleSlant); + + return fontFamily.GetOrAdd(key, GetFallback(fontFamily, key)); + } + + private static SKTypeface GetFallback(IDictionary fontFamily, FontKey key) + { + var keys = fontFamily.Keys.Where( + x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Slant == key.Slant).ToArray(); + + if (!keys.Any()) + { + keys = fontFamily.Keys.Where( + x => x.Weight == key.Weight && (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray(); + + if (!keys.Any()) + { + keys = fontFamily.Keys.Where( + x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && + (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray(); + } + } + + key = keys.FirstOrDefault(); + + fontFamily.TryGetValue(key, out var typeface); - return _cachedTypefaces.TryGetValue(key, out var skTypeface) ? skTypeface : TypefaceCache.Default; + return typeface; } private struct FontKey { - public readonly string Name; public readonly SKFontStyleSlant Slant; public readonly SKFontStyleWeight Weight; - public FontKey(string name, SKFontStyleWeight weight, SKFontStyleSlant slant) + public FontKey(SKFontStyleWeight weight, SKFontStyleSlant slant) { - Name = name; Slant = slant; Weight = weight; } public override int GetHashCode() { - int hash = 17; - hash = hash * 31 + Name.GetHashCode(); - hash = hash * 31 + (int)Slant; - hash = hash * 31 + (int)Weight; + var hash = 17; + hash = (hash * 31) + (int)Slant; + hash = (hash * 31) + (int)Weight; return hash; } public override bool Equals(object other) { - return other is FontKey ? Equals((FontKey)other) : false; + return other is FontKey key && this.Equals(key); } private bool Equals(FontKey other) { - return Name == other.Name && Slant == other.Slant && + return Slant == other.Slant && Weight == other.Weight; } } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index b9a8c9b98b..ab8ee85a54 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs @@ -1,3 +1,6 @@ +// 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.Collections.Concurrent; using Avalonia.Media; using Avalonia.Media.Fonts; From d3329e19d89f004351eb4ea060c01e9a21d827a0 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 5 Dec 2018 20:50:10 +0800 Subject: [PATCH 071/207] Simplify iteration number calculation. --- src/Avalonia.Animation/AnimationInstance`1.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 8184e68d42..52cb9e72c0 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -16,7 +16,7 @@ namespace Avalonia.Animation private T _lastInterpValue; private T _firstKFValue; private long _repeatCount; - private double _currentIteration; + private long _currentIteration; private bool _isLooping; private bool _gotFirstKFValue; private bool _iterationDelay; @@ -72,7 +72,7 @@ namespace Avalonia.Animation _onCompleteAction = OnComplete; _interpolator = Interpolator; _baseClock = baseClock; - } + } protected override void Unsubscribed() { @@ -147,45 +147,51 @@ namespace Avalonia.Animation { _currentIteration = 1; } + // time is currently the second to nth iteration. else if (time > iterationEndpoint) { - //Subtract first iteration to properly get the subsequent iteration time + // Subtract first iteration to properly get the subsequent iteration time. iterationTime -= iterationEndpoint; + // Ignore delays on subsequent iterations if it's not the initial + // iteration unless _iterationDelay is enabled. if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) { delayEndpoint = TimeSpan.Zero; iterationEndpoint = _duration; } - //Calculate the current iteration number - _currentIteration = Math.Min(_repeatCount,(int)Math.Floor((double)((double)iterationTime.Ticks / iterationEndpoint.Ticks)) + 2); + // Calculate the current iteration number + _currentIteration = (iterationTime.Ticks / iterationEndpoint.Ticks) + 2; } else { return; } - // Determine if the current iteration should have its normalized time inverted. + // Determine if the current iteration should have its normalized time reversed. bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : _animationDirection == PlaybackDirection.Reverse ? true : false; - + if (!_isLooping) { - var totalTime = _iterationDelay ? _repeatCount * ( _duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks; + var totalTime = _iterationDelay ? _repeatCount * (_duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks; + + // Clamp value when animations ends. if (time.Ticks >= totalTime) { var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0); _lastInterpValue = _interpolator(easedTime, _neutralValue); - + DoComplete(); return; } } - iterationTime = TimeSpan.FromTicks((long)(iterationTime.Ticks % iterationEndpoint.Ticks)); - + + iterationTime = TimeSpan.FromTicks(iterationTime.Ticks % iterationEndpoint.Ticks); + if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint) { DoDelay(); @@ -199,6 +205,7 @@ namespace Avalonia.Animation // Normalize time var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks; + // Check if normalized time needs to be reversed. if (isCurIterReverse) interpVal = 1 - interpVal; From 52a9e72405f9c30ec597af08b876b2e524ddc49a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 00:10:29 +0800 Subject: [PATCH 072/207] Simplify the animations iteration algorithm --- src/Avalonia.Animation/Animation.cs | 11 +- src/Avalonia.Animation/AnimationInstance`1.cs | 114 +++++++----------- 2 files changed, 44 insertions(+), 81 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index d7efc69e10..8b5e760455 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -54,17 +54,12 @@ namespace Avalonia.Animation /// Describes a delay to be added before the animation starts, and optionally between /// repeats of the animation if is set. /// - public TimeSpan Delay { get; set; } + public TimeSpan Delay { get; set; } = TimeSpan.Zero; /// - /// Gets or sets a value indicating whether will be applied between - /// iterations of the animation. + /// Gets or sets the amount of delay time between iterations. /// - /// - /// If this property is not set, then will only be applied to the first - /// iteration of the animation. - /// - public bool DelayBetweenIterations { get; set; } + public TimeSpan DelayBetweenIterations { get; set; } = TimeSpan.Zero; private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 52cb9e72c0..69b99f7888 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -15,18 +15,18 @@ namespace Avalonia.Animation { private T _lastInterpValue; private T _firstKFValue; - private long _repeatCount; + private float _iterationCount; private long _currentIteration; private bool _isLooping; private bool _gotFirstKFValue; - private bool _iterationDelay; private FillMode _fillMode; private PlaybackDirection _animationDirection; private Animator _parent; private Animatable _targetControl; private T _neutralValue; private double _speedRatio; - private TimeSpan _delay; + private TimeSpan _initialDelay; + private TimeSpan _iterationDelay; private TimeSpan _duration; private Easings.Easing _easeFunc; private Action _onCompleteAction; @@ -50,20 +50,20 @@ namespace Avalonia.Animation _speedRatio = animation.SpeedRatio; - _delay = animation.Delay; + _initialDelay = animation.Delay; _duration = animation.Duration; _iterationDelay = animation.DelayBetweenIterations; switch (animation.RepeatCount.RepeatType) { case RepeatType.None: - _repeatCount = 1; + _iterationCount = 1; break; case RepeatType.Loop: - _isLooping = true; + _iterationCount = float.PositiveInfinity; break; case RepeatType.Repeat: - _repeatCount = (long)animation.RepeatCount.Value; + _iterationCount = animation.RepeatCount.Value; break; } @@ -76,7 +76,7 @@ namespace Avalonia.Animation protected override void Unsubscribed() { - //Animation may have been stopped before it has finished + // Animation may have been stopped before it has finished. ApplyFinalFill(); _timerSubscription?.Dispose(); @@ -138,83 +138,51 @@ namespace Avalonia.Animation private void InternalStep(TimeSpan time) { DoPlayStates(); - var delayEndpoint = _delay; - var iterationEndpoint = delayEndpoint + _duration; - var iterationTime = time; - //determine if time is currently in the first iteration. - if (time >= TimeSpan.Zero & time <= iterationEndpoint) - { - _currentIteration = 1; - } - // time is currently the second to nth iteration. - else if (time > iterationEndpoint) - { - // Subtract first iteration to properly get the subsequent iteration time. - iterationTime -= iterationEndpoint; - - // Ignore delays on subsequent iterations if it's not the initial - // iteration unless _iterationDelay is enabled. - if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) - { - delayEndpoint = TimeSpan.Zero; - iterationEndpoint = _duration; - } + var indexTime = time.Ticks; + var iterDuration = _duration.Ticks * _speedRatio; + var iterDelay = _iterationDelay.Ticks * _speedRatio; + var initDelay = _initialDelay.Ticks * _speedRatio; - // Calculate the current iteration number - _currentIteration = (iterationTime.Ticks / iterationEndpoint.Ticks) + 2; - } - else + if (indexTime > 0 & indexTime <= initDelay) { - return; + DoDelay(); } - - // Determine if the current iteration should have its normalized time reversed. - bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : - _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : - _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : - _animationDirection == PlaybackDirection.Reverse ? true : false; - - if (!_isLooping) + else { - var totalTime = _iterationDelay ? _repeatCount * (_duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks; - - // Clamp value when animations ends. - if (time.Ticks >= totalTime) - { - var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0); - _lastInterpValue = _interpolator(easedTime, _neutralValue); + var fullIterationTime = iterDuration + iterDelay; + var opsTime = indexTime - initDelay; + var playbackTime = opsTime % fullIterationTime; + _currentIteration = (long)Math.Floor(opsTime / fullIterationTime); + + if ((_currentIteration + 1) > _iterationCount) DoComplete(); - return; - } - } - iterationTime = TimeSpan.FromTicks(iterationTime.Ticks % iterationEndpoint.Ticks); - - if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint) - { - DoDelay(); - } - else - { - // Offset the delay time - iterationTime -= delayEndpoint; - iterationEndpoint -= delayEndpoint; + if (playbackTime <= iterDuration) + { + var normalizedTime = playbackTime / iterDuration; - // Normalize time - var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks; + bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : + _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : + _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : + _animationDirection == PlaybackDirection.Reverse ? true : false; - // Check if normalized time needs to be reversed. - if (isCurIterReverse) - interpVal = 1 - interpVal; + // Check if normalized time needs to be reversed. + if (isCurIterReverse) + normalizedTime = 1 - normalizedTime; - // Ease and interpolate - var easedTime = _easeFunc.Ease(interpVal); - _lastInterpValue = _interpolator(easedTime, _neutralValue); + // Ease and interpolate + var easedTime = _easeFunc.Ease(normalizedTime); + _lastInterpValue = _interpolator(easedTime, _neutralValue); - PublishNext(_lastInterpValue); + PublishNext(_lastInterpValue); + } + else if (playbackTime > iterDuration & playbackTime <= fullIterationTime & iterDelay > 0) + { + DoDelay(); + } } } } -} +} \ No newline at end of file From b489e3a935447fd54ee903bf499a1ee2d85145bc Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 01:19:21 +0800 Subject: [PATCH 073/207] Additional fixes for AnimationInstance. --- src/Avalonia.Animation/AnimationInstance`1.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 69b99f7888..b27be609a5 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -15,9 +15,8 @@ namespace Avalonia.Animation { private T _lastInterpValue; private T _firstKFValue; - private float _iterationCount; - private long _currentIteration; - private bool _isLooping; + private ulong? _iterationCount; + private ulong _currentIteration; private bool _gotFirstKFValue; private FillMode _fillMode; private PlaybackDirection _animationDirection; @@ -58,10 +57,7 @@ namespace Avalonia.Animation { case RepeatType.None: _iterationCount = 1; - break; - case RepeatType.Loop: - _iterationCount = float.PositiveInfinity; - break; + break; case RepeatType.Repeat: _iterationCount = animation.RepeatCount.Value; break; @@ -138,7 +134,8 @@ namespace Avalonia.Animation private void InternalStep(TimeSpan time) { DoPlayStates(); - + + // Scale timebases according to speedratio. var indexTime = time.Ticks; var iterDuration = _duration.Ticks * _speedRatio; var iterDelay = _iterationDelay.Ticks * _speedRatio; @@ -150,25 +147,27 @@ namespace Avalonia.Animation } else { - var fullIterationTime = iterDuration + iterDelay; + // Calculate timebases. + var iterationTime = iterDuration + iterDelay; var opsTime = indexTime - initDelay; - var playbackTime = opsTime % fullIterationTime; + var playbackTime = opsTime % iterationTime; - _currentIteration = (long)Math.Floor(opsTime / fullIterationTime); + _currentIteration = (ulong)(opsTime / iterationTime); + // Stop animation when the current iteration is beyond the iteration count. if ((_currentIteration + 1) > _iterationCount) DoComplete(); if (playbackTime <= iterDuration) { + // Normalize time for interpolation. var normalizedTime = playbackTime / iterDuration; + // Check if normalized time needs to be reversed. bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : _animationDirection == PlaybackDirection.Reverse ? true : false; - - // Check if normalized time needs to be reversed. if (isCurIterReverse) normalizedTime = 1 - normalizedTime; @@ -178,7 +177,11 @@ namespace Avalonia.Animation PublishNext(_lastInterpValue); } - else if (playbackTime > iterDuration & playbackTime <= fullIterationTime & iterDelay > 0) + else if (playbackTime > iterDuration & + playbackTime <= iterationTime & + iterDelay > 0 & + // The last iteration's trailing delay should be skipped. + (_currentIteration + 1) < _iterationCount) { DoDelay(); } From a941eaeb4e4b0c0b8d3c99d50991bad972e46ece Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 01:26:31 +0800 Subject: [PATCH 074/207] Make SpeedRatio bindable. --- src/Avalonia.Animation/Animation.cs | 33 +++++-- src/Avalonia.Animation/AnimationInstance`1.cs | 18 ++-- src/Avalonia.Animation/KeyFrames.cs | 33 +++++++ src/Avalonia.Visuals/Animation/CrossFade.cs | 40 +++++---- src/Avalonia.Visuals/Animation/PageSlide.cs | 86 ++++++++++--------- 5 files changed, 137 insertions(+), 73 deletions(-) create mode 100644 src/Avalonia.Animation/KeyFrames.cs diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 8b5e760455..c79ee7b8a8 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -9,14 +9,21 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Animation.Easings; using Avalonia.Collections; +using Avalonia.Metadata; namespace Avalonia.Animation { /// /// Tracks the progress of an animation. /// - public class Animation : AvaloniaList, IAnimation + public class Animation : AvaloniaObject, IAnimation { + /// + /// Gets the children of the . + /// + [Content] + public KeyFrames Children { get; } = new KeyFrames(); + /// /// Gets or sets the active time of this animation. /// @@ -42,10 +49,6 @@ namespace Avalonia.Animation /// public Easing Easing { get; set; } = new LinearEasing(); - /// - /// Gets or sets the speed multiple for this animation. - /// - public double SpeedRatio { get; set; } = 1d; /// /// Gets or sets the delay time for this animation. @@ -61,6 +64,24 @@ namespace Avalonia.Animation /// public TimeSpan DelayBetweenIterations { get; set; } = TimeSpan.Zero; + public static readonly DirectProperty SpeedRatioProperty = + AvaloniaProperty.RegisterDirect( + nameof(_speedRatio), + o => o._speedRatio, + (o, v) => o._speedRatio = v, + 1d); + + private double _speedRatio = 1d; + + /// + /// Gets or sets the speed multiple for this animation. + /// + public double SpeedRatio + { + get { return _speedRatio; } + set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); } + } + private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) @@ -90,7 +111,7 @@ namespace Avalonia.Animation var animatorKeyFrames = new List(); var subscriptions = new List(); - foreach (var keyframe in this) + foreach (var keyframe in Children) { foreach (var setter in keyframe) { diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index b27be609a5..b793dde8f9 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -30,7 +30,7 @@ namespace Avalonia.Animation private Easings.Easing _easeFunc; private Action _onCompleteAction; private Func _interpolator; - private IDisposable _timerSubscription; + private IDisposable _timerSub, _speedRatioSub; private readonly IClock _baseClock; private IClock _clock; @@ -47,7 +47,8 @@ namespace Avalonia.Animation _targetControl = control; _neutralValue = (T)_targetControl.GetValue(_parent.Property); - _speedRatio = animation.SpeedRatio; + _speedRatioSub = animation.GetObservable(Animation.SpeedRatioProperty) + .Subscribe(p => _speedRatio = p); _initialDelay = animation.Delay; _duration = animation.Duration; @@ -57,7 +58,7 @@ namespace Avalonia.Animation { case RepeatType.None: _iterationCount = 1; - break; + break; case RepeatType.Repeat: _iterationCount = animation.RepeatCount.Value; break; @@ -75,14 +76,15 @@ namespace Avalonia.Animation // Animation may have been stopped before it has finished. ApplyFinalFill(); - _timerSubscription?.Dispose(); + _timerSub?.Dispose(); + _speedRatioSub?.Dispose(); _clock.PlayState = PlayState.Stop; } protected override void Subscribed() { _clock = new Clock(_baseClock); - _timerSubscription = _clock.Subscribe(Step); + _timerSub = _clock.Subscribe(Step); } public void Step(TimeSpan frameTick) @@ -134,7 +136,7 @@ namespace Avalonia.Animation private void InternalStep(TimeSpan time) { DoPlayStates(); - + // Scale timebases according to speedratio. var indexTime = time.Ticks; var iterDuration = _duration.Ticks * _speedRatio; @@ -153,7 +155,7 @@ namespace Avalonia.Animation var playbackTime = opsTime % iterationTime; _currentIteration = (ulong)(opsTime / iterationTime); - + // Stop animation when the current iteration is beyond the iteration count. if ((_currentIteration + 1) > _iterationCount) DoComplete(); @@ -178,7 +180,7 @@ namespace Avalonia.Animation PublishNext(_lastInterpValue); } else if (playbackTime > iterDuration & - playbackTime <= iterationTime & + playbackTime <= iterationTime & iterDelay > 0 & // The last iteration's trailing delay should be skipped. (_currentIteration + 1) < _iterationCount) diff --git a/src/Avalonia.Animation/KeyFrames.cs b/src/Avalonia.Animation/KeyFrames.cs new file mode 100644 index 0000000000..9e3b1d9f51 --- /dev/null +++ b/src/Avalonia.Animation/KeyFrames.cs @@ -0,0 +1,33 @@ +// 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.Collections.Generic; +using Avalonia.Collections; + +namespace Avalonia.Animation +{ + /// + /// A collection of s. + /// + public class KeyFrames : AvaloniaList + { + /// + /// Initializes a new instance of the class. + /// + public KeyFrames() + { + ResetBehavior = ResetBehavior.Remove; + } + + /// + /// Initializes a new instance of the class. + /// + /// The initial items in the collection. + public KeyFrames(IEnumerable items) + : base(items) + { + ResetBehavior = ResetBehavior.Remove; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index d5ddf1c7f5..b07123e050 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -21,7 +21,7 @@ namespace Avalonia.Animation /// Initializes a new instance of the class. /// public CrossFade() - :this(TimeSpan.Zero) + : this(TimeSpan.Zero) { } @@ -33,30 +33,36 @@ namespace Avalonia.Animation { _fadeOutAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame + ( + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + ) { - Property = Visual.OpacityProperty, - Value = 0d + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) } }; _fadeInAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame + ( + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + ) { - Property = Visual.OpacityProperty, - Value = 0d + Cue = new Cue(0d) } - ) - { - Cue = new Cue(0d) } }; _fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration; diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index c5d43068c1..c1848d7daa 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -74,34 +74,34 @@ namespace Avalonia.Animation var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty; - - // TODO: Implement relevant transition logic here (or discard this class) - // in favor of XAML based transition for pages if (from != null) { var animation = new Animation { - new KeyFrame - ( - new Setter - { - Property = translateProperty, - Value = 0d - } - ) + Children = { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = 0d + } + ) { - Property = translateProperty, - Value = forward ? -distance : distance + Cue = new Cue(0d) + }, + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = forward ? -distance : distance + } + ) + { + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) } }; animation.Duration = Duration; @@ -113,29 +113,31 @@ namespace Avalonia.Animation to.IsVisible = true; var animation = new Animation { - - new KeyFrame - ( - new Setter - { - Property = translateProperty, - Value = forward ? distance : -distance - } - ) + Children = { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = forward ? distance : -distance + } + ) { - Property = translateProperty, - Value = 0d - } - ) - { - Cue = new Cue(1d) - }, + Cue = new Cue(0d) + }, + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = 0d + } + ) + { + Cue = new Cue(1d) + }, + } }; animation.Duration = Duration; tasks.Add(animation.RunAsync(to)); From 7e023ee5af1e531373f3615cae7a15582c120822 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 01:45:55 +0800 Subject: [PATCH 075/207] [Breaking Change] Replace RepeatCount with IterationCount. --- samples/RenderDemo/Pages/AnimationsPage.xaml | 4 +- samples/RenderDemo/Pages/ClippingPage.xaml | 2 +- src/Avalonia.Animation/Animation.cs | 4 +- src/Avalonia.Animation/AnimationInstance`1.cs | 13 +- src/Avalonia.Animation/IterationCount.cs | 176 ++++++++++++++++ ...rter.cs => IterationCountTypeConverter.cs} | 4 +- src/Avalonia.Animation/RepeatCount.cs | 199 ------------------ src/Avalonia.Themes.Default/ProgressBar.xaml | 4 +- 8 files changed, 188 insertions(+), 218 deletions(-) create mode 100644 src/Avalonia.Animation/IterationCount.cs rename src/Avalonia.Animation/{RepeatCountTypeConverter.cs => IterationCountTypeConverter.cs} (83%) delete mode 100644 src/Avalonia.Animation/RepeatCount.cs diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 1646708797..9f8e7b5fa5 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -43,7 +43,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + @@ -121,6 +149,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + From 87e8bca8793c40b5b320f611213122ee9d343a4a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 01:03:12 +0800 Subject: [PATCH 104/207] Minor fixes. --- samples/RenderDemo/Pages/AnimationsPage.xaml | 2 +- .../Animation/Animators/SolidColorBrushAnimator.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index e6e0eff941..53f8e855e0 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -149,7 +149,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 43cc9d5476..518d3d2ca7 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -7,8 +7,7 @@ using Avalonia.Media.Immutable; namespace Avalonia.Animation.Animators { /// - /// Animator that interpolates through - /// gamma sRGB color space for better visual result. + /// Animator that handles . /// public class SolidColorBrushAnimator : Animator { From 13eb3655714558ed3d66f34b83e1d6ee136b6ccf Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 11:54:38 +0800 Subject: [PATCH 105/207] Fix SCBA. --- .../Animators/SolidColorBrushAnimator.cs | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 518d3d2ca7..c3de578959 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -13,53 +13,62 @@ namespace Avalonia.Animation.Animators { ColorAnimator colorAnimator; + void InitializeColorAnimator() + { + colorAnimator = new ColorAnimator(); + + foreach (AnimatorKeyFrame keyframe in this) + { + colorAnimator.Add(keyframe); + } + + colorAnimator.Property = SolidColorBrush.ColorProperty; + } + public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) { var ctrl = (Visual)control; foreach (var keyframe in this) { - // Return if the keyframe value is not a SolidColorBrush if (keyframe.Value as ISolidColorBrush == null) - { return Disposable.Empty; - } - // Preprocess values to Color if the xaml parser converts them to ISCB + // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush)) { keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color; } } - // Make sure that the target property has SCB instead of the immutable one nor null. + // Add SCB if the target prop is empty. + if (control.GetValue(Property) == null) + control.SetValue(Property, new SolidColorBrush(Colors.Transparent)); var targetVal = control.GetValue(Property); - SolidColorBrush targetSCB = null; - - if (targetVal == null) - targetSCB = new SolidColorBrush(Colors.Transparent); - else if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) - targetSCB = new SolidColorBrush(((ISolidColorBrush)targetVal).Color); - else - return Disposable.Empty; - - control.SetValue(Property, targetSCB); - - if (colorAnimator == null) + // Continue if target prop is not empty & is a SolidColorBrush derivative. + if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) { - colorAnimator = new ColorAnimator(); + if (colorAnimator == null) + InitializeColorAnimator(); + + SolidColorBrush finalTarget; - foreach (AnimatorKeyFrame keyframe in this) + // If it's ISCB, change it back to SCB. + if (targetVal.GetType() == typeof(ImmutableSolidColorBrush)) { - colorAnimator.Add(keyframe); + var col = (ImmutableSolidColorBrush)targetVal; + targetVal = new SolidColorBrush(col.Color); + control.SetValue(Property, targetVal); } - colorAnimator.Property = SolidColorBrush.ColorProperty; + finalTarget = targetVal as SolidColorBrush; + + return colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); } - return colorAnimator.Apply(animation, targetSCB, clock ?? control.Clock, match, onComplete); + return Disposable.Empty; } public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null; From cfe26a787df7f2869643e130f16a7275f43f7f01 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 7 Dec 2018 22:25:52 +0300 Subject: [PATCH 106/207] Enable GPU acceleration on Linux by default --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 8444e509fd..965973d3cb 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -50,7 +50,7 @@ namespace Avalonia.Gtk3 { Resolver.Custom = options.CustomResolver; UseDeferredRendering = EnvOption("USE_DEFERRED_RENDERING", true, options.UseDeferredRendering); - var useGpu = EnvOption("USE_GPU", false, options.UseGpuAcceleration); + var useGpu = EnvOption("USE_GPU", true, options.UseGpuAcceleration); if (!s_gtkInitialized) { try From ea7dc760cc22af125a8d9fdfca46c36dfb0d7a5f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 10 Dec 2018 10:18:52 +0300 Subject: [PATCH 107/207] Updated SkiaSharp to 1.68.0 --- build/Magick.NET-Q16-AnyCPU.props | 2 +- build/SkiaSharp.props | 4 ++-- nukebuild/Build.cs | 5 ++-- .../Avalonia.Skia/FramebufferRenderTarget.cs | 3 ++- src/Skia/Avalonia.Skia/GlRenderTarget.cs | 23 ++++++++----------- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 +- .../Controls/BorderTests.cs | 12 +++++----- tests/Avalonia.RenderTests/TestBase.cs | 1 + tests/Avalonia.RenderTests/TestSkip.cs | 21 +++++++++++++++++ 9 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 tests/Avalonia.RenderTests/TestSkip.cs diff --git a/build/Magick.NET-Q16-AnyCPU.props b/build/Magick.NET-Q16-AnyCPU.props index 4e600a1c11..21d9cdcb1f 100644 --- a/build/Magick.NET-Q16-AnyCPU.props +++ b/build/Magick.NET-Q16-AnyCPU.props @@ -1,5 +1,5 @@ - + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 35c979a95e..a43c99e978 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,6 +1,6 @@  - - + + diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 61944c4dbc..1e1becb1c4 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -138,12 +138,13 @@ partial class Build : NukeBuild }); Target RunRenderTests => _ => _ - .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .OnlyWhen(() => !Parameters.SkipTests) .DependsOn(Compile) .Executes(() => { RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true); - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); }); Target RunDesignerTests => _ => _ diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 5efbc0861e..02b6188fb6 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -42,7 +42,8 @@ namespace Avalonia.Skia { var framebuffer = _platformSurface.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, - framebuffer.Format.ToSkColorType(), SKAlphaType.Premul); + framebuffer.Format.ToSkColorType(), + framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); CreateSurface(framebufferImageInfo, framebuffer); diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index a6269473a6..3360255dae 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -31,24 +31,18 @@ namespace Avalonia.Skia var size = session.Size; var scaling = session.Scaling; - GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc - { - Width = size.Width, - Height = size.Height, - SampleCount = disp.SampleCount, - StencilBits = disp.StencilSize, - Config = GRPixelConfig.Rgba8888, - Origin=GRSurfaceOrigin.BottomLeft, - RenderTargetHandle = new IntPtr(fb) - }; - - gl.Viewport(0, 0, desc.Width, desc.Height); + gl.Viewport(0, 0, size.Width, size.Height); gl.ClearStencil(0); gl.ClearColor(0, 0, 0, 0); gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - var surface = SKSurface.Create(_grContext, desc); - + GRBackendRenderTarget renderTarget = + new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize, + new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat())); + var surface = SKSurface.Create(_grContext, renderTarget, + GRSurfaceOrigin.BottomLeft, + GRPixelConfig.Rgba8888.ToColorType()); + var nfo = new DrawingContextImpl.CreateInfo { GrContext = _grContext, @@ -62,6 +56,7 @@ namespace Avalonia.Skia { surface.Canvas.Flush(); surface.Dispose(); + renderTarget.Dispose(); session.Dispose(); })); } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 36ccd7930a..eb3a11b2a1 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia var nfo = new SKImageInfo(size.Width, size.Height, colorType, SKAlphaType.Premul); var blob = runtimePlatform.AllocBlob(nfo.BytesSize); - _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob); + _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, s_releaseDelegate, blob); } else { diff --git a/tests/Avalonia.RenderTests/Controls/BorderTests.cs b/tests/Avalonia.RenderTests/Controls/BorderTests.cs index c82d616094..4f4004e159 100644 --- a/tests/Avalonia.RenderTests/Controls/BorderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/BorderTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Horizontally() { Decorator target = new Decorator @@ -215,7 +215,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Vertically() { Decorator target = new Decorator @@ -296,7 +296,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Left_Aligns_Content() { Decorator target = new Decorator @@ -323,7 +323,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Right_Aligns_Content() { Decorator target = new Decorator @@ -350,7 +350,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Top_Aligns_Content() { Decorator target = new Decorator @@ -377,7 +377,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Bottom_Aligns_Content() { Decorator target = new Decorator diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 4c8abd85f7..2efd28c2d5 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -46,6 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests public TestBase(string outputPath) { + outputPath = outputPath.Replace('\\', Path.DirectorySeparatorChar); var testPath = GetTestsDirectory(); var testFiles = Path.Combine(testPath, "TestFiles"); #if AVALONIA_SKIA diff --git a/tests/Avalonia.RenderTests/TestSkip.cs b/tests/Avalonia.RenderTests/TestSkip.cs new file mode 100644 index 0000000000..75407332e3 --- /dev/null +++ b/tests/Avalonia.RenderTests/TestSkip.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests +#endif +{ + public class Win32Fact : FactAttribute + { + public Win32Fact(string message) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Skip = message; + } + } +} + From 739309c28f116f3f9ce56a70a884dd330c879393 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 15:58:53 +0800 Subject: [PATCH 108/207] Replace binary-search on keyframe selection with naive approach for now. --- .../Animators/Animator`1.cs | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs index e79aa128d6..e42489d6a6 100644 --- a/src/Avalonia.Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Animation/Animators/Animator`1.cs @@ -78,7 +78,7 @@ namespace Avalonia.Animation.Animators double t0 = firstKeyframe.Cue.CueValue; double t1 = lastKeyframe.Cue.CueValue; - double progress = (animationTime - t0) / (t1 - t0); + double progress = (animationTime - t0) / (t1 - t0); T oldValue, newValue; @@ -97,30 +97,11 @@ namespace Avalonia.Animation.Animators private int FindClosestBeforeKeyFrame(double time) { - int FindClosestBeforeKeyFrame(int startIndex, int length) - { - if (length == 0 || length == 1) - { - return startIndex; - } - - int middle = startIndex + (length / 2); + for (int i = 0; i < _convertedKeyframes.Count; i++) + if (_convertedKeyframes[i].Cue.CueValue > time) + return i - 1; - if (_convertedKeyframes[middle].Cue.CueValue < time) - { - return FindClosestBeforeKeyFrame(middle, length - middle); - } - else if (_convertedKeyframes[middle].Cue.CueValue > time) - { - return FindClosestBeforeKeyFrame(startIndex, middle - startIndex); - } - else - { - return middle; - } - } - - return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count); + throw new Exception("Index time is out of keyframe time range."); } /// @@ -139,13 +120,10 @@ namespace Avalonia.Animation.Animators } /// - /// Interpolates a value given the desired time. + /// Interpolates in-between two key values given the desired progress time. /// public abstract T Interpolate(double progress, T oldValue, T newValue); - /// - /// Verifies, converts and sorts keyframe values according to this class's target type. - /// private void VerifyConvertKeyFrames() { foreach (AnimatorKeyFrame keyframe in this) @@ -193,4 +171,4 @@ namespace Avalonia.Animation.Animators } } } -} \ No newline at end of file +} From 2fe9378ebc79b0f7f32a45bca84c15b514d54b3f Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 16:01:21 +0800 Subject: [PATCH 109/207] Adjust rainbow example --- samples/RenderDemo/Pages/AnimationsPage.xaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 53f8e855e0..b5a8ff6dea 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -106,19 +106,20 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 90100c2f2b..6741bdc7d9 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -23,7 +23,7 @@ Path="UseFloatingWatermark"/> + Converter="{x:Static StringConverters.IsNotNullOrEmpty}"/> @@ -36,7 +36,7 @@ + IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/> Date: Sat, 19 Jan 2019 02:45:59 +0100 Subject: [PATCH 206/207] Fix tests. --- .../Data/BindingTests_Converters.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index 123aadfda5..1ede4ee0b0 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -22,14 +22,14 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { - Converter = StringConverters.NullOrEmpty, + Converter = StringConverters.IsNullOrEmpty, }; var expressionObserver = (BindingExpression)target.Initiate( textBlock, TextBlock.TextProperty).Observable; - Assert.Same(StringConverters.NullOrEmpty, expressionObserver.Converter); + Assert.Same(StringConverters.IsNullOrEmpty, expressionObserver.Converter); } public class When_Binding_To_String @@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { - Converter = StringConverters.NotNullOrEmpty, + Converter = StringConverters.IsNotNullOrEmpty, StringFormat = "Hello {0}", }; From c112ce30c580db8fd03226c5adcb016766a981d5 Mon Sep 17 00:00:00 2001 From: Kermalis <29823718+Kermalis@users.noreply.github.com> Date: Fri, 18 Jan 2019 21:19:46 -0500 Subject: [PATCH 207/207] Update NumericUpDown Text when losing focus --- src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 1f8e991d2e..18cef7d64e 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -287,6 +287,13 @@ namespace Avalonia.Controls ValueProperty.Changed.Subscribe(OnValueChanged); } + /// + protected override void OnLostFocus(RoutedEventArgs e) + { + CommitInput(); + base.OnLostFocus(e); + } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) {