Browse Source

Merge branch 'master' into fix-mac-special-character-input

pull/21569/head
AnastassiaP 4 months ago
committed by GitHub
parent
commit
be7f863214
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      packages/Avalonia/AvaloniaBuildTasks.targets
  2. 8
      src/Android/Avalonia.Android/AvaloniaView.Input.cs
  3. 4
      src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
  4. 34
      src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs
  5. 4
      src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs
  6. 13
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  7. 2
      src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml
  8. 2
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  9. 11
      src/tools/Avalonia.Generators/Avalonia.Generators.props
  10. 78
      tests/Avalonia.Controls.UnitTests/Automation/ComboBoxAutomationPeerTests.cs
  11. 106
      tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

11
packages/Avalonia/AvaloniaBuildTasks.targets

@ -39,6 +39,17 @@
Condition="'$(ApplicationIcon)' != ''" />
</ItemGroup>
<!-- Include AvaloniaXaml and AvaloniaResource as a Roslyn-visible additional files -->
<!-- This is necessary to use these files in source generators and other Roslyn-based extension -->
<Target Name="_InjectAvaloniaAdditionalFiles" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun">
<ItemGroup>
<AdditionalFiles Include="@(AvaloniaXaml)" SourceItemGroup="AvaloniaXaml" />
<_AvaloniaResourceOnly Include="@(AvaloniaResource)"
Exclude="@(AvaloniaXaml)" />
<AdditionalFiles Include="@(_AvaloniaResourceOnly)" SourceItemGroup="AvaloniaResource" />
</ItemGroup>
</Target>
<UsingTask TaskName="GenerateAvaloniaResourcesTask"
AssemblyFile="$(AvaloniaBuildTasksLocation)"
/>

8
src/Android/Avalonia.Android/AvaloniaView.Input.cs

@ -27,7 +27,13 @@ namespace Avalonia.Android
protected override bool DispatchHoverEvent(MotionEvent? e)
{
return _accessHelper.DispatchHoverEvent(e!) || base.DispatchHoverEvent(e);
var res = _view.PointerHelper.DispatchMotionEvent(e, out var callBase);
if (res == false)
callBase = !_accessHelper.DispatchHoverEvent(e!) && callBase;
var baseResult = callBase && base.DispatchHoverEvent(e);
return res ?? baseResult;
}
protected override bool DispatchGenericPointerEvent(MotionEvent? e)

4
src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs

@ -67,6 +67,10 @@ namespace Avalonia.Media.Fonts
//Add to cache with platform typeface family name first
TryAddGlyphTypeface(platformTypeface.FamilyName, key, glyphTypeface);
// Then the requested family name
if (familyName != platformTypeface.FamilyName)
TryAddGlyphTypeface(familyName, key, glyphTypeface);
//Add to cache
if (!TryAddGlyphTypeface(glyphTypeface))

34
src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs

@ -22,19 +22,28 @@ namespace Avalonia.Automation.Peers
public bool ShowsMenu => true;
public void Collapse() => Owner.IsDropDownOpen = false;
public void Expand() => Owner.IsDropDownOpen = true;
bool IValueProvider.IsReadOnly => true;
bool IValueProvider.IsReadOnly => !Owner.IsEditable;
string? IValueProvider.Value
{
get
{
if (Owner.IsEditable)
return Owner.Text;
var selection = GetSelection();
return selection.Count == 1 ? selection[0].GetName() : null;
}
}
void IValueProvider.SetValue(string? value) => throw new NotSupportedException();
void IValueProvider.SetValue(string? value)
{
if (!Owner.IsEditable)
throw new InvalidOperationException("Cannot set the value of a non-editable ComboBox.");
Owner.SetCurrentValue(ComboBox.TextProperty, value);
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.ComboBox;
@ -69,6 +78,23 @@ namespace Avalonia.Automation.Peers
ToState((bool)e.OldValue!),
ToState((bool)e.NewValue!));
}
else if (e.Property == ComboBox.TextProperty && Owner.IsEditable)
{
RaisePropertyChangedEvent(
ValuePatternIdentifiers.ValueProperty,
e.OldValue as string,
e.NewValue as string);
}
else if (e.Property == ComboBox.IsEditableProperty)
{
RaisePropertyChangedEvent(
ValuePatternIdentifiers.IsReadOnlyProperty,
!(bool)e.OldValue!,
!(bool)e.NewValue!);
// The Value pattern switches between SelectedItem name and Text when IsEditable changes.
RaisePropertyChangedEvent(ValuePatternIdentifiers.ValueProperty, null, null);
}
}
private static ExpandCollapseState ToState(bool value)

4
src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs

@ -150,6 +150,10 @@ namespace Avalonia.FreeDesktop.AtSpi.Handlers
if (!string.IsNullOrEmpty(accessKey))
attrs["access-key"] = accessKey;
var placeholderText = node.Peer.GetPlaceholderText();
if (!string.IsNullOrEmpty(placeholderText))
attrs["placeholder-text"] = placeholderText;
return ValueTask.FromResult(attrs);
}

