Browse Source

Merge branch 'fixes/textProcessingFixes' of https://github.com/Gillibald/Avalonia into fixes/textProcessingFixes

pull/8471/head
Benedikt Stebner 4 years ago
parent
commit
05145fb4c1
  1. 1
      Avalonia.sln
  2. 6
      native/Avalonia.Native/src/OSX/AvnView.mm
  3. 6
      native/Avalonia.Native/src/OSX/rendertarget.mm
  4. 27
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  5. 7
      src/Avalonia.Base/Controls/IPseudoClasses.cs
  6. 8
      src/Avalonia.Base/Layout/LayoutManager.cs
  7. 2
      src/Avalonia.Base/Media/GlyphRun.cs
  8. 6
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  9. 27
      src/Avalonia.Controls/Converters/StringFormatConverter.cs
  10. 3
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  11. 44
      src/Avalonia.Controls/ProgressBar.cs
  12. 5
      src/Avalonia.Controls/TrayIcon.cs
  13. 4
      src/Avalonia.Controls/Window.cs
  14. 4
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  15. 20
      src/Avalonia.FreeDesktop/DBusCallQueue.cs
  16. 4
      src/Avalonia.FreeDesktop/DBusHelper.cs
  17. 46
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  18. 28
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs
  19. 31
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs
  20. 38
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  21. 38
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs
  22. 30
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  23. 2
      src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs
  24. 16
      src/Avalonia.FreeDesktop/DBusMenu.cs
  25. 36
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  26. 2
      src/Avalonia.FreeDesktop/DBusRequest.cs
  27. 2
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs
  28. 1
      src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj
  29. 21
      src/Avalonia.Themes.Default/Controls/ProgressBar.xaml
  30. 16
      src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml
  31. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  32. 0
      src/Shared/IsExternalInit.cs
  33. 24
      tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs
  34. 44
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  35. 147
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  36. 27
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  37. 14
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

1
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

6
native/Avalonia.Native/src/OSX/AvnView.mm

@ -127,7 +127,11 @@
[self updateRenderTarget];
auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
if(_parent->IsShown())
{
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
}
}
}

6
native/Avalonia.Native/src/OSX/rendertarget.mm

@ -13,6 +13,7 @@
{
@public IOSurfaceRef surface;
@public AvnPixelSize size;
@public bool hasContent;
@public float scale;
ComPtr<IAvnGlContext> _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;
}

27
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -1,22 +1,37 @@
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel>
<StackPanel Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Maximum</TextBlock>
<NumericUpDown x:Name="maximum" Value="100" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Minimum</TextBlock>
<NumericUpDown x:Name="minimum" Value="0" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Progress Text Format</TextBlock>
<TextBox x:Name="stringFormat" Text="{}{0:0}%" VerticalAlignment="Center"/>
</StackPanel>
<CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" />
<CheckBox x:Name="isIndeterminate" Margin="10,16,0,0" Content="Toggle Indeterminate" />
<StackPanel Orientation="Horizontal" Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16">
<StackPanel Spacing="16">
<ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
<ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}"
Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" ProgressTextFormat="{Binding #stringFormat.Text}"/>
</StackPanel>
<ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
<ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical"
Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" ProgressTextFormat="{Binding #stringFormat.Text}"/>
</StackPanel>
<StackPanel Margin="16">
<Slider Name="hprogress" Maximum="100" Value="40" />
<Slider Name="vprogress" Maximum="100" Value="60" />
<Slider Name="hprogress" Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" Value="40" />
<Slider Name="vprogress" Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" Value="60" />
</StackPanel>
<StackPanel Spacing="10">
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True"
Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.value}"/>
<ProgressBar VerticalAlignment="Center" Value="5" Maximum="10" />
<ProgressBar VerticalAlignment="Center" Value="50" />
<ProgressBar VerticalAlignment="Center" Value="50" Minimum="25" Maximum="75" />

7
src/Avalonia.Base/Controls/IPseudoClasses.cs

@ -19,5 +19,12 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="name">The pseudoclass name.</param>
bool Remove(string name);
/// <summary>
/// Returns whether a pseudoclass is present in the collection.
/// </summary>
/// <param name="name">The pseudoclass name.</param>
/// <returns>Whether the pseudoclass is present.</returns>
bool Contains(string name);
}
}

