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/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.Base/Controls/Classes.cs b/src/Avalonia.Base/Controls/Classes.cs index 50605661fa..e64209c3cb 100644 --- a/src/Avalonia.Base/Controls/Classes.cs +++ b/src/Avalonia.Base/Controls/Classes.cs @@ -14,6 +14,8 @@ namespace Avalonia.Controls /// public class Classes : AvaloniaList, IPseudoClasses { + private List? _listeners; + /// /// Initializes a new instance of the class. /// @@ -39,6 +41,11 @@ namespace Avalonia.Controls { } + /// + /// Gets the number of listeners subscribed to this collection for unit testing purposes. + /// + internal int ListenerCount => _listeners?.Count ?? 0; + /// /// Parses a classes string. /// @@ -62,6 +69,7 @@ namespace Avalonia.Controls if (!Contains(name)) { base.Add(name); + NotifyChanged(); } } @@ -89,6 +97,7 @@ namespace Avalonia.Controls } base.AddRange(c); + NotifyChanged(); } /// @@ -103,6 +112,8 @@ namespace Avalonia.Controls RemoveAt(i); } } + + NotifyChanged(); } /// @@ -122,6 +133,7 @@ namespace Avalonia.Controls if (!Contains(name)) { base.Insert(index, name); + NotifyChanged(); } } @@ -154,6 +166,7 @@ namespace Avalonia.Controls if (toInsert != null) { base.InsertRange(index, toInsert); + NotifyChanged(); } } @@ -169,7 +182,14 @@ namespace Avalonia.Controls public override bool Remove(string name) { ThrowIfPseudoclass(name, "removed"); - return base.Remove(name); + + if (base.Remove(name)) + { + NotifyChanged(); + return true; + } + + return false; } /// @@ -197,6 +217,7 @@ namespace Avalonia.Controls if (toRemove != null) { base.RemoveAll(toRemove); + NotifyChanged(); } } @@ -214,6 +235,7 @@ namespace Avalonia.Controls var name = this[index]; ThrowIfPseudoclass(name, "removed"); base.RemoveAt(index); + NotifyChanged(); } /// @@ -224,6 +246,7 @@ namespace Avalonia.Controls public override void RemoveRange(int index, int count) { base.RemoveRange(index, count); + NotifyChanged(); } /// @@ -255,6 +278,7 @@ namespace Avalonia.Controls } base.AddRange(source); + NotifyChanged(); } /// @@ -263,13 +287,38 @@ namespace Avalonia.Controls if (!Contains(name)) { base.Add(name); + NotifyChanged(); } } /// bool IPseudoClasses.Remove(string name) { - return base.Remove(name); + if (base.Remove(name)) + { + NotifyChanged(); + return true; + } + + return false; + } + + internal void AddListener(IClassesChangedListener listener) + { + (_listeners ??= new()).Add(listener); + } + + internal void RemoveListener(IClassesChangedListener listener) + { + _listeners?.Remove(listener); + } + + private void NotifyChanged() + { + if (_listeners is null) + return; + foreach (var listener in _listeners) + listener.Changed(); } private void ThrowIfPseudoclass(string name, string operation) diff --git a/src/Avalonia.Base/Controls/IClassesChangedListener.cs b/src/Avalonia.Base/Controls/IClassesChangedListener.cs new file mode 100644 index 0000000000..b4de893c97 --- /dev/null +++ b/src/Avalonia.Base/Controls/IClassesChangedListener.cs @@ -0,0 +1,14 @@ +namespace Avalonia.Controls +{ + /// + /// Internal interface for listening to changes in in a more + /// performant manner than subscribing to CollectionChanged. + /// + internal interface IClassesChangedListener + { + /// + /// Notifies the listener that the collection has changed. + /// + void Changed(); + } +} diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index b93bf87fdf..91d69b5d3d 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -55,7 +55,7 @@ namespace Avalonia.Data.Core.Plugins private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName) { - if (instance is IReflectableType reflectableType) + if (instance is IReflectableType reflectableType && instance is not Type) return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags); var type = instance.GetType(); diff --git a/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs b/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs index 98d3f16a0a..3f70ff50b3 100644 --- a/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using Avalonia.Collections; +using Avalonia.Controls; #nullable enable @@ -10,21 +11,17 @@ namespace Avalonia.Styling.Activators /// An which is active when a set of classes match those on a /// control. /// - internal sealed class StyleClassActivator : StyleActivatorBase + internal sealed class StyleClassActivator : StyleActivatorBase, IClassesChangedListener { private readonly IList _match; - private readonly IAvaloniaReadOnlyList _classes; - private NotifyCollectionChangedEventHandler? _classesChangedHandler; + private readonly Classes _classes; - public StyleClassActivator(IAvaloniaReadOnlyList classes, IList match) + public StyleClassActivator(Classes classes, IList match) { _classes = classes; _match = match; } - private NotifyCollectionChangedEventHandler ClassesChangedHandler => - _classesChangedHandler ??= ClassesChanged; - public static bool AreClassesMatching(IReadOnlyList classes, IList toMatch) { int remainingMatches = toMatch.Count; @@ -55,23 +52,20 @@ namespace Avalonia.Styling.Activators return remainingMatches == 0; } - protected override void Initialize() + void IClassesChangedListener.Changed() { PublishNext(IsMatching()); - _classes.CollectionChanged += ClassesChangedHandler; } - protected override void Deinitialize() + protected override void Initialize() { - _classes.CollectionChanged -= ClassesChangedHandler; + PublishNext(IsMatching()); + _classes.AddListener(this); } - private void ClassesChanged(object? sender, NotifyCollectionChangedEventArgs e) + protected override void Deinitialize() { - if (e.Action != NotifyCollectionChangedAction.Move) - { - PublishNext(IsMatching()); - } + _classes.RemoveListener(this); } private bool IsMatching() => AreClassesMatching(_classes, _match); diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 24d5d6bbbf..d52c8c7d5c 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using Avalonia.Controls; using Avalonia.Styling.Activators; #nullable enable @@ -125,7 +126,7 @@ namespace Avalonia.Styling { if (subscribe) { - var observable = new StyleClassActivator(control.Classes, _classes.Value); + var observable = new StyleClassActivator((Classes)control.Classes, _classes.Value); return new SelectorMatch(observable); } 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/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 4403bfce51..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); @@ -396,7 +397,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); } 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.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 3f42d95deb..ae663defd3 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -421,6 +421,7 @@ namespace Avalonia.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { + base.OnAttachedToVisualTree(e); InvalidateMeasure(); _viewportManager.ResetScrollers(); } diff --git a/src/Avalonia.Controls/TrayIcon.cs b/src/Avalonia.Controls/TrayIcon.cs index 2ccb03e447..20c8dff8ea 100644 --- a/src/Avalonia.Controls/TrayIcon.cs +++ b/src/Avalonia.Controls/TrayIcon.cs @@ -189,7 +189,10 @@ namespace Avalonia.Controls var app = Application.Current ?? throw new InvalidOperationException("Application not yet initialized."); var trayIcons = GetIcons(app); - RemoveIcons(trayIcons); + if (trayIcons != null) + { + RemoveIcons(trayIcons); + } } private static void Icons_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml index b7995c38e3..a1a2ab34bf 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml @@ -19,7 +19,7 @@ Classes="textBoxClearButton" ToolTip.Tip="Clear" Cursor="Hand" - Command="{ReflectionBinding $parent[TextBox].Clear}" + Command="{Binding $parent[TextBox].Clear}" Opacity="0.5" /> 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/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 @@ + +"; + + var contentControl = AvaloniaRuntimeXamlLoader.Parse(xaml); + contentControl.Measure(new Size(10, 10)); + + var result = contentControl.GetTemplateChildren().OfType().First(); + Assert.Equal("Hello", result.Content); + } + } + + [Fact] + public void ResolvesElementNameInTemplate() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + +"; + + var contentControl = AvaloniaRuntimeXamlLoader.Parse(xaml); + contentControl.Measure(new Size(10, 10)); + + var result = contentControl.GetTemplateChildren().OfType().First(); + + Assert.Equal("Hello", result.Content); + } + } + [Fact] public void Binds_To_Source() { @@ -1145,6 +1289,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal("bar-" + typeof(TestDataContext).FullName, textBlock.Text); } } + + [Fact] + public void SupportCastToTypeInExpressionWithProperty_ExplicitPropertyCast() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var contentControl = window.GetControl("contentControl"); + + var dataContext = new TestDataContext(); + + window.DataContext = dataContext; + + Assert.Equal(((IHasExplicitProperty)dataContext).ExplicitProperty, contentControl.Content); + } + } [Fact] public void Binds_To_Self_Without_DataType() @@ -1366,6 +1532,43 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void ResolvesDataTypeForAssignBinding() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" +"; + var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var compiledPath = ((CompiledBindingExtension)control.X).Path; + + var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + Assert.Equal(typeof(string), node.Property.PropertyType); + } + } + + [Fact] + public void ResolvesDataTypeForAssignBinding_FromBindingProperty() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" +"; + var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var compiledPath = ((CompiledBindingExtension)control.X).Path; + + var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + Assert.Equal(typeof(string), node.Property.PropertyType); + } + } + void Throws(string type, Action cb) { try @@ -1410,6 +1613,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions public interface IHasPropertyDerived : IHasProperty { } + public interface IHasExplicitProperty + { + string ExplicitProperty { get; } + } + public class AppendConverter : IValueConverter { public static IValueConverter Instance { get; } = new AppendConverter(); @@ -1429,7 +1637,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions public class TestDataContextBaseClass {} - public class TestDataContext : TestDataContextBaseClass, IHasPropertyDerived + public class TestDataContext : TestDataContextBaseClass, IHasPropertyDerived, IHasExplicitProperty { public string StringProperty { get; set; } @@ -1449,6 +1657,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions public INonIntegerIndexerDerived NonIntegerIndexerInterfaceProperty => NonIntegerIndexerProperty; + string IHasExplicitProperty.ExplicitProperty => "Hello"; + + public string ExplicitProperty => "Bye"; + public class NonIntegerIndexer : NotifyingBase, INonIntegerIndexerDerived { private readonly Dictionary _storage = new Dictionary(); @@ -1534,4 +1746,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } public class CustomDataTemplateInherit : CustomDataTemplate { } + + public class AssignBindingControl : Control + { + [AssignBinding] public IBinding X { get; set; } + } }