13
src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs

@ -64,9 +64,9 @@ namespace Avalonia.FreeDesktop.DBusIme
_watchCts = new CancellationTokenSource();
var cancellationToken = _watchCts.Token;
foreach (var name in _knownNames)
WatchNameAsync(name);
_ = WatchNameAsync(name);
async void WatchNameAsync(string name)
async Task WatchNameAsync(string name)
{
try
{
@ -99,10 +99,13 @@ namespace Avalonia.FreeDesktop.DBusIme
OnOwnerChanged(name, owner);
}
}
catch (Exception e) when (!cancellationToken.IsCancellationRequested)
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)
?.Log(this, $"WatchNameOwner for '{name}' failed: {e}");
if (!cancellationToken.IsCancellationRequested)
{
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)
?.Log(this, $"WatchNameOwner for '{name}' failed: {e}");
}
}
}
}

2
src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml

@ -1651,7 +1651,7 @@
<!-- BaseResources for WindowDrawnDecorations.xaml -->
<SolidColorBrush x:Key="CaptionButtonForeground" Color="White" />
<SolidColorBrush x:Key="CaptionButtonBackground" Color="#ffe5e5e5" />
<SolidColorBrush x:Key="CaptionButtonBackground" Color="#ff1d170f" />
<SolidColorBrush x:Key="CaptionButtonBorderBrush" Color="#ffcacaca" />
<StaticResource x:Key="TitleBarBackgroundBrush" ResourceKey="SystemControlBackgroundAltHighBrush" />
</ResourceDictionary>

2
src/Avalonia.Themes.Simple/Accents/Base.xaml

@ -90,7 +90,7 @@
<!-- BaseResources for WindowDrawnDecorations.xaml -->
<SolidColorBrush x:Key="CaptionButtonForeground" Color="White" />
<SolidColorBrush x:Key="CaptionButtonBackground" Color="#ffe5e5e5" />
<SolidColorBrush x:Key="CaptionButtonBackground" Color="#ff1d170f" />
<SolidColorBrush x:Key="CaptionButtonBorderBrush" Color="#ffcacaca" />
<StaticResource x:Key="TitleBarBackgroundBrush" ResourceKey="ThemeBackgroundBrush" />
</ResourceDictionary>

11
src/tools/Avalonia.Generators/Avalonia.Generators.props

@ -9,7 +9,9 @@
<AvaloniaNameGeneratorAttachDevTools Condition="'$(AvaloniaNameGeneratorAttachDevTools)' == ''">true</AvaloniaNameGeneratorAttachDevTools>
</PropertyGroup>
<ItemGroup>
<!-- Mark AvaloniaXaml/AvaloniaResource metadata visible for the Roslyn-compiler -->
<CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceItemGroup"/>
<!-- Mark Avalonia.Generators config properties visible for the Roslyn-compiler -->
<CompilerVisibleProperty Include="AvaloniaNameGeneratorIsEnabled" />
<CompilerVisibleProperty Include="AvaloniaNameGeneratorBehavior" />
<CompilerVisibleProperty Include="AvaloniaNameGeneratorDefaultFieldModifier" />
@ -17,10 +19,9 @@
<CompilerVisibleProperty Include="AvaloniaNameGeneratorFilterByNamespace" />
<CompilerVisibleProperty Include="AvaloniaNameGeneratorViewFileNamingStrategy" />
<CompilerVisibleProperty Include="AvaloniaNameGeneratorAttachDevTools"/>
<!-- Mark additional Avalonia properties that might be useful for the Roslyn-compiler -->
<CompilerVisibleProperty Include="AvaloniaUseCompiledBindingsByDefault" />
<CompilerVisibleProperty Include="AvaloniaMainPackageVersion" />
<CompilerVisibleProperty Include="AvaloniaPreviewerNetCoreToolPath" />
</ItemGroup>
<Target Name="_InjectAdditionalFiles" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun">
<ItemGroup>
<AdditionalFiles Include="@(AvaloniaXaml)" SourceItemGroup="AvaloniaXaml" />
</ItemGroup>
</Target>
</Project>

78
tests/Avalonia.Controls.UnitTests/Automation/ComboBoxAutomationPeerTests.cs