8
src/Avalonia.Base/Layout/LayoutManager.cs

@ -350,7 +350,7 @@ namespace Avalonia.Layout
{
for (var i = 0; i < count; ++i)
{
var l = _effectiveViewportChangedListeners[i];
var l = listeners[i];
if (!l.Listener.IsAttachedToVisualTree)
{
@ -362,7 +362,7 @@ namespace Avalonia.Layout
if (viewport != l.Viewport)
{
l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport));
_effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport);
l.Viewport = viewport;
}
}
}
@ -414,7 +414,7 @@ namespace Avalonia.Layout
}
}
private readonly struct EffectiveViewportChangedListener
private class EffectiveViewportChangedListener
{
public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport)
{
@ -423,7 +423,7 @@ namespace Avalonia.Layout
}
public ILayoutable Listener { get; }
public Rect Viewport { get; }
public Rect Viewport { get; set; }
}
}
}

2
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
{

6
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -7,7 +7,7 @@
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z</StreamGeometry>
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
@ -499,12 +499,12 @@
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconClosedPath}" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton:checked /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconClosedPath}" />
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
<Setter Property="Stretch" Value="UniformToFill" />
</Style>

27
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;
/// <summary>
/// Calls <see cref="string.Format(string, object[])"/> 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.
/// </summary>
public class StringFormatConverter : IMultiValueConverter
{
public object? Convert(IList<object?> 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);
}
}
}

3
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -387,6 +387,7 @@ namespace Avalonia.Controls.Primitives
/// Sets the TemplatedParent property for the created template children.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="templatedParent">The templated parent to apply.</param>
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);
}

44
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<bool> ShowProgressTextProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(ShowProgressText));
public static readonly StyledProperty<string> ProgressTextFormatProperty =
AvaloniaProperty.Register<ProgressBar, string>(nameof(ProgressTextFormat), "{1:0}%");
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
public static readonly DirectProperty<ProgressBar, double> PercentageProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(Percentage),
o => o.Percentage);
[Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>(
@ -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);
@ -174,7 +195,7 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
UpdateIndicator(finalSize);
UpdateIndicator();
return base.ArrangeOverride(finalSize);
}
@ -197,18 +218,21 @@ namespace Avalonia.Controls
{
_indicator = e.NameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size);
UpdateIndicator();
}
private void UpdateIndicator(Size bounds)
private void UpdateIndicator()
{
// Gets the size of the parent indicator container
var barSize = _indicator?.Parent?.Bounds.Size ?? Bounds.Size;
if (_indicator != null)
{
if (IsIndeterminate)
{
// Pulled from ModernWPF.
var dim = Orientation == Orientation.Horizontal ? bounds.Width : bounds.Height;
var dim = Orientation == Orientation.Horizontal ? barSize.Width : barSize.Height;
var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
@ -233,8 +257,8 @@ namespace Avalonia.Controls
new Rect(
padding.Left,
padding.Top,
bounds.Width - (padding.Right + padding.Left),
bounds.Height - (padding.Bottom + padding.Top)
barSize.Width - (padding.Right + padding.Left),
barSize.Height - (padding.Bottom + padding.Top)
));
}
else
@ -242,16 +266,18 @@ namespace Avalonia.Controls
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum);
if (Orientation == Orientation.Horizontal)
_indicator.Width = bounds.Width * percent;
_indicator.Width = barSize.Width * percent;
else
_indicator.Height = bounds.Height * percent;
_indicator.Height = barSize.Height * percent;
Percentage = percent * 100;
}
}
}
private void UpdateIndicatorWhenPropChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateIndicator(Bounds.Size);
UpdateIndicator();
}
private void UpdatePseudoClasses(

5
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)

4
src/Avalonia.Controls/Window.cs

