From 0c81cb24801b7c7761c0b00f0675c8ff08754822 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 9 Jun 2022 10:56:45 +0200 Subject: [PATCH 01/21] fix: DBus Null annotations --- Avalonia.sln | 1 + .../Avalonia.FreeDesktop.csproj | 4 ++ src/Avalonia.FreeDesktop/DBusCallQueue.cs | 20 +++----- src/Avalonia.FreeDesktop/DBusHelper.cs | 4 +- .../DBusIme/DBusTextInputMethodBase.cs | 46 ++++++++++--------- .../DBusIme/Fcitx/FcitxDBus.cs | 28 +++++------ .../DBusIme/Fcitx/FcitxICWrapper.cs | 31 +++++++------ .../DBusIme/Fcitx/FcitxX11TextInputMethod.cs | 38 ++++++++------- .../DBusIme/IBus/IBusDBus.cs | 38 +++++++-------- .../DBusIme/IBus/IBusX11TextInputMethod.cs | 30 ++++++++---- .../DBusIme/X11DBusImeHelper.cs | 2 +- src/Avalonia.FreeDesktop/DBusMenu.cs | 16 +++---- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 36 +++++++-------- src/Avalonia.FreeDesktop/DBusRequest.cs | 2 +- .../LinuxMountedVolumeInfoProvider.cs | 2 +- .../Avalonia.SourceGenerator.csproj | 1 + .../Output/DrmOutput.cs | 2 +- .../IsExternalInit.cs | 0 18 files changed, 161 insertions(+), 140 deletions(-) rename src/{Avalonia.SourceGenerator => Shared}/IsExternalInit.cs (100%) diff --git a/Avalonia.sln b/Avalonia.sln index 25c7daf080..8d2479a663 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -38,6 +38,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DEF5-D50F-4975-8B72-124C9EB54066}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs EndProjectSection diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index a5cb207223..3b1c6cc7b1 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -5,6 +5,10 @@ enable + + + + diff --git a/src/Avalonia.FreeDesktop/DBusCallQueue.cs b/src/Avalonia.FreeDesktop/DBusCallQueue.cs index 5cd748be02..e7c07dcbf9 100644 --- a/src/Avalonia.FreeDesktop/DBusCallQueue.cs +++ b/src/Avalonia.FreeDesktop/DBusCallQueue.cs @@ -8,10 +8,9 @@ namespace Avalonia.FreeDesktop { private readonly Func _errorHandler; - class Item + record Item(Func Callback) { - public Func Callback; - public Action OnFinish; + public Action? OnFinish; } private Queue _q = new Queue(); private bool _processing; @@ -23,19 +22,15 @@ namespace Avalonia.FreeDesktop public void Enqueue(Func cb) { - _q.Enqueue(new Item - { - Callback = cb - }); + _q.Enqueue(new Item(cb)); Process(); } public Task EnqueueAsync(Func cb) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _q.Enqueue(new Item + _q.Enqueue(new Item(cb) { - Callback = cb, OnFinish = e => { if (e == null) @@ -51,13 +46,12 @@ namespace Avalonia.FreeDesktop public Task EnqueueAsync(Func> cb) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _q.Enqueue(new Item - { - Callback = async () => + _q.Enqueue(new Item(async () => { var res = await cb(); tcs.TrySetResult(res); - }, + }) + { OnFinish = e => { if (e != null) diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index 7204e51dbd..9f9d75b411 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -17,7 +17,7 @@ namespace Avalonia.FreeDesktop private readonly object _lock = new(); private SynchronizationContext? _ctx; - public override void Post(SendOrPostCallback d, object state) + public override void Post(SendOrPostCallback d, object? state) { lock (_lock) { @@ -29,7 +29,7 @@ namespace Avalonia.FreeDesktop } } - public override void Send(SendOrPostCallback d, object state) + public override void Send(SendOrPostCallback d, object? state) { lock (_lock) { diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs index 864c579319..eef865d458 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Reflection; using System.Threading.Tasks; -using Avalonia.FreeDesktop.DBusIme.Fcitx; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Logging; @@ -26,7 +24,7 @@ namespace Avalonia.FreeDesktop.DBusIme return (im, im); } } - + internal abstract class DBusTextInputMethodBase : IX11InputMethodControl, ITextInputMethodImpl { private List _disposables = new List(); @@ -34,7 +32,7 @@ namespace Avalonia.FreeDesktop.DBusIme protected Connection Connection { get; } private readonly string[] _knownNames; private bool _connecting; - private string _currentName; + private string? _currentName; private DBusCallQueue _queue; private bool _controlActive, _windowActive; private bool? _imeActive; @@ -42,9 +40,9 @@ namespace Avalonia.FreeDesktop.DBusIme private PixelRect? _lastReportedRect; private double _scaling = 1; private PixelPoint _windowPosition; - + protected bool IsConnected => _currentName != null; - + public DBusTextInputMethodBase(Connection connection, params string[] knownNames) { _queue = new DBusCallQueue(QueueOnError); @@ -58,18 +56,18 @@ namespace Avalonia.FreeDesktop.DBusIme foreach (var name in _knownNames) _disposables.Add(await Connection.ResolveServiceOwnerAsync(name, OnNameChange)); } - + protected abstract Task Connect(string name); protected string GetAppName() => - Application.Current.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia"; - + Application.Current?.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia"; + private async void OnNameChange(ServiceOwnerChangedEventArgs args) { if (args.NewOwner != null && _currentName == null) { _onlineNamesQueue.Enqueue(args.ServiceName); - if(!_connecting) + if (!_connecting) { _connecting = true; try @@ -98,25 +96,25 @@ namespace Avalonia.FreeDesktop.DBusIme _connecting = false; } } - + } - + // IME has crashed if (args.NewOwner == null && args.ServiceName == _currentName) { _currentName = null; - foreach(var s in _disposables) + foreach (var s in _disposables) s.Dispose(); _disposables.Clear(); - + OnDisconnected(); Reset(); - + // Watch again Watch(); } } - + protected virtual Task Disconnect() { return Task.CompletedTask; @@ -124,7 +122,7 @@ namespace Avalonia.FreeDesktop.DBusIme protected virtual void OnDisconnected() { - + } protected virtual void Reset() @@ -149,10 +147,14 @@ namespace Avalonia.FreeDesktop.DBusIme OnDisconnected(); _currentName = null; } - + protected void Enqueue(Func cb) => _queue.Enqueue(cb); - protected void AddDisposable(IDisposable d) => _disposables.Add(d); + protected void AddDisposable(IDisposable? d) + { + if(d is { }) + _disposables.Add(d); + } public void Dispose() { @@ -198,7 +200,7 @@ namespace Avalonia.FreeDesktop.DBusIme UpdateActive(); } - void ITextInputMethodImpl.SetClient(ITextInputMethodClient client) + void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client) { _controlActive = client is { }; UpdateActive(); @@ -225,7 +227,7 @@ namespace Avalonia.FreeDesktop.DBusIme } } - private Action _onCommit; + private Action? _onCommit; event Action IX11InputMethodControl.Commit { add => _onCommit += value; @@ -234,7 +236,7 @@ namespace Avalonia.FreeDesktop.DBusIme protected void FireCommit(string s) => _onCommit?.Invoke(s); - private Action _onForward; + private Action? _onForward; event Action IX11InputMethodControl.ForwardKey { add => _onForward += value; diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs index 7ce2339763..06afacaa29 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs @@ -31,15 +31,15 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor); Task DestroyICAsync(); Task ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time); - Task WatchEnableIMAsync(Action handler, Action onError = null); - Task WatchCloseIMAsync(Action handler, Action onError = null); - Task WatchCommitStringAsync(Action handler, Action onError = null); - Task WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action onError = null); - Task WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action onError = null); - Task WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action onError = null); - Task WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action onError = null); - Task WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action onError = null); - Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action onError = null); + Task WatchEnableIMAsync(Action handler, Action? onError = null); + Task WatchCloseIMAsync(Action handler, Action? onError = null); + Task WatchCommitStringAsync(Action handler, Action? onError = null); + Task WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action? onError = null); + Task WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action? onError = null); + Task WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action? onError = null); + Task WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action? onError = null); + Task WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action? onError = null); + Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action? onError = null); } [DBusInterface("org.fcitx.Fcitx.InputContext1")] @@ -54,11 +54,11 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor); Task DestroyICAsync(); Task ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time); - Task WatchCommitStringAsync(Action handler, Action onError = null); - Task WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action onError = null); - Task WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action onError = null); - Task WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action onError = null); - Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action onError = null); + Task WatchCommitStringAsync(Action handler, Action? onError = null); + Task WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action? onError = null); + Task WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action? onError = null); + Task WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action? onError = null); + Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action? onError = null); } [DBusInterface("org.fcitx.Fcitx.InputMethod1")] diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs index a03ea213aa..6c503edb41 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs @@ -5,8 +5,8 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx { internal class FcitxICWrapper { - private readonly IFcitxInputContext1 _modern; - private readonly IFcitxInputContext _old; + private readonly IFcitxInputContext1? _modern; + private readonly IFcitxInputContext? _old; public FcitxICWrapper(IFcitxInputContext old) { @@ -18,34 +18,37 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx _modern = modern; } - public Task FocusInAsync() => _old?.FocusInAsync() ?? _modern.FocusInAsync(); + public Task FocusInAsync() => _old?.FocusInAsync() ?? _modern?.FocusInAsync() ?? Task.CompletedTask; - public Task FocusOutAsync() => _old?.FocusOutAsync() ?? _modern.FocusOutAsync(); + public Task FocusOutAsync() => _old?.FocusOutAsync() ?? _modern?.FocusOutAsync() ?? Task.CompletedTask; - public Task ResetAsync() => _old?.ResetAsync() ?? _modern.ResetAsync(); + public Task ResetAsync() => _old?.ResetAsync() ?? _modern?.ResetAsync() ?? Task.CompletedTask; public Task SetCursorRectAsync(int x, int y, int w, int h) => - _old?.SetCursorRectAsync(x, y, w, h) ?? _modern.SetCursorRectAsync(x, y, w, h); - public Task DestroyICAsync() => _old?.DestroyICAsync() ?? _modern.DestroyICAsync(); + _old?.SetCursorRectAsync(x, y, w, h) ?? _modern?.SetCursorRectAsync(x, y, w, h) ?? Task.CompletedTask; + public Task DestroyICAsync() => _old?.DestroyICAsync() ?? _modern?.DestroyICAsync() ?? Task.CompletedTask; public async Task ProcessKeyEventAsync(uint keyVal, uint keyCode, uint state, int type, uint time) { if(_old!=null) return await _old.ProcessKeyEventAsync(keyVal, keyCode, state, type, time) != 0; - return await _modern.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time); + return await (_modern?.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time) ?? Task.FromResult(false)); } - public Task WatchCommitStringAsync(Action handler) => - _old?.WatchCommitStringAsync(handler) ?? _modern.WatchCommitStringAsync(handler); + public Task WatchCommitStringAsync(Action handler) => + _old?.WatchCommitStringAsync(handler) + ?? _modern?.WatchCommitStringAsync(handler) + ?? Task.FromResult(default(IDisposable?)); - public Task WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler) + public Task WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler) { return _old?.WatchForwardKeyAsync(handler) - ?? _modern.WatchForwardKeyAsync(ev => - handler((ev.keyval, ev.state, ev.type ? 1 : 0))); + ?? _modern?.WatchForwardKeyAsync(ev => + handler((ev.keyval, ev.state, ev.type ? 1 : 0))) + ?? Task.FromResult(default(IDisposable?)); } public Task SetCapacityAsync(uint flags) => - _old?.SetCapacityAsync(flags) ?? _modern.SetCapabilityAsync(flags); + _old?.SetCapacityAsync(flags) ?? _modern?.SetCapabilityAsync(flags) ?? Task.CompletedTask; } } diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 0b85965de7..791431dfa7 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -12,7 +12,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx { internal class FcitxX11TextInputMethod : DBusTextInputMethodBase { - private FcitxICWrapper _context; + private FcitxICWrapper? _context; private FcitxCapabilityFlags? _lastReportedFlags; public FcitxX11TextInputMethod(Connection connection) : base(connection, @@ -49,7 +49,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx return true; } - protected override Task Disconnect() => _context.DestroyICAsync(); + protected override Task Disconnect() => _context?.DestroyICAsync() ?? Task.CompletedTask; protected override void OnDisconnected() => _context = null; @@ -60,18 +60,18 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx } protected override Task SetCursorRectCore(PixelRect cursorRect) => - _context.SetCursorRectAsync(cursorRect.X, cursorRect.Y, Math.Max(1, cursorRect.Width), - Math.Max(1, cursorRect.Height)); - - protected override Task SetActiveCore(bool active) - { - if (active) - return _context.FocusInAsync(); - else - return _context.FocusOutAsync(); - } + _context?.SetCursorRectAsync(cursorRect.X, cursorRect.Y, Math.Max(1, cursorRect.Width), + Math.Max(1, cursorRect.Height)) + ?? Task.CompletedTask; + + protected override Task SetActiveCore(bool active)=> (active + ? _context?.FocusInAsync() + : _context?.FocusOutAsync()) + ?? Task.CompletedTask; + - protected override Task ResetContextCore() => _context.ResetAsync(); + protected override Task ResetContextCore() => _context?.ResetAsync() + ?? Task.CompletedTask; protected override async Task HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode) { @@ -88,9 +88,15 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx var type = args.Type == RawKeyEventType.KeyDown ? FcitxKeyEventType.FCITX_PRESS_KEY : FcitxKeyEventType.FCITX_RELEASE_KEY; - - return await _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state, (int)type, - (uint)args.Timestamp).ConfigureAwait(false); + if (_context is { }) + { + return await _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state, (int)type, + (uint)args.Timestamp).ConfigureAwait(false); + } + else + { + return false; + } } public override void SetOptions(TextInputOptions options) => diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs index 26c0d249f3..4ef034adb9 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs @@ -22,25 +22,25 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus Task GetEngineAsync(); Task DestroyAsync(); Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos); - Task WatchCommitTextAsync(Action cb, Action onError = null); - Task WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action onError = null); - Task WatchRequireSurroundingTextAsync(Action handler, Action onError = null); - Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action onError = null); - Task WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action onError = null); - Task WatchShowPreeditTextAsync(Action handler, Action onError = null); - Task WatchHidePreeditTextAsync(Action handler, Action onError = null); - Task WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action onError = null); - Task WatchShowAuxiliaryTextAsync(Action handler, Action onError = null); - Task WatchHideAuxiliaryTextAsync(Action handler, Action onError = null); - Task WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action onError = null); - Task WatchShowLookupTableAsync(Action handler, Action onError = null); - Task WatchHideLookupTableAsync(Action handler, Action onError = null); - Task WatchPageUpLookupTableAsync(Action handler, Action onError = null); - Task WatchPageDownLookupTableAsync(Action handler, Action onError = null); - Task WatchCursorUpLookupTableAsync(Action handler, Action onError = null); - Task WatchCursorDownLookupTableAsync(Action handler, Action onError = null); - Task WatchRegisterPropertiesAsync(Action handler, Action onError = null); - Task WatchUpdatePropertyAsync(Action handler, Action onError = null); + Task WatchCommitTextAsync(Action cb, Action? onError = null); + Task WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action? onError = null); + Task WatchRequireSurroundingTextAsync(Action handler, Action? onError = null); + Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action? onError = null); + Task WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action? onError = null); + Task WatchShowPreeditTextAsync(Action handler, Action? onError = null); + Task WatchHidePreeditTextAsync(Action handler, Action? onError = null); + Task WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action? onError = null); + Task WatchShowAuxiliaryTextAsync(Action handler, Action? onError = null); + Task WatchHideAuxiliaryTextAsync(Action handler, Action? onError = null); + Task WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action? onError = null); + Task WatchShowLookupTableAsync(Action handler, Action? onError = null); + Task WatchHideLookupTableAsync(Action handler, Action? onError = null); + Task WatchPageUpLookupTableAsync(Action handler, Action? onError = null); + Task WatchPageDownLookupTableAsync(Action handler, Action? onError = null); + Task WatchCursorUpLookupTableAsync(Action handler, Action? onError = null); + Task WatchCursorDownLookupTableAsync(Action handler, Action? onError = null); + Task WatchRegisterPropertiesAsync(Action handler, Action? onError = null); + Task WatchUpdatePropertyAsync(Action handler, Action? onError = null); } diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 1397eaa57b..2324ca44a7 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -9,7 +9,7 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus { internal class IBusX11TextInputMethod : DBusTextInputMethodBase { - private IIBusInputContext _context; + private IIBusInputContext? _context; public IBusX11TextInputMethod(Connection connection) : base(connection, "org.freedesktop.portal.IBus") @@ -53,16 +53,16 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus private void OnCommitText(object wtf) { // Hello darkness, my old friend - var prop = wtf.GetType().GetField("Item3"); - if (prop != null) + if (wtf.GetType().GetField("Item3") is { } prop) { - var text = (string)prop.GetValue(wtf); + var text = prop.GetValue(wtf) as string; if (!string.IsNullOrEmpty(text)) - FireCommit(text); + FireCommit(text!); } } - protected override Task Disconnect() => _context.DestroyAsync(); + protected override Task Disconnect() => _context?.DestroyAsync() + ?? Task.CompletedTask; protected override void OnDisconnected() { @@ -71,13 +71,15 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus } protected override Task SetCursorRectCore(PixelRect rect) - => _context.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height); + => _context?.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height) + ?? Task.CompletedTask; protected override Task SetActiveCore(bool active) - => active ? _context.FocusInAsync() : _context.FocusOutAsync(); + => (active ? _context?.FocusInAsync() : _context?.FocusOutAsync()) + ?? Task.CompletedTask; protected override Task ResetContextCore() - => _context.ResetAsync(); + => _context?.ResetAsync() ?? Task.CompletedTask; protected override Task HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode) { @@ -94,7 +96,15 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus if (args.Type == RawKeyEventType.KeyUp) state |= IBusModifierMask.ReleaseMask; - return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state); + if(_context is { }) + { + return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state); + } + else + { + return Task.FromResult(false); + } + } public override void SetOptions(TextInputOptions options) diff --git a/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs b/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs index 7f71ecf0ff..86978c8b60 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs @@ -17,7 +17,7 @@ namespace Avalonia.FreeDesktop.DBusIme new DBusInputMethodFactory(_ => new IBusX11TextInputMethod(conn)) }; - static Func DetectInputMethod() + static Func? DetectInputMethod() { foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" }) { diff --git a/src/Avalonia.FreeDesktop/DBusMenu.cs b/src/Avalonia.FreeDesktop/DBusMenu.cs index 7180345386..3a1c65e7c9 100644 --- a/src/Avalonia.FreeDesktop/DBusMenu.cs +++ b/src/Avalonia.FreeDesktop/DBusMenu.cs @@ -28,18 +28,18 @@ namespace Avalonia.FreeDesktop.DBusMenu Task EventGroupAsync((int id, string eventId, object data, uint timestamp)[] events); Task AboutToShowAsync(int Id); Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids); - Task WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action onError = null); - Task WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action onError = null); - Task WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action onError = null); + Task WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action? onError = null); + Task WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action? onError = null); + Task WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action? onError = null); } [Dictionary] class DBusMenuProperties { public uint Version { get; set; } = default (uint); - public string TextDirection { get; set; } = default (string); - public string Status { get; set; } = default (string); - public string[] IconThemePath { get; set; } = default (string[]); + public string? TextDirection { get; set; } = default (string); + public string? Status { get; set; } = default (string); + public string[]? IconThemePath { get; set; } = default (string[]); } @@ -50,7 +50,7 @@ namespace Avalonia.FreeDesktop.DBusMenu Task UnregisterWindowAsync(uint WindowId); Task<(string service, ObjectPath menuObjectPath)> GetMenuForWindowAsync(uint WindowId); Task<(uint, string, ObjectPath)[]> GetMenusAsync(); - Task WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action onError = null); - Task WatchWindowUnregisteredAsync(Action handler, Action onError = null); + Task WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action? onError = null); + Task WatchWindowUnregisteredAsync(Action handler, Action? onError = null); } } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 206c24ad5e..c0511420a6 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -17,7 +17,7 @@ namespace Avalonia.FreeDesktop { public class DBusMenuExporter { - public static ITopLevelNativeMenuExporter TryCreateTopLevelNativeMenu(IntPtr xid) + public static ITopLevelNativeMenuExporter? TryCreateTopLevelNativeMenu(IntPtr xid) { if (DBusHelper.Connection == null) return null; @@ -37,10 +37,10 @@ namespace Avalonia.FreeDesktop { private readonly Connection _dbus; private readonly uint _xid; - private IRegistrar _registrar; + private IRegistrar? _registrar; private bool _disposed; private uint _revision = 1; - private NativeMenu _menu; + private NativeMenu? _menu; private readonly Dictionary _idsToItems = new Dictionary(); private readonly Dictionary _itemsToIds = new Dictionary(); private readonly HashSet _menus = new HashSet(); @@ -73,10 +73,10 @@ namespace Avalonia.FreeDesktop if (_appMenu) { await _dbus.RegisterObjectAsync(this); - _registrar = DBusHelper.Connection.CreateProxy( + _registrar = DBusHelper.Connection?.CreateProxy( "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar"); - if (!_disposed) + if (!_disposed && _registrar is { }) await _registrar.RegisterWindowAsync(_xid, ObjectPath); } else @@ -109,9 +109,9 @@ namespace Avalonia.FreeDesktop public bool IsNativeMenuExported { get; private set; } - public event EventHandler OnIsNativeMenuExportedChanged; + public event EventHandler? OnIsNativeMenuExportedChanged; - public void SetNativeMenu(NativeMenu menu) + public void SetNativeMenu(NativeMenu? menu) { if (menu == null) menu = new NativeMenu(); @@ -153,7 +153,7 @@ namespace Avalonia.FreeDesktop Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background); } - private (NativeMenuItemBase item, NativeMenu menu) GetMenu(int id) + private (NativeMenuItemBase? item, NativeMenu? menu) GetMenu(int id) { if (id == 0) return (null, _menu); @@ -161,7 +161,7 @@ namespace Avalonia.FreeDesktop return (item, (item as NativeMenuItem)?.Menu); } - private void EnsureSubscribed(NativeMenu menu) + private void EnsureSubscribed(NativeMenu? menu) { if(menu!=null && _menus.Add(menu)) ((INotifyCollectionChanged)menu.Items).CollectionChanged += OnMenuItemsChanged; @@ -180,12 +180,12 @@ namespace Avalonia.FreeDesktop return id; } - private void OnMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnMenuItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) { QueueReset(); } - private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + private void OnItemPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { QueueReset(); } @@ -216,7 +216,7 @@ namespace Avalonia.FreeDesktop "type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data" }; - object GetProperty((NativeMenuItemBase item, NativeMenu menu) i, string name) + object? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) { var (it, menu) = i; @@ -302,7 +302,7 @@ namespace Avalonia.FreeDesktop } private List> _reusablePropertyList = new List>(); - KeyValuePair[] GetProperties((NativeMenuItemBase item, NativeMenu menu) i, string[] names) + KeyValuePair[] GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) { if (names?.Length > 0 != true) names = AllProperties; @@ -336,7 +336,7 @@ namespace Avalonia.FreeDesktop return Task.FromResult(rv); } - (int, KeyValuePair[], object[]) GetLayout(NativeMenuItemBase item, NativeMenu menu, int depth, string[] propertyNames) + (int, KeyValuePair[], object[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames) { var id = item == null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); @@ -414,22 +414,22 @@ namespace Avalonia.FreeDesktop private event Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> ItemsPropertiesUpdated { add { } remove { } } - private event Action<(uint revision, int parent)> LayoutUpdated; + private event Action<(uint revision, int parent)>? LayoutUpdated; private event Action<(int id, uint timestamp)> ItemActivationRequested { add { } remove { } } private event Action PropertiesChanged { add { } remove { } } - async Task IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action onError) + async Task IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action? onError) { ItemsPropertiesUpdated += handler; return Disposable.Create(() => ItemsPropertiesUpdated -= handler); } - async Task IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action onError) + async Task IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action? onError) { LayoutUpdated += handler; return Disposable.Create(() => LayoutUpdated -= handler); } - async Task IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action onError) + async Task IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action? onError) { ItemActivationRequested+= handler; return Disposable.Create(() => ItemActivationRequested -= handler); diff --git a/src/Avalonia.FreeDesktop/DBusRequest.cs b/src/Avalonia.FreeDesktop/DBusRequest.cs index 940a476916..d84905324f 100644 --- a/src/Avalonia.FreeDesktop/DBusRequest.cs +++ b/src/Avalonia.FreeDesktop/DBusRequest.cs @@ -11,6 +11,6 @@ namespace Avalonia.FreeDesktop internal interface IRequest : IDBusObject { Task CloseAsync(); - Task WatchResponseAsync(Action<(uint response, IDictionary results)> handler, Action onError = null); + Task WatchResponseAsync(Action<(uint response, IDictionary results)> handler, Action? onError = null); } } diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs index d68c02bfd6..b69ea68a76 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs @@ -10,7 +10,7 @@ namespace Avalonia.FreeDesktop public IDisposable Listen(ObservableCollection mountedDrives) { Contract.Requires(mountedDrives != null); - return new LinuxMountedVolumeInfoListener(ref mountedDrives); + return new LinuxMountedVolumeInfoListener(ref mountedDrives!); } } } diff --git a/src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj b/src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj index 97e58f8a64..b5c955a8a6 100644 --- a/src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj +++ b/src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj @@ -11,6 +11,7 @@ all + diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index ee4125101c..46a985c0e8 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -133,7 +133,7 @@ namespace Avalonia.LinuxFramebuffer.Output var device = gbm_create_device(card.Fd); _gbmTargetSurface = gbm_surface_create(device, modeInfo.Resolution.Width, modeInfo.Resolution.Height, GbmColorFormats.GBM_FORMAT_XRGB8888, GbmBoFlags.GBM_BO_USE_SCANOUT | GbmBoFlags.GBM_BO_USE_RENDERING); - if(_gbmTargetSurface == null) + if(_gbmTargetSurface == IntPtr.Zero) throw new InvalidOperationException("Unable to create GBM surface"); _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), false, 0x31D7, device, null); diff --git a/src/Avalonia.SourceGenerator/IsExternalInit.cs b/src/Shared/IsExternalInit.cs similarity index 100% rename from src/Avalonia.SourceGenerator/IsExternalInit.cs rename to src/Shared/IsExternalInit.cs From 230166d0bc894dc802dc85fc25ef19452caa414c Mon Sep 17 00:00:00 2001 From: Lubomir Tetak Date: Wed, 6 Jul 2022 14:54:33 +0200 Subject: [PATCH 02/21] EffectiveViewportChangedListeners thread safety improved --- src/Avalonia.Base/Layout/LayoutManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index fc988a8d6c..dd2db9304d 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -340,7 +340,7 @@ namespace Avalonia.Layout { for (var i = 0; i < count; ++i) { - var l = _effectiveViewportChangedListeners[i]; + var l = listeners[i]; if (!l.Listener.IsAttachedToVisualTree) { @@ -352,7 +352,7 @@ namespace Avalonia.Layout if (viewport != l.Viewport) { l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); - _effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport); + l.Viewport = viewport; } } } @@ -404,7 +404,7 @@ namespace Avalonia.Layout } } - private readonly struct EffectiveViewportChangedListener + private struct EffectiveViewportChangedListener { public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport) { @@ -413,7 +413,7 @@ namespace Avalonia.Layout } public ILayoutable Listener { get; } - public Rect Viewport { get; } + public Rect Viewport { get; set; } } } } From 22971f56cb3eabc56d87d94ac8059aed78977aab Mon Sep 17 00:00:00 2001 From: Lubomir Tetak Date: Thu, 7 Jul 2022 10:20:30 +0200 Subject: [PATCH 03/21] EffectiveViewportChangedListeners must be reference type --- src/Avalonia.Base/Layout/LayoutManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index 91358adc05..b9ca6bfbd7 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -414,7 +414,7 @@ namespace Avalonia.Layout } } - private struct EffectiveViewportChangedListener + private class EffectiveViewportChangedListener { public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport) { From 1812540ad9c722bb995e1cc2a9b60ca1b269cc8b Mon Sep 17 00:00:00 2001 From: Lubomir Tetak Date: Thu, 7 Jul 2022 10:44:19 +0200 Subject: [PATCH 04/21] unit test --- ...ayoutableTests_EffectiveViewportChanged.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs index 7c1a525002..a58d28004f 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs @@ -312,6 +312,30 @@ namespace Avalonia.Base.UnitTests.Layout }); } + [Fact] + public async Task Event_Unsubscribed_While_Inside_Callback() + { + await RunOnUIThread.Execute(async () => + { + var root = CreateRoot(); + var target = new Canvas(); + var raised = 0; + + void OnTargetOnEffectiveViewportChanged(object s, EffectiveViewportChangedEventArgs e) + { + target.EffectiveViewportChanged -= OnTargetOnEffectiveViewportChanged; + ++raised; + } + target.EffectiveViewportChanged += OnTargetOnEffectiveViewportChanged; + + root.Child = target; + + await ExecuteInitialLayoutPass(root); + + Assert.Equal(1, raised); + }); + } + private TestRoot CreateRoot() => new TestRoot { Width = 1200, Height = 900 }; private Task ExecuteInitialLayoutPass(TestRoot root) From 6483b5edc778e8fbeb1a0c29d55c150f9d60965e Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 7 Jul 2022 10:46:06 +0200 Subject: [PATCH 05/21] Add Has method to IPseudoClasses --- src/Avalonia.Base/Controls/IPseudoClasses.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Base/Controls/IPseudoClasses.cs b/src/Avalonia.Base/Controls/IPseudoClasses.cs index eda521727f..45013ee069 100644 --- a/src/Avalonia.Base/Controls/IPseudoClasses.cs +++ b/src/Avalonia.Base/Controls/IPseudoClasses.cs @@ -19,5 +19,12 @@ namespace Avalonia.Controls /// /// The pseudoclass name. bool Remove(string name); + + /// + /// Returns whether a pseudoclass is present in the collection. + /// + /// The pseudoclass name. + /// Whether the pseudoclass is present. + bool Has(string name); } } From 0553c7ceabf01199a0c45cf21c3e6126fde1e660 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 8 Jul 2022 15:32:09 +0100 Subject: [PATCH 06/21] add a failing unit test --- .../WindowTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index a8c9b68d12..ab4fd566c8 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -540,6 +540,51 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(window.Position, expectedPosition); } } + + [Fact] + public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen_1080_175() + { + var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + + var screens = new Mock(); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object }); + screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); + + + var windowImpl = MockWindowingPlatform.CreateWindowMock(); + windowImpl.Setup(x => x.DesktopScaling).Returns(1.75); + windowImpl.Setup(x => x.RenderScaling).Returns(1.75); + windowImpl.Setup(x => x.Screen).Returns(screens.Object); + + var clientSize = new Size(801.142, 366); + + windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); + + windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) + .Callback((x, y) => + { + clientSize = x;//.Constrain(screen1.Object.Bounds.Size.ToSize(1.75)); + windowImpl.Object.Resized?.Invoke(clientSize, y); + }); + + windowImpl.Setup(x => x.FrameSize).Returns(() => clientSize.Inflate(new Thickness(5, 25, 5, 5))); + + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(windowImpl.Object); + window.WindowStartupLocation = WindowStartupLocation.CenterScreen; + window.MinWidth = 720; + window.MinHeight = 480; + //window.Width = 720; + //window.Height = 480; + + window.Show(); + + var expectedPosition = new PixelPoint(321, 36); + + Assert.Equal(window.Position, expectedPosition); + } + } [Fact] public void Window_Should_Be_Centered_Relative_To_Owner_When_WindowStartupLocation_Is_CenterOwner() From b9aa49bc05d039b01a52e0a10d0494be6bdb92d9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 8 Jul 2022 18:31:49 +0100 Subject: [PATCH 07/21] Add failing unit test. --- .../Avalonia.Controls.UnitTests/WindowTests.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index ab4fd566c8..3677d62050 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -542,28 +542,25 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen_1080_175() + public void Window_Should_Be_Sized_To_MinSize_If_InitialSize_Less_Than_MinSize() { var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); - var screens = new Mock(); screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object }); screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); - var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1.75); windowImpl.Setup(x => x.RenderScaling).Returns(1.75); windowImpl.Setup(x => x.Screen).Returns(screens.Object); - var clientSize = new Size(801.142, 366); + var clientSize = new Size(400.142, 366); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); - windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) .Callback((x, y) => { - clientSize = x;//.Constrain(screen1.Object.Bounds.Size.ToSize(1.75)); + clientSize = x; windowImpl.Object.Resized?.Invoke(clientSize, y); }); @@ -575,14 +572,10 @@ namespace Avalonia.Controls.UnitTests window.WindowStartupLocation = WindowStartupLocation.CenterScreen; window.MinWidth = 720; window.MinHeight = 480; - //window.Width = 720; - //window.Height = 480; window.Show(); - - var expectedPosition = new PixelPoint(321, 36); - - Assert.Equal(window.Position, expectedPosition); + + Assert.Equal(new Size(720, 480), window.Bounds.Size); } } From 8f0b3911a5216dee5d44b5d5719f34b3a0ef2973 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 12:22:57 +0200 Subject: [PATCH 08/21] Added failing tests for MenuItem.TemplatedParent. Seems #8412 caused `Menu.TemplatedParent` to get set to the parent `MenuItem` when re-opening a popup. --- .../MenuItemTests.cs | 44 ++++++ .../Primitives/PopupTests.cs | 147 +++++++++++++++++- 2 files changed, 190 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index db31d22b4f..d25a790fde 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -8,6 +8,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Platform; using Avalonia.UnitTests; +using Avalonia.VisualTree; using Moq; using Xunit; @@ -301,6 +302,49 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(3, canExecuteCallCount); } } + + + [Fact] + public void TemplatedParent_Should_Not_Be_Applied_To_Submenus() + { + using (Application()) + { + MenuItem topLevelMenu; + MenuItem childMenu1; + MenuItem childMenu2; + var menu = new Menu + { + Items = new[] + { + (topLevelMenu = new MenuItem + { + Header = "Foo", + Items = new[] + { + (childMenu1 = new MenuItem { Header = "Bar" }), + (childMenu2 = new MenuItem { Header = "Baz" }), + } + }), + } + }; + + var window = new Window { Content = menu }; + window.LayoutManager.ExecuteInitialLayoutPass(); + + topLevelMenu.IsSubMenuOpen = true; + + Assert.True(((IVisual)childMenu1).IsAttachedToVisualTree); + Assert.Null(childMenu1.TemplatedParent); + Assert.Null(childMenu2.TemplatedParent); + + topLevelMenu.IsSubMenuOpen = false; + topLevelMenu.IsSubMenuOpen = true; + + Assert.Null(childMenu1.TemplatedParent); + Assert.Null(childMenu2.TemplatedParent); + } + } + private IDisposable Application() { var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d9cb40d1cc..5f91f2e2a1 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -295,7 +295,7 @@ namespace Avalonia.Controls.UnitTests.Primitives } [Fact] - public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent() + public void ContentControl_With_Popup_In_Template_Should_Set_TemplatedParent() { // Test uses OverlayPopupHost default template using (CreateServices()) @@ -384,6 +384,134 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void ItemsControl_With_Popup_In_Template_Should_Set_TemplatedParent() + { + // Test uses OverlayPopupHost default template + using (CreateServices()) + { + PopupItemsControl target; + var item = new Border(); + var root = PreparedWindow(target = new PopupItemsControl + { + Items = new[] { item }, + Template = new FuncControlTemplate(PopupItemsControlTemplate), + }); ; + root.Show(); + + target.ApplyTemplate(); + + var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); + popup.Open(); + + var popupRoot = (Control)popup.Host; + popupRoot.Measure(Size.Infinity); + popupRoot.Arrange(new Rect(popupRoot.DesiredSize)); + + var children = popupRoot.GetVisualDescendants().ToList(); + var types = children.Select(x => x.GetType().Name).ToList(); + + if (UsePopupHost) + { + Assert.Equal( + new[] + { + "LayoutTransformControl", + "VisualLayerManager", + "ContentPresenter", + "ItemsPresenter", + "StackPanel", + "Border", + }, + types); + } + else + { + Assert.Equal( + new[] + { + "LayoutTransformControl", + "Panel", + "Border", + "VisualLayerManager", + "ContentPresenter", + "ItemsPresenter", + "StackPanel", + "Border", + }, + types); + } + + var templatedParents = children + .OfType() + .Select(x => x.TemplatedParent).ToList(); + + if (UsePopupHost) + { + Assert.Equal( + new object[] + { + popupRoot, + popupRoot, + popupRoot, + target, + target, + null, + }, + templatedParents); + } + else + { + Assert.Equal( + new object[] + { + popupRoot, + popupRoot, + popupRoot, + popupRoot, + popupRoot, + target, + target, + null, + }, + templatedParents); + } + } + } + + [Fact] + public void Should_Not_Overwrite_TemplatedParent_Of_Item_In_ItemsControl_With_Popup_On_Second_Open() + { + // Test uses OverlayPopupHost default template + using (CreateServices()) + { + PopupItemsControl target; + var item = new Border(); + var root = PreparedWindow(target = new PopupItemsControl + { + Items = new[] { item }, + Template = new FuncControlTemplate(PopupItemsControlTemplate), + }); + root.Show(); + + target.ApplyTemplate(); + + var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); + popup.Open(); + + var popupRoot = (Control)popup.Host; + popupRoot.Measure(Size.Infinity); + popupRoot.Arrange(new Rect(popupRoot.DesiredSize)); + + Assert.Null(item.TemplatedParent); + + popup.Close(); + popup.Open(); + + Assert.Null(item.TemplatedParent); + } + } + [Fact] public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit() { @@ -979,10 +1107,27 @@ namespace Avalonia.Controls.UnitTests.Primitives }.RegisterInNameScope(scope); } + private static IControl PopupItemsControlTemplate(PopupItemsControl control, INameScope scope) + { + return new Popup + { + Name = "popup", + PlacementTarget = control, + Child = new ItemsPresenter + { + [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], + } + }.RegisterInNameScope(scope); + } + private class PopupContentControl : ContentControl { } + private class PopupItemsControl : ItemsControl + { + } + private class TestControl : Decorator { public event EventHandler DataContextBeginUpdate; From 78a7257a5a3a0cd33ad77382d4d0c29759263dfa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 12:32:22 +0200 Subject: [PATCH 09/21] Don't apply templated parent if already set. When applying the `TemplatedParent`, don't move into trees that already have the `TemplatedParent` property set - in this case we can assume that the templated parent has already been set for this tree, and it fixes the problem with menus added in the previous commit. --- src/Avalonia.Controls/Primitives/TemplatedControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 4403bfce51..c3e0f3e523 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -396,7 +396,7 @@ namespace Avalonia.Controls.Primitives for (var i = 0; i < count; i++) { - if (children[i] is IStyledElement child) + if (children[i] is IStyledElement child && child.TemplatedParent is null) { ApplyTemplatedParent(child, templatedParent); } From 5da9d528c9a9901afa6cd6e856b9055022cdb706 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 14:50:52 +0200 Subject: [PATCH 10/21] Added missing param docs. --- src/Avalonia.Controls/Primitives/TemplatedControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index c3e0f3e523..e50f991cdb 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -387,6 +387,7 @@ namespace Avalonia.Controls.Primitives /// Sets the TemplatedParent property for the created template children. /// /// The control. + /// The templated parent to apply. internal static void ApplyTemplatedParent(IStyledElement control, ITemplatedControl? templatedParent) { control.SetValue(TemplatedParentProperty, templatedParent); From 435419588a3702efc1f67eec7a6865c64a03cbb5 Mon Sep 17 00:00:00 2001 From: Eric M Date: Wed, 6 Jul 2022 21:18:40 +1000 Subject: [PATCH 11/21] Allow customisation of ProgressBar text format. --- .../ControlCatalog/Pages/ProgressBarPage.xaml | 27 ++++++++++++++----- .../Converters/StringFormatConverter.cs | 27 +++++++++++++++++++ src/Avalonia.Controls/ProgressBar.cs | 23 ++++++++++++++++ .../Controls/ProgressBar.xaml | 21 ++++++++++++--- .../Controls/ProgressBar.xaml | 16 ++++++++++- 5 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 src/Avalonia.Controls/Converters/StringFormatConverter.cs diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml index db7d7d3280..8e73f1d0f5 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -1,22 +1,37 @@ A progress bar control - + + + Maximum + + + + Minimum + + + + Progress Text Format + + - + - + - - + + - + diff --git a/src/Avalonia.Controls/Converters/StringFormatConverter.cs b/src/Avalonia.Controls/Converters/StringFormatConverter.cs new file mode 100644 index 0000000000..ae920dac7e --- /dev/null +++ b/src/Avalonia.Controls/Converters/StringFormatConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Avalonia.Data; +using Avalonia.Data.Converters; + +namespace Avalonia.Controls.Converters; + +/// +/// Calls on the passed in values, where the first element in the list +/// is the string, and everything after it is passed into the object array in order. +/// +public class StringFormatConverter : IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + try + { + return string.Format((string)values[0]!, values.Skip(1).ToArray()); + } + catch (Exception e) + { + return new BindingNotification(e, BindingErrorType.Error); + } + } +} diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 1075328c67..70edeadfd9 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -96,6 +96,7 @@ namespace Avalonia.Controls } } + private double _percentage; private double _indeterminateStartingOffset; private double _indeterminateEndingOffset; private Border? _indicator; @@ -106,9 +107,17 @@ namespace Avalonia.Controls public static readonly StyledProperty ShowProgressTextProperty = AvaloniaProperty.Register(nameof(ShowProgressText)); + public static readonly StyledProperty ProgressTextFormatProperty = + AvaloniaProperty.Register(nameof(ProgressTextFormat), "{1:0}%"); + public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); + public static readonly DirectProperty PercentageProperty = + AvaloniaProperty.RegisterDirect( + nameof(Percentage), + o => o.Percentage); + [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public static readonly DirectProperty IndeterminateStartingOffsetProperty = AvaloniaProperty.RegisterDirect( @@ -123,6 +132,12 @@ namespace Avalonia.Controls p => p.IndeterminateEndingOffset, (p, o) => p.IndeterminateEndingOffset = o); + public double Percentage + { + get { return _percentage; } + private set { SetAndRaise(PercentageProperty, ref _percentage, value); } + } + [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public double IndeterminateStartingOffset { @@ -165,6 +180,12 @@ namespace Avalonia.Controls set => SetValue(ShowProgressTextProperty, value); } + public string ProgressTextFormat + { + get => GetValue(ProgressTextFormatProperty); + set => SetValue(ProgressTextFormatProperty, value); + } + public Orientation Orientation { get => GetValue(OrientationProperty); @@ -245,6 +266,8 @@ namespace Avalonia.Controls _indicator.Width = bounds.Width * percent; else _indicator.Height = bounds.Height * percent; + + Percentage = percent * 100; } } } diff --git a/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml index fd847b5d65..3f684f3936 100644 --- a/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml @@ -1,4 +1,6 @@ - + @@ -11,10 +13,13 @@ From 4ca4986b4f6e6bf31e7e53a5de531895a33b8ee7 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 12 Jul 2022 15:58:58 +0200 Subject: [PATCH 19/21] rename --- src/Avalonia.Base/Controls/IPseudoClasses.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Controls/IPseudoClasses.cs b/src/Avalonia.Base/Controls/IPseudoClasses.cs index 45013ee069..438b05a8cf 100644 --- a/src/Avalonia.Base/Controls/IPseudoClasses.cs +++ b/src/Avalonia.Base/Controls/IPseudoClasses.cs @@ -25,6 +25,6 @@ namespace Avalonia.Controls /// /// The pseudoclass name. /// Whether the pseudoclass is present. - bool Has(string name); + bool Contains(string name); } } From d1f3c4d691c3e8336b3f98dc7f524b3fc30f515b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 12 Jul 2022 17:04:03 +0300 Subject: [PATCH 20/21] [mac] Don't update layer if IOSurface doesn't have any valid content yet --- native/Avalonia.Native/src/OSX/rendertarget.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 266d0345d1..2075cc85ab 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -13,6 +13,7 @@ { @public IOSurfaceRef surface; @public AvnPixelSize size; + @public bool hasContent; @public float scale; ComPtr _context; GLuint _framebuffer, _texture, _renderbuffer; @@ -41,6 +42,7 @@ self->scale = scale; self->size = size; self->_context = context; + self->hasContent = false; return self; } @@ -92,6 +94,7 @@ _context->MakeCurrent(release.getPPV()); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glFlush(); + self->hasContent = true; } -(void) dealloc @@ -170,6 +173,8 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta @synchronized (lock) { if(_layer == nil) return; + if(!surface->hasContent) + return; [CATransaction begin]; [_layer setContents: nil]; if(surface != nil) @@ -213,6 +218,7 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes); } IOSurfaceUnlock(surf, 0, nil); + surface->hasContent = true; [self updateLayer]; return S_OK; } From 7df34acb3d8e01c03fe39dc0fc3aec3c012c7b60 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 13 Jul 2022 12:14:10 +0200 Subject: [PATCH 21/21] Update src/Avalonia.Base/Media/GlyphRun.cs --- src/Avalonia.Base/Media/GlyphRun.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 703b56b0e8..da2143c188 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -652,7 +652,7 @@ namespace Avalonia.Media for (var index = 0; index < glyphCount; index++) { width -= GetGlyphAdvance(index, out _); - } + } } else {