@ -0,0 +1,78 @@
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests.Automation;
public class ComboBoxAutomationPeerTests : ScopedTestBase
{
private static IValueProvider Value(ComboBox comboBox)
=> Assert.IsAssignableFrom<IValueProvider>(ControlAutomationPeer.CreatePeerForElement(comboBox));
[Fact]
public void Non_Editable_ComboBox_Is_ReadOnly()
{
var provider = Value(new ComboBox());
Assert.True(provider.IsReadOnly);
}
[Fact]
public void Editable_ComboBox_Is_Not_ReadOnly()
{
var provider = Value(new ComboBox { IsEditable = true });
Assert.False(provider.IsReadOnly);
}
[Fact]
public void Value_Returns_Text_When_Editable()
{
var provider = Value(new ComboBox { IsEditable = true, Text = "hello" });
Assert.Equal("hello", provider.Value);
}
[Fact]
public void SetValue_Updates_Text_When_Editable()
{
var comboBox = new ComboBox { IsEditable = true };
var provider = Value(comboBox);
provider.SetValue("typed");
Assert.Equal("typed", comboBox.Text);
Assert.Equal("typed", provider.Value);
}
[Fact]
public void SetValue_Throws_When_Not_Editable()
{
var provider = Value(new ComboBox());
Assert.Throws<System.InvalidOperationException>(() => provider.SetValue("x"));
}
[Fact]
public void Text_Change_Raises_Value_PropertyChanged()
{
var comboBox = new ComboBox { IsEditable = true };
var peer = ControlAutomationPeer.CreatePeerForElement(comboBox);
var raised = 0;
peer.PropertyChanged += (_, e) =>
{
if (e.Property == ValuePatternIdentifiers.ValueProperty)
{
Assert.Equal("abc", e.NewValue);
raised++;
}
};
comboBox.Text = "abc";
Assert.Equal(1, raised);
}
}

106
tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using Avalonia.Fonts.Inter;
using Avalonia.Logging;
using Avalonia.Media;
@ -643,5 +646,108 @@ namespace Avalonia.Skia.UnitTests.Media
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("Inter", FontStyle.Normal, (FontWeight)650), out var nearestMatchTypeface));
Assert.Same(boldGlyphTypeface, nearestMatchTypeface);
}
[Fact]
public void TryGetGlyphTypeface_Should_Cache_Matched_GlyphTypeface_Under_Requested_FamilyName()
{
var fontManagerImpl = new FamilyRemappingFontManagerImpl("NotInstalled", "Noto Mono");
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: fontManagerImpl));
// "NotInstalled" is not installed, so the platform substitutes it with a different
// family ("Noto Mono"), much like requesting "Arial" yields "Liberation Sans" on some Linux distributions.
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("NotInstalled"), out var first));
Assert.Equal("Noto Mono", first.FamilyName);
// The substitute should now be cached under the requested "NotInstalled" name, so a second
// lookup must resolve from the cache instead of asking the platform again.
Assert.True(FontManager.Current.TryGetGlyphTypeface(new Typeface("NotInstalled"), out var second));
Assert.Same(first, second);
Assert.Equal(1, fontManagerImpl.RequestedFamilyCreateCount);
}
/// <summary>
/// A font manager whose every by-name lookup resolves to a single matched font whose family name
/// differs from the requested one.
/// </summary>
private sealed class FamilyRemappingFontManagerImpl(string requestedFamilyName, string matchedFamilyName)
: IFontManagerImpl, IDisposable
{
public int RequestedFamilyCreateCount { get; private set; }
public string GetDefaultFontFamilyName()
=> matchedFamilyName;
public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false)
=> [matchedFamilyName];
public bool TryCreateGlyphTypeface(
string familyName,
FontStyle style,
FontWeight weight,
FontStretch stretch,
[NotNullWhen(true)] out IPlatformTypeface? platformTypeface)
{
if (string.Equals(familyName, requestedFamilyName, StringComparison.OrdinalIgnoreCase))
RequestedFamilyCreateCount++;
platformTypeface = new SkiaTypeface(CreateMatchedTypeface(), FontSimulations.None);
return true;
}
public bool TryCreateGlyphTypeface(
Stream stream,
FontSimulations fontSimulations,
[NotNullWhen(true)] out IPlatformTypeface? platformTypeface)
{
platformTypeface = new SkiaTypeface(SKTypeface.FromStream(stream), fontSimulations);
return true;
}
public bool TryMatchCharacter(
int codepoint,
FontStyle fontStyle,
FontWeight fontWeight,
FontStretch fontStretch,
string? familyName,
CultureInfo? culture,
[NotNullWhen(true)] out IPlatformTypeface? platformTypeface)
{
platformTypeface = null;
return false;
}
public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
{
familyTypefaces = null;
return false;
}
private SKTypeface CreateMatchedTypeface()
{
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
// LoadFontAssets ignores the family fragment and returns every embedded font asset,
// so pick the one whose family name matches the substitute we want to return.
foreach (var fontAsset in FontFamilyLoader.LoadFontAssets(new Uri(s_fontUri)))
{
var stream = assetLoader.Open(fontAsset);
var typeface = SKTypeface.FromStream(stream);
if (typeface is not null &&
string.Equals(typeface.FamilyName, matchedFamilyName, StringComparison.OrdinalIgnoreCase))
{
return typeface;
}
typeface?.Dispose();
}
throw new InvalidOperationException($"Could not load the '{matchedFamilyName}' font asset.");
}
public void Dispose()
{
}
}
}
}

Loading…
Cancel
Save