@ -681,8 +681,8 @@ namespace Avalonia.Controls
IsVisible = true;
var initialSize = new Size(
double.IsNaN(Width) ? ClientSize.Width : Width,
double.IsNaN(Height) ? ClientSize.Height : Height);
double.IsNaN(Width) ? Math.Max(MinWidth, ClientSize.Width) : Width,
double.IsNaN(Height) ? Math.Max(MinHeight, ClientSize.Height) : Height);
if (initialSize != ClientSize)
{

4
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -5,6 +5,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />

20
src/Avalonia.FreeDesktop/DBusCallQueue.cs

@ -8,10 +8,9 @@ namespace Avalonia.FreeDesktop
{
private readonly Func<Exception, Task> _errorHandler;
class Item
record Item(Func<Task> Callback)
{
public Func<Task> Callback;
public Action<Exception> OnFinish;
public Action<Exception?>? OnFinish;
}
private Queue<Item> _q = new Queue<Item>();
private bool _processing;
@ -23,19 +22,15 @@ namespace Avalonia.FreeDesktop
public void Enqueue(Func<Task> cb)
{
_q.Enqueue(new Item
{
Callback = cb
});
_q.Enqueue(new Item(cb));
Process();
}
public Task EnqueueAsync(Func<Task> cb)
{
var tcs = new TaskCompletionSource<int>(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<T> EnqueueAsync<T>(Func<Task<T>> cb)
{
var tcs = new TaskCompletionSource<T>(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)

4
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)
{

46
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<IDisposable> _disposables = new List<IDisposable>();
@ -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<bool> 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<Task> 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<string> _onCommit;
private Action<string>? _onCommit;
event Action<string> IX11InputMethodControl.Commit
{
add => _onCommit += value;
@ -234,7 +236,7 @@ namespace Avalonia.FreeDesktop.DBusIme
protected void FireCommit(string s) => _onCommit?.Invoke(s);
private Action<X11InputMethodForwardedKey> _onForward;
private Action<X11InputMethodForwardedKey>? _onForward;
event Action<X11InputMethodForwardedKey> IX11InputMethodControl.ForwardKey
{
add => _onForward += value;

28
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<int> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time);
Task<IDisposable> WatchEnableIMAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCloseIMAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCommitStringAsync(Action<string> handler, Action<Exception> onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchEnableIMAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCloseIMAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable?> WatchCommitStringAsync(Action<string> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable?> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception>? 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<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time);
Task<IDisposable> WatchCommitStringAsync(Action<string> handler, Action<Exception> onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception> onError = null);
Task<IDisposable?> WatchCommitStringAsync(Action<string> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable?> WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception>? onError = null);
}
[DBusInterface("org.fcitx.Fcitx.InputMethod1")]

31
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<bool> 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<IDisposable> WatchCommitStringAsync(Action<string> handler) =>
_old?.WatchCommitStringAsync(handler) ?? _modern.WatchCommitStringAsync(handler);
public Task<IDisposable?> WatchCommitStringAsync(Action<string> handler) =>
_old?.WatchCommitStringAsync(handler)
?? _modern?.WatchCommitStringAsync(handler)
?? Task.FromResult(default(IDisposable?));
public Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler)
public Task<IDisposable?> 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;
}
}

38
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<bool> 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) =>

38
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs

@ -22,25 +22,25 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
Task<object> GetEngineAsync();
Task DestroyAsync();
Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos);
Task<IDisposable> WatchCommitTextAsync(Action<object> cb, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchRequireSurroundingTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowPreeditTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHidePreeditTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowAuxiliaryTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHideAuxiliaryTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHideLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchPageUpLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchPageDownLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCursorUpLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCursorDownLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchRegisterPropertiesAsync(Action<object> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePropertyAsync(Action<object> handler, Action<Exception> onError = null);
Task<IDisposable> WatchCommitTextAsync(Action<object> cb, Action<Exception>? onError = null);
Task<IDisposable> WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchRequireSurroundingTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchShowPreeditTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchHidePreeditTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchShowAuxiliaryTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchHideAuxiliaryTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchShowLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchHideLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchPageUpLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchPageDownLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCursorUpLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCursorDownLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchRegisterPropertiesAsync(Action<object> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdatePropertyAsync(Action<object> handler, Action<Exception>? onError = null);
}

30
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<bool> 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)

2
src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs

@ -17,7 +17,7 @@ namespace Avalonia.FreeDesktop.DBusIme
new DBusInputMethodFactory<IBusX11TextInputMethod>(_ => new IBusX11TextInputMethod(conn))
};
static Func<Connection, IX11InputMethodFactory> DetectInputMethod()
static Func<Connection, IX11InputMethodFactory>? DetectInputMethod()
{
foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" })
{

16
src/Avalonia.FreeDesktop/DBusMenu.cs

@ -28,18 +28,18 @@ namespace Avalonia.FreeDesktop.DBusMenu
Task<int[]> EventGroupAsync((int id, string eventId, object data, uint timestamp)[] events);
Task<bool> AboutToShowAsync(int Id);
Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids);
Task<IDisposable> WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception>? 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<IDisposable> WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchWindowUnregisteredAsync(Action<uint> handler, Action<Exception> onError = null);
Task<IDisposable> WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchWindowUnregisteredAsync(Action<uint> handler, Action<Exception>? onError = null);
}
}

36
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<int, NativeMenuItemBase> _idsToItems = new Dictionary<int, NativeMenuItemBase>();
private readonly Dictionary<NativeMenuItemBase, int> _itemsToIds = new Dictionary<NativeMenuItemBase, int>();
private readonly HashSet<NativeMenu> _menus = new HashSet<NativeMenu>();
@ -73,10 +73,10 @@ namespace Avalonia.FreeDesktop
if (_appMenu)
{
await _dbus.RegisterObjectAsync(this);
_registrar = DBusHelper.Connection.CreateProxy<IRegistrar>(
_registrar = DBusHelper.Connection?.CreateProxy<IRegistrar>(
"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<KeyValuePair<string, object>> _reusablePropertyList = new List<KeyValuePair<string, object>>();
KeyValuePair<string, object>[] GetProperties((NativeMenuItemBase item, NativeMenu menu) i, string[] names)
KeyValuePair<string, object>[] 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<string, object>[], object[]) GetLayout(NativeMenuItemBase item, NativeMenu menu, int depth, string[] propertyNames)
(int, KeyValuePair<string, object>[], 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<string, object>)[] 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<PropertyChanges> PropertiesChanged { add { } remove { } }
async Task<IDisposable> IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception> onError)
async Task<IDisposable> IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception>? onError)
{
ItemsPropertiesUpdated += handler;
return Disposable.Create(() => ItemsPropertiesUpdated -= handler);
}
async Task<IDisposable> IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception> onError)
async Task<IDisposable> IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception>? onError)
{
LayoutUpdated += handler;
return Disposable.Create(() => LayoutUpdated -= handler);
}
async Task<IDisposable> IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception> onError)
async Task<IDisposable> IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception>? onError)
{
ItemActivationRequested+= handler;
return Disposable.Create(() => ItemActivationRequested -= handler);

2
src/Avalonia.FreeDesktop/DBusRequest.cs

@ -11,6 +11,6 @@ namespace Avalonia.FreeDesktop
internal interface IRequest : IDBusObject
{
Task CloseAsync();
Task<IDisposable> WatchResponseAsync(Action<(uint response, IDictionary<string, object> results)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchResponseAsync(Action<(uint response, IDictionary<string, object> results)> handler, Action<Exception>? onError = null);
}
}

2
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs

@ -10,7 +10,7 @@ namespace Avalonia.FreeDesktop
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{
Contract.Requires<ArgumentNullException>(mountedDrives != null);
return new LinuxMountedVolumeInfoListener(ref mountedDrives);
return new LinuxMountedVolumeInfoListener(ref mountedDrives!);
}
}
}

1
src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj

@ -11,6 +11,7 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" />
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
<Compile Include="..\Shared\SourceGeneratorAttributes.cs" />
</ItemGroup>

21
src/Avalonia.Themes.Default/Controls/ProgressBar.xaml

@ -1,4 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="10">
@ -11,10 +13,13 @@
</Border>
</Design.PreviewWith>
<Style Selector="ProgressBar">
<Style.Resources>
<converters:StringFormatConverter x:Key="StringFormatConverter"/>
</Style.Resources>
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="Template">
<ControlTemplate>
<ControlTemplate TargetType="ProgressBar">
<Grid>
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
@ -23,7 +28,17 @@
</Panel>
</Border>
<LayoutTransformControl HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{Binding ShowProgressText, RelativeSource={RelativeSource TemplatedParent}}" Name="PART_LayoutTransformControl">
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}" Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
<TextBlock Foreground="{DynamicResource ThemeForegroundBrush}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<TemplateBinding Property="ProgressTextFormat"/>
<Binding Path="Value" RelativeSource="{RelativeSource TemplatedParent}"/>
<TemplateBinding Property="Percentage"/>
<TemplateBinding Property="Minimum"/>
<TemplateBinding Property="Maximum"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</LayoutTransformControl>
</Grid>
</ControlTemplate>

16
src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml

@ -1,5 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
@ -13,6 +14,9 @@
</Border>
</Design.PreviewWith>
<Style Selector="ProgressBar">
<Style.Resources>
<converters:StringFormatConverter x:Key="StringFormatConverter"/>
</Style.Resources>
<Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ProgressBarBorderThemeThickness}" />
@ -32,7 +36,17 @@
<Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
</Panel>
<LayoutTransformControl x:Name="PART_LayoutTransformControl" HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{TemplateBinding ShowProgressText}">
<TextBlock Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
<TextBlock Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<TemplateBinding Property="ProgressTextFormat"/>
<Binding Path="Value" RelativeSource="{RelativeSource TemplatedParent}"/>
<TemplateBinding Property="Percentage"/>
<TemplateBinding Property="Minimum"/>
<TemplateBinding Property="Maximum"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</LayoutTransformControl>
</Panel>
</Border>

2
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);

0
src/Avalonia.SourceGenerator/IsExternalInit.cs → src/Shared/IsExternalInit.cs

24
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)

44
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));

147
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<PopupItemsControl>(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<IControl>()
.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<PopupItemsControl>(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;

27
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -540,6 +540,33 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(window.Position, expectedPosition);
}
}
[Fact]
public void Window_Should_Be_Sized_To_MinSize_If_InitialSize_Less_Than_MinSize()
{
var screen1 = new Mock<Screen>(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true);
var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object });
screens.Setup(x => x.ScreenFromPoint(It.IsAny<PixelPoint>())).Returns(screen1.Object);
var windowImpl = MockWindowingPlatform.CreateWindowMock(400, 300);
windowImpl.Setup(x => x.DesktopScaling).Returns(1.75);
windowImpl.Setup(x => x.RenderScaling).Returns(1.75);
windowImpl.Setup(x => x.Screen).Returns(screens.Object);
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window(windowImpl.Object);
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
window.MinWidth = 720;
window.MinHeight = 480;
window.Show();
Assert.Equal(new PixelPoint(330, 63), window.Position);
Assert.Equal(new Size(720, 480), window.Bounds.Size);
}
}
[Fact]
public void Window_Should_Be_Centered_Relative_To_Owner_When_WindowStartupLocation_Is_CenterOwner()

14
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -21,11 +21,11 @@ namespace Avalonia.UnitTests
_popupImpl = popupImpl;
}
public static Mock<IWindowImpl> CreateWindowMock()
public static Mock<IWindowImpl> CreateWindowMock(double initialWidth = 800, double initialHeight = 600)
{
var windowImpl = new Mock<IWindowImpl>();
var position = new PixelPoint();
var clientSize = new Size(800, 600);
var clientSize = new Size(initialWidth, initialHeight);
windowImpl.SetupAllProperties();
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
@ -55,12 +55,18 @@ namespace Avalonia.UnitTests
windowImpl.Setup(x => x.Resize(It.IsAny<Size>(), It.IsAny<PlatformResizeReason>()))
.Callback<Size, PlatformResizeReason>((x, y) =>
{
clientSize = x.Constrain(s_screenSize);
windowImpl.Object.Resized?.Invoke(clientSize, y);
var constrainedSize = x.Constrain(s_screenSize);
if (constrainedSize != clientSize)
{
clientSize = constrainedSize;
windowImpl.Object.Resized?.Invoke(clientSize, y);
}
});
windowImpl.Setup(x => x.Show(true, It.IsAny<bool>())).Callback(() =>
{
windowImpl.Object.Resized?.Invoke(windowImpl.Object.ClientSize, PlatformResizeReason.Unspecified);
windowImpl.Object.Activated?.Invoke();
});

Loading…
Cancel
Save