Browse Source

Merge branch 'master' into feature/moveThemeFontAssets

pull/10293/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
b16a3eddde
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  2. 2
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  3. 5
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  4. 29
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  5. 6
      native/Avalonia.Native/src/OSX/WindowImpl.h
  6. 101
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  7. 11
      samples/ControlCatalog.Browser.Blazor/App.razor.cs
  8. 12
      samples/ControlCatalog.Browser.Blazor/Program.cs
  9. 20
      samples/ControlCatalog.Browser/Program.cs
  10. 3
      samples/ControlCatalog.Browser/main.js
  11. 51
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  12. 4
      samples/IntegrationTestApp/MainWindow.axaml
  13. 2
      samples/IntegrationTestApp/MainWindow.axaml.cs
  14. 4
      samples/MobileSandbox/MainView.xaml
  15. 42
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  16. 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  17. 127
      src/Android/Avalonia.Android/InputEditable.cs
  18. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
  19. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  20. 141
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  21. 6
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  22. 6
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  23. 4
      src/Android/Avalonia.Android/PlatformIconLoader.cs
  24. 6
      src/Android/Avalonia.Android/Stubs.cs
  25. 70
      src/Avalonia.Base/AvaloniaObject.cs
  26. 50
      src/Avalonia.Base/AvaloniaProperty.cs
  27. 4
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  28. 4
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  29. 23
      src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
  30. 5
      src/Avalonia.Base/DirectPropertyBase.cs
  31. 23
      src/Avalonia.Base/Input/TextInput/ITextEditable.cs
  32. 11
      src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
  33. 27
      src/Avalonia.Base/Layout/LayoutInformation.cs
  34. 3
      src/Avalonia.Base/Layout/Layoutable.cs
  35. 2
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  36. 6
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  37. 6
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs
  38. 16
      src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs
  39. 12
      src/Avalonia.Base/Platform/Storage/IStorageFile.cs
  40. 6
      src/Avalonia.Base/Platform/Storage/IStorageProvider.cs
  41. 45
      src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
  42. 6
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  43. 36
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  44. 50
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  45. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  46. 12
      src/Avalonia.Base/StyledElement.cs
  47. 46
      src/Avalonia.Base/StyledProperty.cs
  48. 24
      src/Avalonia.Base/Utilities/WeakEvents.cs
  49. 2
      src/Avalonia.Base/Visual.cs
  50. 1
      src/Avalonia.Base/composition-schema.xml
  51. 22
      src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
  52. 7
      src/Avalonia.Controls/ItemsControl.cs
  53. 6
      src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
  54. 33
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  55. 5
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  56. 10
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  57. 39
      src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
  58. 6
      src/Avalonia.Controls/Slider.cs
  59. 117
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  60. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml
  61. 8
      src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
  62. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  63. 2
      src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
  64. 2
      src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
  65. 2
      src/Avalonia.FreeDesktop/DBusHelper.cs
  66. 2
      src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs
  67. 2
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  68. 2
      src/Avalonia.FreeDesktop/DBusSystemDialog.cs
  69. 6
      src/Avalonia.FreeDesktop/IX11InputMethod.cs
  70. 2
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs
  71. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  72. 2
      src/Avalonia.Native/CallbackBase.cs
  73. 2
      src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs
  74. 2
      src/Avalonia.Native/MenuActionCallback.cs
  75. 2
      src/Avalonia.Native/PredicateCallback.cs
  76. 2
      src/Avalonia.Native/ScreenImpl.cs
  77. 6
      src/Avalonia.Native/SystemDialogs.cs
  78. 1
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  79. 5
      src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs
  80. 33
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  81. 6
      src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs
  82. 22
      src/Avalonia.OpenGL/Egl/EglContext.cs
  83. 11
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  84. 18
      src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
  85. 11
      src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
  86. 8
      src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs
  87. 11
      src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
  88. 10
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  89. 5
      src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
  90. 43
      src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
  91. 16
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  92. 21
      src/Avalonia.OpenGL/GlInterface.cs
  93. 3
      src/Avalonia.OpenGL/IGlContext.cs
  94. 3
      src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
  95. 6
      src/Avalonia.OpenGL/OpenGlException.cs
  96. 2
      src/Avalonia.X11/Glx/Glx.cs
  97. 2
      src/Avalonia.X11/Glx/GlxConsts.cs
  98. 6
      src/Avalonia.X11/Glx/GlxContext.cs
  99. 8
      src/Avalonia.X11/Glx/GlxDisplay.cs
  100. 8
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

17
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -223,6 +223,19 @@
}
}
// From chromium:
//
// > The delegate or the window class should implement this method so that
// > -[NSWindow isZoomed] can be then determined by whether or not the current
// > window frame is equal to the zoomed frame.
//
// If we don't implement this, then isZoomed always returns true for a non-
// resizable window ¯\_(ツ)_/¯
- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
defaultFrame:(NSRect)newFrame {
return newFrame;
}
-(BOOL)canBecomeKeyWindow
{
if(_canBecomeKeyWindow)
@ -261,10 +274,6 @@
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
[[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
[[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
[[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
}
-(void)becomeKeyWindow

2
native/Avalonia.Native/src/OSX/PopupImpl.mm

@ -29,7 +29,7 @@ private:
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
virtual NSWindowStyleMask GetStyle() override
virtual NSWindowStyleMask CalculateStyleMask() override
{
return NSWindowStyleMaskBorderless;
}

5
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -105,9 +105,8 @@ BEGIN_INTERFACE_MAP()
virtual void BringToFront ();
protected:
virtual NSWindowStyleMask GetStyle();
void UpdateStyle();
virtual NSWindowStyleMask CalculateStyleMask() = 0;
virtual void UpdateStyle();
private:
void CreateNSWindow (bool isDialog);

29
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl,
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
lastMenu = nullptr;
CreateNSWindow(usePanel);
[Window setContentView:StandardContainer];
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
}
@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() {
return false;
}
NSWindowStyleMask WindowBaseImpl::GetStyle() {
return NSWindowStyleMaskBorderless;
}
void WindowBaseImpl::UpdateStyle() {
[Window setStyleMask:GetStyle()];
[Window setStyleMask:CalculateStyleMask()];
}
void WindowBaseImpl::CleanNSWindow() {
@ -580,21 +572,12 @@ void WindowBaseImpl::CleanNSWindow() {
}
}
void WindowBaseImpl::CreateNSWindow(bool isDialog) {
if (isDialog) {
if (![Window isKindOfClass:[AvnPanel class]]) {
CleanNSWindow();
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
[Window setHidesOnDeactivate:false];
}
void WindowBaseImpl::CreateNSWindow(bool usePanel) {
if (usePanel) {
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
[Window setHidesOnDeactivate:false];
} else {
if (![Window isKindOfClass:[AvnWindow class]]) {
CleanNSWindow();
Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
}
Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
}
}

6
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP()
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
void HideOrShowTrafficLights ();
virtual HRESULT Show (bool activate, bool isDialog) override;
virtual HRESULT SetEnabled (bool enable) override;
@ -100,9 +98,11 @@ BEGIN_INTERFACE_MAP()
bool CanBecomeKeyWindow ();
protected:
virtual NSWindowStyleMask GetStyle() override;
virtual NSWindowStyleMask CalculateStyleMask() override;
void UpdateStyle () override;
private:
void ZOrderChildWindows();
void OnInitialiseNSWindow();
NSString *_lastTitle;
};

101
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
OnInitialiseNSWindow();
}
void WindowImpl::HideOrShowTrafficLights() {
if (Window == nil) {
return;
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
[[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
[[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
[[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
}
void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true];
@ -67,8 +54,6 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
WindowBaseImpl::Show(activate, isDialog);
GetWindowState(&_actualWindowState);
HideOrShowTrafficLights();
return SetWindowState(_lastWindowState);
}
}
@ -134,14 +119,19 @@ void WindowImpl::BringToFront()
}
[Window invalidateShadow];
ZOrderChildWindows();
}
}
void WindowImpl::ZOrderChildWindows()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
auto window = (*iterator)->Window;
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
auto window = (*iterator)->Window;
// #9565: Only bring window to front if it's on the currently active space
if ([window isOnActiveSpace])
(*iterator)->BringToFront();
// #9565: Only bring window to front if it's on the currently active space
if ([window isOnActiveSpace]) {
(*iterator)->BringToFront();
}
}
}
@ -161,13 +151,15 @@ bool WindowImpl::CanBecomeKeyWindow()
void WindowImpl::StartStateTransition() {
_transitioningWindowState = true;
UpdateStyle();
}
void WindowImpl::EndStateTransition() {
_transitioningWindowState = false;
UpdateStyle();
// Ensure correct order of child windows after fullscreen transition.
BringToFront();
ZOrderChildWindows();
}
SystemDecorations WindowImpl::Decorations() {
@ -225,16 +217,12 @@ bool WindowImpl::IsZoomed() {
}
void WindowImpl::DoZoom() {
switch (_decorations) {
case SystemDecorationsNone:
case SystemDecorationsBorderOnly:
[Window setFrame:[Window screen].visibleFrame display:true];
break;
case SystemDecorationsFull:
[Window performZoom:Window];
break;
if (_decorations == SystemDecorationsNone ||
_decorations == SystemDecorationsBorderOnly ||
_canResize == false) {
[Window setFrame:[Window screen].visibleFrame display:true];
} else {
[Window performZoom:Window];
}
}
@ -261,8 +249,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
UpdateStyle();
HideOrShowTrafficLights();
switch (_decorations) {
case SystemDecorationsNone:
[Window setHasShadow:NO];
@ -419,9 +405,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) {
}
[GetWindowProtocol() setIsExtended:enable];
HideOrShowTrafficLights();
UpdateStyle();
}
@ -577,14 +560,16 @@ bool WindowImpl::IsOwned() {
return _parent != nullptr;
}
NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = NSWindowStyleMaskBorderless;
NSWindowStyleMask WindowImpl::CalculateStyleMask() {
// Use the current style mask and only clear the flags we're going to be modifying.
unsigned long s = [Window styleMask] &
~(NSWindowStyleMaskFullSizeContentView |
NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskResizable |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskTexturedBackground);
if(_actualWindowState == FullScreen)
{
s |= NSWindowStyleMaskFullScreen;
}
switch (_decorations) {
case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView;
@ -597,7 +582,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
if (_canResize && _isEnabled) {
if ((_canResize && _isEnabled) || _transitioningWindowState) {
s = s | NSWindowStyleMaskResizable;
}
break;
@ -612,3 +597,25 @@ NSWindowStyleMask WindowImpl::GetStyle() {
}
return s;
}
void WindowImpl::UpdateStyle() {
WindowBaseImpl::UpdateStyle();
if (Window == nil) {
return;
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];
NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton];
[closeButton setHidden:!hasTrafficLights];
[closeButton setEnabled:_isEnabled];
[miniaturizeButton setHidden:!hasTrafficLights];
[miniaturizeButton setEnabled:_isEnabled];
[zoomButton setHidden:!hasTrafficLights];
[zoomButton setEnabled:_isEnabled && _canResize];
}

11
samples/ControlCatalog.Browser.Blazor/App.razor.cs

@ -1,3 +1,5 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser.Blazor;
@ -5,13 +7,4 @@ namespace ControlCatalog.Browser.Blazor;
public partial class App
{
protected override void OnParametersSet()
{
AppBuilder.Configure<ControlCatalog.App>()
.UseBlazor()
// .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
.SetupWithSingleViewLifetime();
base.OnParametersSet();
}
}

12
samples/ControlCatalog.Browser.Blazor/Program.cs

@ -1,6 +1,8 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser.Blazor;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ControlCatalog.Browser.Blazor;
@ -9,9 +11,17 @@ public class Program
{
public static async Task Main(string[] args)
{
await CreateHostBuilder(args).Build().RunAsync();
var host = CreateHostBuilder(args).Build();
await StartAvaloniaApp();
await host.RunAsync();
}
public static async Task StartAvaloniaApp()
{
await AppBuilder.Configure<ControlCatalog.App>()
.StartBlazorAppAsync();
}
public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);

20
samples/ControlCatalog.Browser/Program.cs

@ -1,6 +1,8 @@
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser;
using Avalonia.Controls;
using ControlCatalog;
using ControlCatalog.Browser;
@ -8,15 +10,27 @@ using ControlCatalog.Browser;
internal partial class Program
{
private static void Main(string[] args)
public static async Task Main(string[] args)
{
BuildAvaloniaApp()
await BuildAvaloniaApp()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
}).SetupBrowserApp("out");
})
.StartBrowserAppAsync("out");
}
// Example without a ISingleViewApplicationLifetime
// private static AvaloniaView _avaloniaView;
// public static async Task Main(string[] args)
// {
// await BuildAvaloniaApp()
// .SetupBrowserApp();
//
// _avaloniaView = new AvaloniaView("out");
// _avaloniaView.Content = new TextBlock { Text = "Hello world" };
// }
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>();
}

3
samples/ControlCatalog.Browser/main.js

@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { dotnet } from './dotnet.js'
import { registerAvaloniaModule } from './avalonia.js';
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
@ -12,8 +11,6 @@ const dotnetRuntime = await dotnet
.withApplicationArgumentsFromQuery()
.create();
await registerAvaloniaModule(dotnetRuntime);
const config = dotnetRuntime.getConfig();
await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);

51
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -40,7 +40,7 @@ namespace ControlCatalog.Pages
if (Enum.TryParse<WellKnownFolder>(currentFolderBox.Text, true, out var folderEnum))
{
lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolder(folderEnum);
lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum);
}
else
{
@ -51,7 +51,7 @@ namespace ControlCatalog.Pages
if (folderLink is not null)
{
lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPath(folderLink);
lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink);
}
}
};
@ -82,7 +82,13 @@ namespace ControlCatalog.Pages
return new List<FilePickerFileType>
{
FilePickerFileTypes.All,
FilePickerFileTypes.TextPlain
FilePickerFileTypes.TextPlain,
new("Binary Log")
{
Patterns = new[] { "*.binlog", "*.buildlog" },
MimeTypes = new[] { "application/binlog", "application/buildlog" },
AppleUniformTypeIdentifiers = new []{ "public.data" }
}
};
}
@ -142,7 +148,7 @@ namespace ControlCatalog.Pages
}
else
{
SetFolder(await GetStorageProvider().TryGetFolderFromPath(result));
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
results.Items = new[] { result };
resultsVisible.IsVisible = true;
}
@ -223,7 +229,7 @@ namespace ControlCatalog.Pages
ShowOverwritePrompt = false
});
if (file is not null && file.CanOpenWrite)
if (file is not null)
{
// Sync disposal of StreamWriter is not supported on WASM
#if NET6_0_OR_GREATER
@ -275,7 +281,7 @@ namespace ControlCatalog.Pages
{
ignoreTextChanged = true;
lastSelectedDirectory = folder;
currentFolderBox.Text = folder?.Path.LocalPath;
currentFolderBox.Text = folder?.Path is { IsAbsoluteUri: true } abs ? abs.LocalPath : folder?.Path?.ToString();
ignoreTextChanged = false;
}
async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
@ -298,31 +304,26 @@ namespace ControlCatalog.Pages
if (item is IStorageFile file)
{
resultText += @$"
CanOpenRead: {file.CanOpenRead}
CanOpenWrite: {file.CanOpenWrite}
Content:
";
if (file.CanOpenRead)
{
#if NET6_0_OR_GREATER
await using var stream = await file.OpenReadAsync();
await using var stream = await file.OpenReadAsync();
#else
using var stream = await file.OpenReadAsync();
using var stream = await file.OpenReadAsync();
#endif
using var reader = new System.IO.StreamReader(stream);
using var reader = new System.IO.StreamReader(stream);
// 4GB file test, shouldn't load more than 10000 chars into a memory.
const int length = 10000;
var buffer = ArrayPool<char>.Shared.Rent(length);
try
{
var charsRead = await reader.ReadAsync(buffer, 0, length);
resultText += new string(buffer, 0, charsRead);
}
finally
{
ArrayPool<char>.Shared.Return(buffer);
}
// 4GB file test, shouldn't load more than 10000 chars into a memory.
const int length = 10000;
var buffer = ArrayPool<char>.Shared.Rent(length);
try
{
var charsRead = await reader.ReadAsync(buffer, 0, length);
resultText += new string(buffer, 0, charsRead);
}
finally
{
ArrayPool<char>.Shared.Return(buffer);
}
}

4
samples/IntegrationTestApp/MainWindow.axaml

@ -140,6 +140,7 @@
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>
<Button Name="EnterFullscreen">Enter Fullscreen</Button>
@ -152,6 +153,9 @@
</StackPanel>
</Grid>
</TabItem>
<TabItem Header="SliderTab">
<Slider VerticalAlignment="Top" Name="Slider" Value="30"/>
</TabItem>
</TabControl>
</DockPanel>
</Window>

2
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -66,11 +66,13 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
var canResizeCheckBox = this.GetControl<CheckBox>("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
var window = new ShowWindowTest
{
WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
CanResize = canResizeCheckBox.IsChecked.Value,
};
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)

4
samples/MobileSandbox/MainView.xaml

@ -5,8 +5,8 @@
x:DataType="mobileSandbox:MainView">
<StackPanel Margin="100 50" Spacing="50">
<TextBlock Text="Login" Foreground="White" />
<TextBox Watermark="Text" />
<TextBox Watermark="Username" TextInputOptions.ContentType="Email" AcceptsReturn="True" TextInputOptions.ReturnKeyType="Search" />
<TextBox TextInputOptions.Multiline="True" AcceptsReturn="True" Watermark="Text" Height="200" TextWrapping="Wrap"/>
<TextBox Watermark="Username" TextInputOptions.ContentType="Email" TextInputOptions.ReturnKeyType="Done" />
<TextBox Watermark="Password" PasswordChar="*" TextInputOptions.ContentType="Password" />
<TextBox Watermark="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" />
<Button Content="Login" Command="{Binding ButtonCommand}" />

42
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -5,8 +5,10 @@ using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.Reactive;
namespace Avalonia.Android
{
@ -32,7 +34,7 @@ namespace Avalonia.Android
ActionPrevious = 0x00000007,
}
class AndroidInputMethod<TView> : ITextInputMethodImpl, IAndroidInputMethod
internal class AndroidInputMethod<TView> : ITextInputMethodImpl, IAndroidInputMethod
where TView : View, IInitEditorInfo
{
private readonly TView _host;
@ -68,23 +70,10 @@ namespace Avalonia.Android
public void SetClient(ITextInputMethodClient client)
{
if (_client != null)
{
_client.SurroundingTextChanged -= SurroundingTextChanged;
}
if(_inputConnection != null)
{
_inputConnection.ComposingText = null;
_inputConnection.ComposingRegion = default;
}
_client = client;
if (IsActive)
{
_client.SurroundingTextChanged += SurroundingTextChanged;
_host.RequestFocus();
_imm.RestartInput(View);
@ -101,24 +90,6 @@ namespace Avalonia.Android
}
}
private void SurroundingTextChanged(object sender, EventArgs e)
{
if (IsActive && _inputConnection != null)
{
var surroundingText = Client.SurroundingText;
_inputConnection.SurroundingText = surroundingText;
_imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset)
{
_inputConnection.CommitText(_inputConnection.ComposingText, 0);
_inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
}
}
public void SetCursorRect(Rect rect)
{
@ -157,17 +128,20 @@ namespace Avalonia.Android
TextInputReturnKeyType.Search => (ImeFlags)CustomImeFlags.ActionSearch,
TextInputReturnKeyType.Next => (ImeFlags)CustomImeFlags.ActionNext,
TextInputReturnKeyType.Previous => (ImeFlags)CustomImeFlags.ActionPrevious,
_ => (ImeFlags)CustomImeFlags.ActionDone
TextInputReturnKeyType.Done => (ImeFlags)CustomImeFlags.ActionDone,
_ => options.Multiline ? ImeFlags.NoEnterAction : (ImeFlags)CustomImeFlags.ActionDone
};
outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
_client.TextEditable = _inputConnection.InputEditable;
return _inputConnection;
});
}
}
public readonly record struct ComposingRegion
internal readonly record struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;

1
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -5,6 +5,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<DebugType>portable</DebugType>
<AndroidResgenNamespace>Avalonia.Android.Internal</AndroidResgenNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />

127
src/Android/Avalonia.Android/InputEditable.cs

@ -0,0 +1,127 @@
using System;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Java.Lang;
using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Android
{
internal class InputEditable : SpannableStringBuilder, ITextEditable
{
private readonly TopLevelImpl _topLevel;
private readonly IAndroidInputMethod _inputMethod;
private readonly AvaloniaInputConnection _avaloniaInputConnection;
private int _currentBatchLevel;
private string _previousText;
private int _previousSelectionStart;
private int _previousSelectionEnd;
public event EventHandler TextChanged;
public event EventHandler SelectionChanged;
public event EventHandler CompositionChanged;
public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod, AvaloniaInputConnection avaloniaInputConnection)
{
_topLevel = topLevel;
_inputMethod = inputMethod;
_avaloniaInputConnection = avaloniaInputConnection;
}
public InputEditable(ICharSequence text) : base(text)
{
}
public InputEditable(string text) : base(text)
{
}
public InputEditable(ICharSequence text, int start, int end) : base(text, start, end)
{
}
public InputEditable(string text, int start, int end) : base(text, start, end)
{
}
protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public int SelectionStart
{
get => Selection.GetSelectionStart(this); set
{
var end = SelectionEnd < 0 ? 0 : SelectionEnd;
_avaloniaInputConnection.SetSelection(value, end);
_inputMethod.IMM.UpdateSelection(_topLevel.View, value, end, value, end);
}
}
public int SelectionEnd
{
get => Selection.GetSelectionEnd(this); set
{
var start = SelectionStart < 0 ? 0 : SelectionStart;
_avaloniaInputConnection.SetSelection(start, value);
_inputMethod.IMM.UpdateSelection(_topLevel.View, start, value, start, value);
}
}
public string? Text
{
get => ToString(); set
{
if (Text != value)
{
Clear();
Insert(0, value ?? "");
}
}
}
public int CompositionStart => BaseInputConnection.GetComposingSpanStart(this);
public int CompositionEnd => BaseInputConnection.GetComposingSpanEnd(this);
public void BeginBatchEdit()
{
_currentBatchLevel++;
if (_currentBatchLevel == 1)
{
_previousText = ToString();
_previousSelectionStart = SelectionStart;
_previousSelectionEnd = SelectionEnd;
}
}
public void EndBatchEdit()
{
if (_currentBatchLevel == 1)
{
if(_previousText != Text)
{
TextChanged?.Invoke(this, EventArgs.Empty);
}
if (_previousSelectionStart != SelectionStart || _previousSelectionEnd != SelectionEnd)
{
SelectionChanged?.Invoke(this, EventArgs.Empty);
}
}
_currentBatchLevel--;
}
public void RaiseCompositionChanged()
{
CompositionChanged?.Invoke(this, EventArgs.Empty);
}
}
}

2
src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs

@ -5,7 +5,7 @@ using Avalonia.Input;
namespace Avalonia.Android.Platform.Input
{
public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
internal class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
private static readonly Dictionary<Keycode, Key> KeyDic = new Dictionary<Keycode, Key>
{
// { Keycode.Cancel?, Key.Cancel },

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -10,7 +10,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
internal abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
{
bool _invalidateQueued;
readonly object _lock = new object();

141
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -28,6 +28,7 @@ using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
using Java.Util;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -410,159 +411,73 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
private readonly TopLevelImpl _topLevel;
private readonly IAndroidInputMethod _inputMethod;
private readonly InputEditable _editable;
public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{
_topLevel = topLevel;
_inputMethod = inputMethod;
_editable = new InputEditable(_topLevel, _inputMethod, this);
}
public TextInputMethodSurroundingText SurroundingText { get; set; }
public override IEditable Editable => _editable;
public string ComposingText { get; internal set; }
public ComposingRegion? ComposingRegion { get; internal set; }
public bool IsComposing => !string.IsNullOrEmpty(ComposingText);
public bool IsCommiting { get; private set; }
internal InputEditable InputEditable => _editable;
public override bool SetComposingRegion(int start, int end)
{
//System.Diagnostics.Debug.WriteLine($"Composing Region: [{start}|{end}] {SurroundingText.Text?.Substring(start, end - start)}");
var ret = base.SetComposingRegion(start, end);
ComposingRegion = new ComposingRegion(start, end);
InputEditable.RaiseCompositionChanged();
return base.SetComposingRegion(start, end);
return ret;
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{
var composingText = text.ToString();
ComposingText = composingText;
_inputMethod.Client?.SetPreeditText(ComposingText);
return base.SetComposingText(text, newCursorPosition);
}
public override bool FinishComposingText()
{
if (!string.IsNullOrEmpty(ComposingText))
if (string.IsNullOrEmpty(composingText))
{
CommitText(ComposingText, ComposingText.Length);
return CommitText(text, newCursorPosition);
}
else
{
ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
}
return base.FinishComposingText();
}
public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
{
if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0)
{
var start = System.Math.Max(SurroundingText.CursorOffset - length, 0);
var ret = base.SetComposingText(text, newCursorPosition);
var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset);
InputEditable.RaiseCompositionChanged();
var text = SurroundingText.Text.Substring(start, end - start);
//System.Diagnostics.Debug.WriteLine($"Text Before: {text}");
return new Java.Lang.String(text);
return ret;
}
return null;
}
public override ICharSequence GetTextAfterCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
public override bool BeginBatchEdit()
{
if (!string.IsNullOrEmpty(SurroundingText.Text))
{
var start = SurroundingText.CursorOffset;
var end = System.Math.Min(start + length, SurroundingText.Text.Length);
var text = SurroundingText.Text.Substring(start, end - start);
_editable.BeginBatchEdit();
//System.Diagnostics.Debug.WriteLine($"Text After: {text}");
return new Java.Lang.String(text);
}
return null;
return base.BeginBatchEdit();
}
public override bool CommitText(ICharSequence text, int newCursorPosition)
public override bool EndBatchEdit()
{
IsCommiting = true;
var committedText = text.ToString();
_inputMethod.Client.SetPreeditText(null);
var ret = base.EndBatchEdit();
_editable.EndBatchEdit();
int? start, end;
if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset)
{
start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
}
else if (ComposingRegion != null)
{
start = ComposingRegion?.Start;
end = ComposingRegion?.End;
ComposingRegion = null;
}
else
{
start = end = _inputMethod.Client.SurroundingText.CursorOffset;
}
_inputMethod.Client.SelectInSurroundingText((int)start, (int)end);
var time = DateTime.Now.TimeOfDay;
var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)time.Ticks, _topLevel.InputRoot, committedText);
_topLevel.Input(rawTextEvent);
ComposingText = null;
ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition);
return base.CommitText(text, newCursorPosition);
return ret;
}
public override bool DeleteSurroundingText(int beforeLength, int afterLength)
public override bool FinishComposingText()
{
var surroundingText = _inputMethod.Client.SurroundingText;
var selectionStart = surroundingText.CursorOffset;
_inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength);
_inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
surroundingText = _inputMethod.Client.SurroundingText;
selectionStart = surroundingText.CursorOffset;
ComposingRegion = new ComposingRegion(selectionStart, selectionStart);
return base.DeleteSurroundingText(beforeLength, afterLength);
var ret = base.FinishComposingText();
InputEditable.RaiseCompositionChanged();
return ret;
}
public override bool SetSelection(int start, int end)
public override bool CommitText(ICharSequence text, int newCursorPosition)
{
_inputMethod.Client.SelectInSurroundingText(start, end);
ComposingRegion = new ComposingRegion(start, end);
return base.SetSelection(start, end);
var ret = base.CommitText(text, newCursorPosition);
InputEditable.RaiseCompositionChanged();
return ret;
}
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)

6
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -177,11 +177,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
public AndroidStorageFile(Activity activity, AndroidUri uri) : base(activity, uri, false)
{
}
public bool CanOpenRead => true;
public bool CanOpenWrite => true;
public Task<Stream> OpenReadAsync() => Task.FromResult(OpenContentStream(Activity, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream"));

6
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs

@ -37,7 +37,7 @@ internal class AndroidStorageProvider : IStorageProvider
return Task.FromResult<IStorageBookmarkFolder?>(new AndroidStorageFolder(_activity, uri, false));
}
public async Task<IStorageFile?> TryGetFileFromPath(Uri filePath)
public async Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath)
{
if (filePath is null)
{
@ -70,7 +70,7 @@ internal class AndroidStorageProvider : IStorageProvider
return new AndroidStorageFile(_activity, androidUri);
}
public async Task<IStorageFolder?> TryGetFolderFromPath(Uri folderPath)
public async Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath)
{
if (folderPath is null)
{
@ -103,7 +103,7 @@ internal class AndroidStorageProvider : IStorageProvider
return new AndroidStorageFolder(_activity, androidUri, false);
}
public Task<IStorageFolder?> TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
public Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)
{
var dirCode = wellKnownFolder switch
{

4
src/Android/Avalonia.Android/PlatformIconLoader.cs

@ -3,7 +3,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
class PlatformIconLoader : IPlatformIconLoader
internal class PlatformIconLoader : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@ -29,7 +29,7 @@ namespace Avalonia.Android
}
// Stores the icon created as a stream to support saving even though an icon is never shown
public class FakeIcon : IWindowIconImpl
internal class FakeIcon : IWindowIconImpl
{
private Stream stream = new MemoryStream();

6
src/Android/Avalonia.Android/Stubs.cs

@ -4,7 +4,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
class WindowingPlatformStub : IWindowingPlatform
internal class WindowingPlatformStub : IWindowingPlatform
{
public IWindowImpl CreateWindow() => throw new NotSupportedException();
@ -13,7 +13,7 @@ namespace Avalonia.Android
public ITrayIconImpl CreateTrayIcon() => null;
}
class PlatformIconLoaderStub : IPlatformIconLoader
internal class PlatformIconLoaderStub : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@ -38,7 +38,7 @@ namespace Avalonia.Android
}
}
public class IconStub : IWindowIconImpl
internal class IconStub : IWindowIconImpl
{
private readonly MemoryStream _ms;

70
src/Avalonia.Base/AvaloniaObject.cs

@ -118,7 +118,7 @@ namespace Avalonia
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
_values.ClearLocalValue(property);
_values.ClearValue(property);
}
/// <summary>
@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
_values.ClearLocalValue(property);
_values.ClearValue(property);
}
/// <summary>
@ -329,7 +329,7 @@ namespace Avalonia
if (value is UnsetValueType)
{
if (priority == BindingPriority.LocalValue)
_values.ClearLocalValue(property);
_values.ClearValue(property);
}
else if (value is not DoNothingType)
{
@ -355,6 +355,57 @@ namespace Avalonia
SetDirectValueUnchecked(property, value);
}
/// <summary>
/// Sets the value of a dependency property without changing its value source.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <remarks>
/// This method is used by a component that programmatically sets the value of one of its
/// own properties without disabling an application's declared use of the property. The
/// method changes the effective value of the property, but existing data bindings and
/// styles will continue to work.
///
/// The new value will have the property's current <see cref="BindingPriority"/>, even if
/// that priority is <see cref="BindingPriority.Unset"/> or
/// <see cref="BindingPriority.Inherited"/>.
/// </remarks>
public void SetCurrentValue(AvaloniaProperty property, object? value) =>
property.RouteSetCurrentValue(this, value);
/// <summary>
/// Sets the value of a dependency property without changing its value source.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <remarks>
/// This method is used by a component that programmatically sets the value of one of its
/// own properties without disabling an application's declared use of the property. The
/// method changes the effective value of the property, but existing data bindings and
/// styles will continue to work.
///
/// The new value will have the property's current <see cref="BindingPriority"/>, even if
/// that priority is <see cref="BindingPriority.Unset"/> or
/// <see cref="BindingPriority.Inherited"/>.
/// </remarks>
public void SetCurrentValue<T>(StyledProperty<T> property, T value)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
LogPropertySet(property, value, BindingPriority.LocalValue);
if (value is UnsetValueType)
{
_values.ClearValue(property);
}
else if (value is not DoNothingType)
{
_values.SetCurrentValue(property, value);
}
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
@ -547,7 +598,8 @@ namespace Avalonia
property,
GetValue(property),
BindingPriority.LocalValue,
null);
null,
false);
}
return _values.GetDiagnostic(property);
@ -612,14 +664,12 @@ namespace Avalonia
/// <param name="property">The property that has changed.</param>
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param>
/// <param name="priority">The priority of the binding that produced the value.</param>
protected void RaisePropertyChanged<T>(
DirectPropertyBase<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority = BindingPriority.LocalValue)
T oldValue,
T newValue)
{
RaisePropertyChanged(property, oldValue, newValue, priority, true);
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue, true);
}
/// <summary>
@ -668,7 +718,7 @@ namespace Avalonia
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
protected bool SetAndRaise<T>(DirectPropertyBase<T> property, ref T field, T value)
{
VerifyAccess();

50
src/Avalonia.Base/AvaloniaProperty.cs

@ -225,13 +225,8 @@ namespace Avalonia
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="coerce">A value coercion callback.</param>
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
/// <returns>A <see cref="StyledProperty{TValue}"/></returns>
public static StyledProperty<TValue> Register<TOwner, TValue>(
string name,
@ -239,8 +234,40 @@ namespace Avalonia
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool>? validate = null,
Func<AvaloniaObject, TValue, TValue>? coerce = null,
Action<AvaloniaObject, bool>? notifying = null)
Func<AvaloniaObject, TValue, TValue>? coerce = null)
where TOwner : AvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
var metadata = new StyledPropertyMetadata<TValue>(
defaultValue,
defaultBindingMode: defaultBindingMode,
coerce: coerce);
var result = new StyledProperty<TValue>(
name,
typeof(TOwner),
metadata,
inherits,
validate);
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
/// <inheritdoc cref="Register{TOwner, TValue}" />
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
internal static StyledProperty<TValue> Register<TOwner, TValue>(
string name,
TValue defaultValue,
bool inherits,
BindingMode defaultBindingMode,
Func<TValue, bool>? validate,
Func<AvaloniaObject, TValue, TValue>? coerce,
Action<AvaloniaObject, bool>? notifying)
where TOwner : AvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
@ -496,6 +523,13 @@ namespace Avalonia
object? value,
BindingPriority priority);
/// <summary>
/// Routes an untyped SetCurrentValue call to a typed call.
/// </summary>
/// <param name="o">The object instance.</param>
/// <param name="value">The value.</param>
internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value);
/// <summary>
/// Routes an untyped Bind call to a typed call.
/// </summary>

4
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -22,7 +22,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc)
{
WeakEvents.PropertyChanged.Subscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
}
ValueChanged(GetValue(target));
@ -39,7 +39,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc)
{
WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
}
}
}

4
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -160,7 +160,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
}
private object? GetReferenceTarget()
@ -185,7 +185,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
WeakEvents.PropertyChanged.Subscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
}
}
}

23
src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs

@ -3,28 +3,23 @@ using Avalonia.Data;
namespace Avalonia.Diagnostics
{
/// <summary>
/// Holds diagnostic-related information about the value of a <see cref="AvaloniaProperty"/>
/// on a <see cref="AvaloniaObject"/>.
/// Holds diagnostic-related information about the value of an <see cref="AvaloniaProperty"/>
/// on an <see cref="AvaloniaObject"/>.
/// </summary>
public class AvaloniaPropertyValue
{
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaPropertyValue"/> class.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The current property value.</param>
/// <param name="priority">The priority of the current value.</param>
/// <param name="diagnostic">A diagnostic string.</param>
public AvaloniaPropertyValue(
internal AvaloniaPropertyValue(
AvaloniaProperty property,
object? value,
BindingPriority priority,
string? diagnostic)
string? diagnostic,
bool isOverriddenCurrentValue)
{
Property = property;
Value = value;
Priority = priority;
Diagnostic = diagnostic;
IsOverriddenCurrentValue = isOverriddenCurrentValue;
}
/// <summary>
@ -46,5 +41,11 @@ namespace Avalonia.Diagnostics
/// Gets a diagnostic string.
/// </summary>
public string? Diagnostic { get; }
/// <summary>
/// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to
/// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
/// </summary>
public bool IsOverriddenCurrentValue { get; }
}
}

5
src/Avalonia.Base/DirectPropertyBase.cs

@ -152,6 +152,11 @@ namespace Avalonia
return null;
}
internal override void RouteSetCurrentValue(AvaloniaObject o, object? value)
{
RouteSetValue(o, value, BindingPriority.LocalValue);
}
/// <summary>
/// Routes an untyped Bind call to a typed call.
/// </summary>

23
src/Avalonia.Base/Input/TextInput/ITextEditable.cs

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Metadata;
namespace Avalonia.Input.TextInput
{
[NotClientImplementable]
public interface ITextEditable
{
event EventHandler TextChanged;
event EventHandler SelectionChanged;
event EventHandler CompositionChanged;
int SelectionStart { get; set; }
int SelectionEnd { get; set; }
int CompositionStart { get; }
int CompositionEnd { get; }
string? Text { get; set; }
}
}

11
src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Media.TextFormatting;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
@ -30,6 +31,11 @@ namespace Avalonia.Input.TextInput
/// </summary>
void SetPreeditText(string? text);
/// <summary>
/// Sets the current composing region. This doesn't remove the composing text from the commited text.
/// </summary>
void SetComposingRegion(TextRange? region);
/// <summary>
/// Indicates if text input client is capable of providing the text around the cursor
/// </summary>
@ -43,6 +49,11 @@ namespace Avalonia.Input.TextInput
/// </summary>
event EventHandler? SurroundingTextChanged;
/// <summary>
/// Gets or sets a platform editable. Text and selection changes made in the editable are forwarded to the IM client.
/// </summary>
ITextEditable? TextEditable { get; set; }
void SelectInSurroundingText(int start, int end);
}

27
src/Avalonia.Base/Layout/LayoutInformation.cs

@ -0,0 +1,27 @@
namespace Avalonia.Layout;
/// <summary>
/// Provides access to layout information of a control.
/// </summary>
public static class LayoutInformation
{
/// <summary>
/// Gets the available size constraint passed in the previous layout pass.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>Previous control measure constraint, if any.</returns>
public static Size? GetPreviousMeasureConstraint(Layoutable control)
{
return control.PreviousMeasure;
}
/// <summary>
/// Gets the control bounds used in the previous layout arrange pass.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>Previous control arrange bounds, if any.</returns>
public static Rect? GetPreviousArrangeBounds(Layoutable control)
{
return control.PreviousArrange;
}
}

3
src/Avalonia.Base/Layout/Layoutable.cs

@ -323,6 +323,9 @@ namespace Avalonia.Layout
set { SetValue(UseLayoutRoundingProperty, value); }
}
/// <summary>
/// Gets the available size passed in the previous layout pass, if any.
/// </summary>
internal Size? PreviousMeasure => _previousMeasure;
/// <summary>

2
src/Avalonia.Base/Platform/DefaultPlatformSettings.cs

@ -37,7 +37,7 @@ namespace Avalonia.Platform
};
}
public event EventHandler<PlatformColorValues>? ColorValuesChanged;
public virtual event EventHandler<PlatformColorValues>? ColorValuesChanged;
protected void OnColorValuesChanged(PlatformColorValues colorValues)
{

6
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs

@ -18,11 +18,7 @@ internal class BclStorageFile : IStorageBookmarkFile
}
public FileInfo FileInfo { get; }
public bool CanOpenRead => true;
public bool CanOpenWrite => true;
public string Name => FileInfo.Name;
public virtual bool CanBookmark => true;

6
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs

@ -34,7 +34,7 @@ internal abstract class BclStorageProvider : IStorageProvider
: Task.FromResult<IStorageBookmarkFolder?>(null);
}
public virtual Task<IStorageFile?> TryGetFileFromPath(Uri filePath)
public virtual Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath)
{
if (filePath.IsAbsoluteUri)
{
@ -48,7 +48,7 @@ internal abstract class BclStorageProvider : IStorageProvider
return Task.FromResult<IStorageFile?>(null);
}
public virtual Task<IStorageFolder?> TryGetFolderFromPath(Uri folderPath)
public virtual Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath)
{
if (folderPath.IsAbsoluteUri)
{
@ -62,7 +62,7 @@ internal abstract class BclStorageProvider : IStorageProvider
return Task.FromResult<IStorageFolder?>(null);
}
public virtual Task<IStorageFolder?> TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
public virtual Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)
{
// Note, this BCL API returns different values depending on the .NET version.
// We should also document it.

16
src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Avalonia.Platform.Storage;
@ -21,7 +23,7 @@ public sealed class FilePickerFileType
/// List of extensions in GLOB format. I.e. "*.png" or "*.*".
/// </summary>
/// <remarks>
/// Used on Windows and Linux systems.
/// Used on Windows, Linux and Browser platforms.
/// </remarks>
public IReadOnlyList<string>? Patterns { get; set; }
@ -29,7 +31,7 @@ public sealed class FilePickerFileType
/// List of extensions in MIME format.
/// </summary>
/// <remarks>
/// Used on Android, Browser and Linux systems.
/// Used on Android, Linux and Browser platforms.
/// </remarks>
public IReadOnlyList<string>? MimeTypes { get; set; }
@ -41,4 +43,14 @@ public sealed class FilePickerFileType
/// See https://developer.apple.com/documentation/uniformtypeidentifiers/system_declared_uniform_type_identifiers.
/// </remarks>
public IReadOnlyList<string>? AppleUniformTypeIdentifiers { get; set; }
internal IReadOnlyList<string>? TryGetExtensions()
{
// Converts random glob pattern to a simple extension name.
// GetExtension should be sufficient here.
// Only exception is "*.*proj" patterns that should be filtered as well.
return Patterns?.Select(Path.GetExtension)
.Where(e => !string.IsNullOrEmpty(e) && !e.Contains('*') && e.StartsWith("."))
.ToArray()!;
}
}

12
src/Avalonia.Base/Platform/Storage/IStorageFile.cs

@ -10,22 +10,12 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable]
public interface IStorageFile : IStorageItem
{
/// <summary>
/// Returns true, if file is readable.
/// </summary>
bool CanOpenRead { get; }
/// <summary>
/// Opens a stream for read access.
/// </summary>
/// <exception cref="System.UnauthorizedAccessException" />
Task<Stream> OpenReadAsync();
/// <summary>
/// Returns true, if file is writeable.
/// </summary>
bool CanOpenWrite { get; }
/// <summary>
/// Opens stream for writing to the file.
/// </summary>

6
src/Avalonia.Base/Platform/Storage/IStorageProvider.cs

@ -66,7 +66,7 @@ public interface IStorageProvider
/// It also might ask user for the permission, and throw an exception if it was denied.
/// </remarks>
/// <returns>File or null if it doesn't exist.</returns>
Task<IStorageFile?> TryGetFileFromPath(Uri filePath);
Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath);
/// <summary>
/// Attempts to read folder from the file-system by its path.
@ -78,12 +78,12 @@ public interface IStorageProvider
/// It also might ask user for the permission, and throw an exception if it was denied.
/// </remarks>
/// <returns>Folder or null if it doesn't exist.</returns>
Task<IStorageFolder?> TryGetFolderFromPath(Uri folderPath);
Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath);
/// <summary>
/// Attempts to read folder from the file-system by its path
/// </summary>
/// <param name="wellKnownFolder">Well known folder identifier.</param>
/// <returns>Folder or null if it doesn't exist.</returns>
Task<IStorageFolder?> TryGetWellKnownFolder(WellKnownFolder wellKnownFolder);
Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder);
}

45
src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs

@ -8,48 +8,47 @@ namespace Avalonia.Platform.Storage;
/// </summary>
public static class StorageProviderExtensions
{
/// <inheritdoc cref="IStorageProvider.TryGetFileFromPath"/>
public static Task<IStorageFile?> TryGetFileFromPath(this IStorageProvider provider, string filePath)
/// <inheritdoc cref="IStorageProvider.TryGetFileFromPathAsync"/>
public static Task<IStorageFile?> TryGetFileFromPathAsync(this IStorageProvider provider, string filePath)
{
return provider.TryGetFileFromPath(StorageProviderHelpers.FilePathToUri(filePath));
return provider.TryGetFileFromPathAsync(StorageProviderHelpers.FilePathToUri(filePath));
}
/// <inheritdoc cref="IStorageProvider.TryGetFolderFromPath"/>
public static Task<IStorageFolder?> TryGetFolderFromPath(this IStorageProvider provider, string folderPath)
/// <inheritdoc cref="IStorageProvider.TryGetFolderFromPathAsync"/>
public static Task<IStorageFolder?> TryGetFolderFromPathAsync(this IStorageProvider provider, string folderPath)
{
return provider.TryGetFolderFromPath(StorageProviderHelpers.FilePathToUri(folderPath));
return provider.TryGetFolderFromPathAsync(StorageProviderHelpers.FilePathToUri(folderPath));
}
internal static string? TryGetFullPath(this IStorageFolder folder)
/// <summary>
/// Gets the local file system path of the item as a string.
/// </summary>
/// <param name="item">Storage folder or file.</param>
/// <returns>Full local path to the folder or file if possible, otherwise null.</returns>
/// <remarks>
/// Android platform usually uses "content:" virtual file paths
/// and Browser platform has isolated access without full paths,
/// so on these platforms this method will return null.
/// </remarks>
public static string? TryGetLocalPath(this IStorageItem item)
{
// We can avoid double escaping of the path by checking for BclStorageFolder.
// Ideally, `folder.Path.LocalPath` should also work, as that's only available way for the users.
if (folder is BclStorageFolder storageFolder)
if (item is BclStorageFolder storageFolder)
{
return storageFolder.DirectoryInfo.FullName;
}
if (folder.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
{
return absolutePath.LocalPath;
}
// android "content:", browser and ios relative links go here.
return null;
}
internal static string? TryGetFullPath(this IStorageFile file)
{
if (file is BclStorageFile storageFolder)
if (item is BclStorageFile storageFile)
{
return storageFolder.FileInfo.FullName;
return storageFile.FileInfo.FullName;
}
if (file.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
if (item.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
{
return absolutePath.LocalPath;
}
// android "content:", browser and ios relative links go here.
return null;
}
}

6
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@ -29,6 +29,12 @@ namespace Avalonia.PropertyStore
/// </summary>
public BindingPriority BasePriority { get; protected set; }
/// <summary>
/// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to
/// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
/// </summary>
public bool IsOverridenCurrentValue { get; set; }
/// <summary>
/// Begins a reevaluation pass on the effective value.
/// </summary>

36
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -19,13 +19,16 @@ namespace Avalonia.PropertyStore
private T? _baseValue;
private UncommonFields? _uncommon;
public EffectiveValue(AvaloniaObject owner, StyledProperty<T> property)
public EffectiveValue(
AvaloniaObject owner,
StyledProperty<T> property,
EffectiveValue<T>? inherited)
{
Priority = BindingPriority.Unset;
BasePriority = BindingPriority.Unset;
_metadata = property.GetMetadata(owner.GetType());
var value = _metadata.DefaultValue;
var value = inherited is null ? _metadata.DefaultValue : inherited.Value;
if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
{
@ -57,7 +60,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(priority != BindingPriority.LocalValue);
UpdateValueEntry(value, priority);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority, false);
}
public void SetLocalValueAndRaise(
@ -65,7 +68,16 @@ namespace Avalonia.PropertyStore
StyledProperty<T> property,
T value)
{
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue, false);
}
public void SetCurrentValueAndRaise(
ValueStore owner,
StyledProperty<T> property,
T value)
{
IsOverridenCurrentValue = true;
SetAndRaiseCore(owner, property, value, Priority, true);
}
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
@ -98,7 +110,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(Priority != BindingPriority.Animation);
Debug.Assert(BasePriority != BindingPriority.Unset);
UpdateValueEntry(null, BindingPriority.Animation);
SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority);
SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority, false);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
@ -158,15 +170,16 @@ namespace Avalonia.PropertyStore
ValueStore owner,
StyledProperty<T> property,
T value,
BindingPriority priority)
BindingPriority priority,
bool isOverriddenCurrentValue)
{
Debug.Assert(priority < BindingPriority.Inherited);
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
var v = value;
IsOverridenCurrentValue = isOverriddenCurrentValue;
if (_uncommon?._coerce is { } coerce)
v = coerce(owner.Owner, value);
@ -209,7 +222,6 @@ namespace Avalonia.PropertyStore
T baseValue,
BindingPriority basePriority)
{
Debug.Assert(priority < BindingPriority.Inherited);
Debug.Assert(basePriority > BindingPriority.Animation);
Debug.Assert(priority <= basePriority);
@ -225,7 +237,7 @@ namespace Avalonia.PropertyStore
bv = coerce(owner.Owner, baseValue);
}
if (priority != BindingPriority.Unset && !EqualityComparer<T>.Default.Equals(Value, v))
if (!EqualityComparer<T>.Default.Equals(Value, v))
{
Value = v;
valueChanged = true;
@ -233,9 +245,7 @@ namespace Avalonia.PropertyStore
_uncommon._uncoercedValue = value;
}
if (priority != BindingPriority.Unset &&
(BasePriority == BindingPriority.Unset ||
!EqualityComparer<T>.Default.Equals(_baseValue, bv)))
if (!EqualityComparer<T>.Default.Equals(_baseValue, bv))
{
_baseValue = v;
baseValueChanged = true;

50
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -7,7 +7,6 @@ using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Styling;
using Avalonia.Utilities;
using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@ -156,11 +155,12 @@ namespace Avalonia.PropertyStore
return observer;
}
public void ClearLocalValue(AvaloniaProperty property)
public void ClearValue(AvaloniaProperty property)
{
if (TryGetEffectiveValue(property, out var effective) &&
effective.Priority == BindingPriority.LocalValue)
(effective.Priority == BindingPriority.LocalValue || effective.IsOverridenCurrentValue))
{
effective.IsOverridenCurrentValue = false;
ReevaluateEffectiveValue(property, effective, ignoreLocalValue: true);
}
}
@ -184,7 +184,7 @@ namespace Avalonia.PropertyStore
}
else
{
var effectiveValue = new EffectiveValue<T>(Owner, property);
var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetAndRaise(this, result, priority);
}
@ -200,7 +200,7 @@ namespace Avalonia.PropertyStore
}
else
{
var effectiveValue = new EffectiveValue<T>(Owner, property);
var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetLocalValueAndRaise(this, property, value);
}
@ -209,6 +209,20 @@ namespace Avalonia.PropertyStore
}
}
public void SetCurrentValue<T>(StyledProperty<T> property, T value)
{
if (TryGetEffectiveValue(property, out var v))
{
((EffectiveValue<T>)v).SetCurrentValueAndRaise(this, property, value);
}
else
{
var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetCurrentValueAndRaise(this, property, value);
}
}
public object? GetValue(AvaloniaProperty property)
{
if (_effectiveValues.TryGetValue(property, out var v))
@ -235,12 +249,7 @@ namespace Avalonia.PropertyStore
return false;
}
public bool IsSet(AvaloniaProperty property)
{
if (_effectiveValues.TryGetValue(property, out var v))
return v.Priority < BindingPriority.Inherited;
return false;
}
public bool IsSet(AvaloniaProperty property) => _effectiveValues.TryGetValue(property, out _);
public void CoerceValue(AvaloniaProperty property)
{
@ -278,6 +287,16 @@ namespace Avalonia.PropertyStore
return false;
}
public EffectiveValue<T> CreateEffectiveValue<T>(StyledProperty<T> property)
{
EffectiveValue<T>? inherited = null;
if (property.Inherits && TryGetInheritedValue(property, out var v))
inherited = (EffectiveValue<T>)v;
return new EffectiveValue<T>(Owner, property, inherited);
}
public void SetInheritanceParent(AvaloniaObject? newParent)
{
var values = AvaloniaPropertyDictionaryPool<OldNewValue>.Get();
@ -490,7 +509,7 @@ namespace Avalonia.PropertyStore
if (existing == observer)
{
_localValueBindings?.Remove(property.Id);
ClearLocalValue(property);
ClearValue(property);
}
}
}
@ -616,11 +635,13 @@ namespace Avalonia.PropertyStore
{
object? value;
BindingPriority priority;
bool overridden = false;
if (_effectiveValues.TryGetValue(property, out var v))
{
value = v.Value;
priority = v.Priority;
overridden = v.IsOverridenCurrentValue;
}
else if (property.Inherits && TryGetInheritedValue(property, out v))
{
@ -637,7 +658,8 @@ namespace Avalonia.PropertyStore
property,
value,
priority,
null);
null,
overridden);
}
private int InsertFrame(ValueFrame frame)
@ -787,7 +809,7 @@ namespace Avalonia.PropertyStore
// - The value is a non-animation value and its priority is higher than the current
// effective value's base priority
var isRelevantPriority = current is null ||
priority < current.Priority ||
(priority < current.Priority && priority < current.BasePriority) ||
(priority > BindingPriority.Animation && priority < current.BasePriority);
if (foundEntry && isRelevantPriority && entry!.HasValue)

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -48,7 +48,8 @@ namespace Avalonia.Rendering.Composition.Server
{
canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity;
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
if (AdornerIsClipped)
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
}
var transform = GlobalTransformMatrix;
canvas.PostTransform = MatrixUtils.ToMatrix(transform);
@ -74,7 +75,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopGeometryClip();
if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip();
if (AdornedVisual != null)
if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip();
if(Opacity != 1)
canvas.PopOpacity();

12
src/Avalonia.Base/StyledElement.cs

@ -41,7 +41,11 @@ namespace Avalonia
public static readonly StyledProperty<object?> DataContextProperty =
AvaloniaProperty.Register<StyledElement, object?>(
nameof(DataContext),
defaultValue: null,
inherits: true,
defaultBindingMode: BindingMode.OneWay,
validate: null,
coerce: null,
notifying: DataContextNotifying);
/// <summary>
@ -520,13 +524,7 @@ namespace Avalonia
NotifyResourcesChanged();
}
#nullable disable
RaisePropertyChanged(
ParentProperty,
new Optional<StyledElement>(old),
new BindingValue<StyledElement>(Parent),
BindingPriority.LocalValue);
#nullable enable
RaisePropertyChanged(ParentProperty, old, Parent);
}
}

46
src/Avalonia.Base/StyledProperty.cs

@ -171,7 +171,7 @@ namespace Avalonia
internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
{
return new EffectiveValue<TValue>(o, this);
return o.GetValueStore().CreateEffectiveValue(this);
}
/// <inheritdoc/>
@ -194,24 +194,48 @@ namespace Avalonia
}
/// <inheritdoc/>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
internal override IDisposable? RouteSetValue(
AvaloniaObject target,
object? value,
BindingPriority priority)
{
if (ShouldSetValue(target, value, out var converted))
return target.SetValue<TValue>(this, converted, priority);
return null;
}
internal override void RouteSetCurrentValue(AvaloniaObject target, object? value)
{
if (ShouldSetValue(target, value, out var converted))
target.SetCurrentValue<TValue>(this, converted);
}
internal override IDisposable RouteBind(
AvaloniaObject target,
IObservable<object?> source,
BindingPriority priority)
{
return target.Bind<TValue>(this, source, priority);
}
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
private bool ShouldSetValue(AvaloniaObject target, object? value, [NotNullWhen(true)] out TValue? converted)
{
if (value == BindingOperations.DoNothing)
{
return null;
converted = default;
return false;
}
else if (value == UnsetValue)
if (value == UnsetValue)
{
target.ClearValue(this);
return null;
converted = default;
return false;
}
else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted))
else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var v))
{
return target.SetValue<TValue>(this, (TValue)converted!, priority);
converted = (TValue)v!;
return true;
}
else
{
@ -220,14 +244,6 @@ namespace Avalonia
}
}
internal override IDisposable RouteBind(
AvaloniaObject target,
IObservable<object?> source,
BindingPriority priority)
{
return target.Bind<TValue>(this, source, priority);
}
private object? GetDefaultBoxedValue(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));

24
src/Avalonia.Base/Utilities/WeakEvents.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Input;
using Avalonia.Threading;
namespace Avalonia.Utilities;
@ -20,15 +21,30 @@ public class WeakEvents
});
/// <summary>
/// Represents PropertyChanged event from <see cref="INotifyPropertyChanged"/>
/// Represents PropertyChanged event from <see cref="INotifyPropertyChanged"/> with auto-dispatching to the UI thread
/// </summary>
public static readonly WeakEvent<INotifyPropertyChanged, PropertyChangedEventArgs>
PropertyChanged = WeakEvent.Register<INotifyPropertyChanged, PropertyChangedEventArgs>(
ThreadSafePropertyChanged = WeakEvent.Register<INotifyPropertyChanged, PropertyChangedEventArgs>(
(s, h) =>
{
PropertyChangedEventHandler handler = (_, e) => h(s, e);
bool unsubscribed = false;
PropertyChangedEventHandler handler = (_, e) =>
{
if (Dispatcher.UIThread.CheckAccess())
h(s, e);
else
Dispatcher.UIThread.Post(() =>
{
if (!unsubscribed)
h(s, e);
});
};
s.PropertyChanged += handler;
return () => s.PropertyChanged -= handler;
return () =>
{
unsubscribed = true;
s.PropertyChanged -= handler;
};
});

2
src/Avalonia.Base/Visual.cs

@ -573,7 +573,7 @@ namespace Avalonia
/// <param name="newParent">The new visual parent.</param>
protected virtual void OnVisualParentChanged(Visual? oldParent, Visual? newParent)
{
RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
RaisePropertyChanged(VisualParentProperty, oldParent, newParent);
}
internal override ParametrizedLogger? GetBindingWarningLogger(

1
src/Avalonia.Base/composition-schema.xml

@ -26,6 +26,7 @@
<Property Name="Scale" Type="Vector3" DefaultValue="new Vector3(1, 1, 1)" Animated="true"/>
<Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/>
<Property Name="AdornedVisual" Type="CompositionVisual?" Internal="true" />
<Property Name="AdornerIsClipped" Type="bool" Internal="true" />
<Property Name="OpacityMaskBrush" Type="Avalonia.Media.IBrush?" Internal="true" />
</Object>
<Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/>

22
src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs

@ -0,0 +1,22 @@
using Avalonia.Automation.Peers;
namespace Avalonia.Controls.Automation.Peers
{
public class SliderAutomationPeer : RangeBaseAutomationPeer
{
public SliderAutomationPeer(Slider owner) : base(owner)
{
}
override protected string GetClassNameCore()
{
return "Slider";
}
override protected AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Slider;
}
}
}

7
src/Avalonia.Controls/ItemsControl.cs

@ -559,7 +559,12 @@ namespace Avalonia.Controls
return new ItemContainerGenerator(this);
}
internal void AddLogicalChild(Control c) => LogicalChildren.Add(c);
internal void AddLogicalChild(Control c)
{
if (!LogicalChildren.Contains(c))
LogicalChildren.Add(c);
}
internal void RemoveLogicalChild(Control c) => LogicalChildren.Remove(c);
/// <summary>

6
src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs

@ -27,7 +27,7 @@ namespace Avalonia.Controls.Platform
var files = await filePicker.OpenFilePickerAsync(options);
return files
.Select(file => file.TryGetFullPath() ?? file.Name)
.Select(file => file.TryGetLocalPath() ?? file.Name)
.ToArray();
}
else if (dialog is SaveFileDialog saveDialog)
@ -46,7 +46,7 @@ namespace Avalonia.Controls.Platform
return null;
}
var filePath = file.TryGetFullPath() ?? file.Name;
var filePath = file.TryGetLocalPath() ?? file.Name;
return new[] { filePath };
}
return null;
@ -64,7 +64,7 @@ namespace Avalonia.Controls.Platform
var folders = await filePicker.OpenFolderPickerAsync(options);
return folders
.Select(folder => folder.TryGetFullPath() ?? folder.Name)
.Select(folder => folder.TryGetLocalPath() ?? folder.Name)
.FirstOrDefault(u => u is not null);
}
}

33
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -63,6 +63,15 @@ namespace Avalonia.Controls.Presenters
o => o.PreeditText,
(o, v) => o.PreeditText = v);
/// <summary>
/// Defines the <see cref="CompositionRegion"/> property.
/// </summary>
public static readonly DirectProperty<TextPresenter, TextRange?> CompositionRegionProperty =
AvaloniaProperty.RegisterDirect<TextPresenter, TextRange?>(
nameof(CompositionRegion),
o => o.CompositionRegion,
(o, v) => o.CompositionRegion = v);
/// <summary>
/// Defines the <see cref="TextAlignment"/> property.
/// </summary>
@ -106,6 +115,7 @@ namespace Avalonia.Controls.Presenters
private Rect _caretBounds;
private Point _navigationPosition;
private string? _preeditText;
private TextRange? _compositionRegion;
static TextPresenter()
{
@ -146,6 +156,12 @@ namespace Avalonia.Controls.Presenters
set => SetAndRaise(PreeditTextProperty, ref _preeditText, value);
}
public TextRange? CompositionRegion
{
get => _compositionRegion;
set => SetAndRaise(CompositionRegionProperty, ref _compositionRegion, value);
}
/// <summary>
/// Gets or sets the font family.
/// </summary>
@ -548,7 +564,20 @@ namespace Avalonia.Controls.Presenters
var foreground = Foreground;
if (!string.IsNullOrEmpty(_preeditText))
if(_compositionRegion != null)
{
var preeditHighlight = new ValueSpan<TextRunProperties>(_compositionRegion?.Start ?? 0, _compositionRegion?.Length ?? 0,
new GenericTextRunProperties(typeface, FontSize,
foregroundBrush: foreground,
textDecorations: TextDecorations.Underline));
textStyleOverrides = new[]
{
preeditHighlight
};
}
else if (!string.IsNullOrEmpty(_preeditText))
{
var preeditHighlight = new ValueSpan<TextRunProperties>(_caretIndex, _preeditText.Length,
new GenericTextRunProperties(typeface, FontSize,
@ -911,6 +940,7 @@ namespace Avalonia.Controls.Presenters
break;
}
case nameof(CompositionRegion):
case nameof(Foreground):
case nameof(FontSize):
case nameof(FontStyle):
@ -931,7 +961,6 @@ namespace Avalonia.Controls.Presenters
case nameof(PasswordChar):
case nameof(RevealPassword):
case nameof(FlowDirection):
{
InvalidateTextLayout();

5
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -279,8 +279,11 @@ namespace Avalonia.Controls.Primitives
private void UpdateAdornedElement(Visual adorner, Visual? adorned)
{
if (adorner.CompositionVisual != null)
{
adorner.CompositionVisual.AdornedVisual = adorned?.CompositionVisual;
adorner.CompositionVisual.AdornerIsClipped = GetIsClipEnabled(adorner);
}
var info = adorner.GetValue(s_adornedElementInfoProperty);
if (info != null)

10
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -345,10 +345,7 @@ namespace Avalonia.Controls.Primitives
if (_oldSelectedItems != SelectedItems)
{
RaisePropertyChanged(
SelectedItemsProperty,
new Optional<IList?>(_oldSelectedItems),
new BindingValue<IList?>(SelectedItems));
RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems);
_oldSelectedItems = SelectedItems;
}
}
@ -909,10 +906,7 @@ namespace Avalonia.Controls.Primitives
else if (e.PropertyName == nameof(InternalSelectionModel.WritableSelectedItems) &&
_oldSelectedItems != (Selection as InternalSelectionModel)?.SelectedItems)
{
RaisePropertyChanged(
SelectedItemsProperty,
new Optional<IList?>(_oldSelectedItems),
new BindingValue<IList?>(SelectedItems));
RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems);
_oldSelectedItems = SelectedItems;
}
else if (e.PropertyName == nameof(ISelectionModel.Source))

39
src/Avalonia.Controls/RelativePanel.AttachedProperties.cs

@ -1,37 +1,30 @@
using Avalonia.Layout;
using Avalonia.Threading;
namespace Avalonia.Controls
{
public partial class RelativePanel
{
private static void OnAlignPropertiesChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
if (d is Layoutable layoutable && layoutable.Parent is Layoutable layoutableParent)
{
layoutableParent.InvalidateArrange();
}
}
static RelativePanel()
{
ClipToBoundsProperty.OverrideDefaultValue<RelativePanel>(true);
AboveProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignBottomWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignBottomWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignHorizontalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignHorizontalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignLeftWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignLeftWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignRightWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignRightWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignTopWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignTopWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignVerticalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignVerticalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
BelowProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
LeftOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
RightOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AffectsParentArrange<RelativePanel>(
AlignLeftWithPanelProperty, AlignLeftWithProperty, LeftOfProperty,
AlignRightWithPanelProperty, AlignRightWithProperty, RightOfProperty,
AlignTopWithPanelProperty, AlignTopWithProperty, AboveProperty,
AlignBottomWithPanelProperty, AlignBottomWithProperty, BelowProperty,
AlignHorizontalCenterWithPanelProperty, AlignHorizontalCenterWithProperty,
AlignVerticalCenterWithPanelProperty, AlignVerticalCenterWithProperty);
AffectsParentMeasure<RelativePanel>(
AlignLeftWithPanelProperty, AlignLeftWithProperty, LeftOfProperty,
AlignRightWithPanelProperty, AlignRightWithProperty, RightOfProperty,
AlignTopWithPanelProperty, AlignTopWithProperty, AboveProperty,
AlignBottomWithPanelProperty, AlignBottomWithProperty, BelowProperty,
AlignHorizontalCenterWithPanelProperty, AlignHorizontalCenterWithProperty,
AlignVerticalCenterWithPanelProperty, AlignVerticalCenterWithProperty);
}
/// <summary>

6
src/Avalonia.Controls/Slider.cs

@ -10,6 +10,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.Automation;
using Avalonia.Controls.Automation.Peers;
namespace Avalonia.Controls
{
@ -380,6 +381,11 @@ namespace Avalonia.Controls
}
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new SliderAutomationPeer(this);
}
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{

117
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -5,6 +5,7 @@ using Avalonia.Media.TextFormatting;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Controls
{
@ -12,6 +13,7 @@ namespace Avalonia.Controls
{
private TextBox? _parent;
private TextPresenter? _presenter;
private ITextEditable? _textEditable;
public Visual TextViewVisual => _presenter!;
@ -45,7 +47,7 @@ namespace Avalonia.Controls
{
get
{
if(_presenter is null || _parent is null)
if (_presenter is null || _parent is null)
{
return default;
}
@ -71,13 +73,70 @@ namespace Avalonia.Controls
}
}
public ITextEditable? TextEditable
{
get => _textEditable; set
{
if(_textEditable != null)
{
_textEditable.TextChanged -= TextEditable_TextChanged;
_textEditable.SelectionChanged -= TextEditable_SelectionChanged;
_textEditable.CompositionChanged -= TextEditable_CompositionChanged;
}
_textEditable = value;
if(_textEditable != null)
{
_textEditable.TextChanged += TextEditable_TextChanged;
_textEditable.SelectionChanged += TextEditable_SelectionChanged;
_textEditable.CompositionChanged += TextEditable_CompositionChanged;
if (_presenter != null)
{
_textEditable.Text = _presenter.Text;
_textEditable.SelectionStart = _presenter.SelectionStart;
_textEditable.SelectionEnd = _presenter.SelectionEnd;
}
}
}
}
private void TextEditable_CompositionChanged(object? sender, EventArgs e)
{
if (_presenter != null && _textEditable != null)
{
_presenter.CompositionRegion = new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd);
}
}
private void TextEditable_SelectionChanged(object? sender, EventArgs e)
{
if(_parent != null && _textEditable != null)
{
_parent.SelectionStart = _textEditable.SelectionStart;
_parent.SelectionEnd = _textEditable.SelectionEnd;
}
}
private void TextEditable_TextChanged(object? sender, EventArgs e)
{
if (_parent != null)
{
if (_parent.Text != _textEditable?.Text)
{
_parent.Text = _textEditable?.Text;
}
}
}
private static string GetTextLineText(TextLine textLine)
{
var builder = StringBuilderCache.Acquire(textLine.Length);
foreach (var run in textLine.TextRuns)
{
if(run.Length > 0)
if (run.Length > 0)
{
#if NET6_0_OR_GREATER
builder.Append(run.Text.Span);
@ -110,9 +169,18 @@ namespace Avalonia.Controls
_presenter.PreeditText = text;
}
public void SetComposingRegion(TextRange? region)
{
if (_presenter == null)
{
return;
}
_presenter.CompositionRegion = region;
}
public void SelectInSurroundingText(int start, int end)
{
if(_parent is null ||_presenter is null)
if (_parent is null || _presenter is null)
{
return;
}
@ -125,21 +193,21 @@ namespace Avalonia.Controls
var selectionStart = lineStart + start;
var selectionEnd = lineStart + end;
_parent.SelectionStart = selectionStart;
_parent.SelectionEnd = selectionEnd;
}
}
public void SetPresenter(TextPresenter? presenter, TextBox? parent)
{
if(_parent != null)
if (_parent != null)
{
_parent.PropertyChanged -= OnParentPropertyChanged;
}
_parent = parent;
if(_parent != null)
if (_parent != null)
{
_parent.PropertyChanged += OnParentPropertyChanged;
}
@ -148,16 +216,18 @@ namespace Avalonia.Controls
{
_presenter.PreeditText = null;
_presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
_presenter.CompositionRegion = null;
_presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
}
_presenter = presenter;
if (_presenter != null)
{
_presenter.CaretBoundsChanged += OnCaretBoundsChanged;
}
TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
OnCaretBoundsChanged(this, EventArgs.Empty);
@ -165,12 +235,33 @@ namespace Avalonia.Controls
private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if(e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
{
if (SupportsSurroundingText)
{
SurroundingTextChanged?.Invoke(this, e);
}
if (_textEditable != null)
{
var value = (int)(e.NewValue ?? 0);
if (e.Property == TextBox.SelectionStartProperty)
{
_textEditable.SelectionStart = value;
}
if (e.Property == TextBox.SelectionEndProperty)
{
_textEditable.SelectionEnd = value;
}
}
}
if(e.Property == TextBox.TextProperty)
{
if(_textEditable != null)
{
_textEditable.Text = (string?)e.NewValue;
}
}
}

2
src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml

@ -16,6 +16,8 @@
<StackPanel Orientation="Horizontal" Spacing="1">
<Button Margin="0,0,2,0"
Classes="textBoxClearButton"
Theme="{StaticResource SimpleTextBoxClearButtonTheme}"
Focusable="False"
ToolTip.Tip="Clear"
Cursor="Hand"
Command="{Binding $parent[TextBox].Clear}"

8
src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs

@ -54,8 +54,8 @@ namespace Avalonia.Diagnostics.Screenshots
protected override async Task<Stream?> GetStream(Control control)
{
var storageProvider = GetTopLevel(control).StorageProvider;
var defaultFolder = await storageProvider.TryGetFolderFromPath(_screenshotRoot)
?? await storageProvider.TryGetWellKnownFolder(WellKnownFolder.Pictures);
var defaultFolder = await storageProvider.TryGetFolderFromPathAsync(_screenshotRoot)
?? await storageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Pictures);
var result = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
@ -68,10 +68,6 @@ namespace Avalonia.Diagnostics.Screenshots
{
return null;
}
if (!result.CanOpenWrite)
{
throw new InvalidOperationException("Read-only file was selected.");
}
return await result.OpenWriteAsync();
}

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

@ -103,13 +103,13 @@ namespace Avalonia.Diagnostics.ViewModels
public HorizontalAlignment HorizontalAlignment
{
get => _horizontalAlignment;
private set => RaiseAndSetIfChanged(ref _horizontalAlignment, value);
set => RaiseAndSetIfChanged(ref _horizontalAlignment, value);
}
public VerticalAlignment VerticalAlignment
{
get => _verticalAlignment;
private set => RaiseAndSetIfChanged(ref _verticalAlignment, value);
set => RaiseAndSetIfChanged(ref _verticalAlignment, value);
}
public bool HasPadding { get; }

2
src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs

@ -260,7 +260,7 @@ namespace Avalonia.Dialogs.Internal
public void Navigate(IStorageFolder path, string initialSelectionName = null)
{
var fullDirectoryPath = path?.TryGetFullPath() ?? Directory.GetCurrentDirectory();
var fullDirectoryPath = path?.TryGetLocalPath() ?? Directory.GetCurrentDirectory();
Navigate(fullDirectoryPath, initialSelectionName);
}

2
src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

@ -51,7 +51,7 @@ namespace Avalonia.Dialogs
var files = await impl.OpenFilePickerAsync(dialog.ToFilePickerOpenOptions());
return files
.Select(file => file.TryGetFullPath() ?? file.Name)
.Select(file => file.TryGetLocalPath() ?? file.Name)
.ToArray();
}
}

2
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -6,7 +6,7 @@ using Tmds.DBus;
namespace Avalonia.FreeDesktop
{
public static class DBusHelper
internal static class DBusHelper
{
/// <summary>
/// This class uses synchronous execution at DBus connection establishment stage

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

@ -6,7 +6,7 @@ using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme
{
public class X11DBusImeHelper
internal class X11DBusImeHelper
{
private static readonly Dictionary<string, Func<Connection, IX11InputMethodFactory>> KnownMethods =
new Dictionary<string, Func<Connection, IX11InputMethodFactory>>

2
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -15,7 +15,7 @@ using Tmds.DBus;
namespace Avalonia.FreeDesktop
{
public class DBusMenuExporter
internal class DBusMenuExporter
{
public static ITopLevelNativeMenuExporter? TryCreateTopLevelNativeMenu(IntPtr xid)
{

2
src/Avalonia.FreeDesktop/DBusSystemDialog.cs

@ -88,7 +88,7 @@ namespace Avalonia.FreeDesktop
if (options.SuggestedFileName is { } currentName)
chooserOptions.Add("current_name", currentName);
if (options.SuggestedStartLocation?.TryGetFullPath() is { } folderPath)
if (options.SuggestedStartLocation?.TryGetLocalPath() is { } folderPath)
chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(folderPath));
objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);

6
src/Avalonia.FreeDesktop/IX11InputMethod.cs

@ -6,13 +6,13 @@ using Avalonia.Input.TextInput;
namespace Avalonia.FreeDesktop
{
public interface IX11InputMethodFactory
internal interface IX11InputMethodFactory
{
(ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid);
}
#pragma warning disable CA1815 // Override equals and operator equals on value types
public struct X11InputMethodForwardedKey
internal struct X11InputMethodForwardedKey
#pragma warning restore CA1815 // Override equals and operator equals on value types
{
public int KeyVal { get; set; }
@ -20,7 +20,7 @@ namespace Avalonia.FreeDesktop
public RawKeyEventType Type { get; set; }
}
public interface IX11InputMethodControl : IDisposable
internal interface IX11InputMethodControl : IDisposable
{
void SetWindowActive(bool active);
bool IsEnabled { get; }

2
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs

@ -5,7 +5,7 @@ using Avalonia.Controls.Platform;
namespace Avalonia.FreeDesktop
{
public class LinuxMountedVolumeInfoProvider : IMountedVolumeInfoProvider
internal class LinuxMountedVolumeInfoProvider : IMountedVolumeInfoProvider
{
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -68,8 +68,6 @@ namespace Avalonia.Headless
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new RenderTimer(60))
.Bind<IFontManagerImpl>().ToSingleton<HeadlessFontManagerStub>()
.Bind<ITextShaperImpl>().ToSingleton<HeadlessTextShaperStub>()
.Bind<IWindowingPlatform>().ToConstant(new HeadlessWindowingPlatform())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(), null);

2
src/Avalonia.Native/CallbackBase.cs

@ -6,7 +6,7 @@ using MicroCom.Runtime;
namespace Avalonia.Native
{
public abstract class NativeCallbackBase : CallbackBase, IMicroComExceptionCallback
internal abstract class NativeCallbackBase : CallbackBase, IMicroComExceptionCallback
{
public void RaiseException(Exception e)
{

2
src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs

@ -64,7 +64,7 @@ namespace Avalonia.Native
}
}
public class MacOSMountedVolumeInfoProvider : IMountedVolumeInfoProvider
internal class MacOSMountedVolumeInfoProvider : IMountedVolumeInfoProvider
{
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{

2
src/Avalonia.Native/MenuActionCallback.cs

@ -3,7 +3,7 @@ using Avalonia.Native.Interop;
namespace Avalonia.Native
{
public class MenuActionCallback : NativeCallbackBase, IAvnActionCallback
internal class MenuActionCallback : NativeCallbackBase, IAvnActionCallback
{
private Action _action;

2
src/Avalonia.Native/PredicateCallback.cs

@ -3,7 +3,7 @@ using Avalonia.Native.Interop;
namespace Avalonia.Native
{
public class PredicateCallback : NativeCallbackBase, IAvnPredicateCallback
internal class PredicateCallback : NativeCallbackBase, IAvnPredicateCallback
{
private Func<bool> _predicate;

2
src/Avalonia.Native/ScreenImpl.cs

@ -5,7 +5,7 @@ using Avalonia.Platform;
namespace Avalonia.Native
{
class ScreenImpl : IScreenImpl, IDisposable
internal class ScreenImpl : IScreenImpl, IDisposable
{
private IAvnScreens _native;

6
src/Avalonia.Native/SystemDialogs.cs

@ -33,7 +33,7 @@ namespace Avalonia.Native
{
using var events = new SystemDialogEvents();
var suggestedDirectory = options.SuggestedStartLocation?.TryGetFullPath() ?? string.Empty;
var suggestedDirectory = options.SuggestedStartLocation?.TryGetLocalPath() ?? string.Empty;
_native.OpenFileDialog((IAvnWindow)_window.Native,
events,
@ -53,7 +53,7 @@ namespace Avalonia.Native
{
using var events = new SystemDialogEvents();
var suggestedDirectory = options.SuggestedStartLocation?.TryGetFullPath() ?? string.Empty;
var suggestedDirectory = options.SuggestedStartLocation?.TryGetLocalPath() ?? string.Empty;
_native.SaveFileDialog((IAvnWindow)_window.Native,
events,
@ -72,7 +72,7 @@ namespace Avalonia.Native
{
using var events = new SystemDialogEvents();
var suggestedDirectory = options.SuggestedStartLocation?.TryGetFullPath() ?? string.Empty;
var suggestedDirectory = options.SuggestedStartLocation?.TryGetLocalPath() ?? string.Empty;
_native.SelectFolderDialog((IAvnWindow)_window.Native, events, options.AllowMultiple.AsComBool(), options.Title ?? "", suggestedDirectory);

1
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -13,6 +13,7 @@
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\NullableEnable.props" />
<ItemGroup>
<Compile Remove="..\Shared\SourceGeneratorAttributes.cs" />

5
src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering;
@ -22,7 +21,7 @@ internal class CompositionOpenGlSwapchain : SwapchainBase<IGlSwapchainImage>
}
public CompositionOpenGlSwapchain(IGlContext context, ICompositionGpuInterop interop, CompositionDrawingSurface target,
IGlContextExternalObjectsFeature externalObjectsFeature) : base(interop, target)
IGlContextExternalObjectsFeature? externalObjectsFeature) : base(interop, target)
{
_context = context;
_externalObjectsFeature = externalObjectsFeature;
@ -95,7 +94,7 @@ internal class DxgiMutexOpenGlSwapChainImage : IGlSwapchainImage
public int TextureId => _texture.TextureId;
public int InternalFormat => _texture.InternalFormat;
public PixelSize Size => new(_texture.Properties.Width, _texture.Properties.Height);
public Task LastPresent => _lastPresent;
public Task? LastPresent => _lastPresent;
public void BeginDraw() => _texture.AcquireKeyedMutex(0);
public void Present()

33
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia.Controls;
@ -11,10 +12,10 @@ namespace Avalonia.OpenGL.Controls
{
public abstract class OpenGlControlBase : Control
{
private CompositionSurfaceVisual _visual;
private Action _update;
private CompositionSurfaceVisual? _visual;
private readonly Action _update;
private bool _updateQueued;
private Task<bool> _initialization;
private Task<bool>? _initialization;
private OpenGlControlBaseResources? _resources;
private Compositor? _compositor;
protected GlVersion GlVersion => _resources?.Context.Version ?? default;
@ -24,7 +25,7 @@ namespace Avalonia.OpenGL.Controls
_update = Update;
}
void DoCleanup()
private void DoCleanup()
{
if (_initialization is { Status: TaskStatus.RanToCompletion } && _resources != null)
{
@ -63,14 +64,14 @@ namespace Avalonia.OpenGL.Controls
RequestNextFrameRendering();
}
[MemberNotNullWhen(true, nameof(_resources))]
private bool EnsureInitializedCore(
ICompositionGpuInterop interop,
IOpenGlTextureSharingRenderInterfaceContextFeature contextSharingFeature)
IOpenGlTextureSharingRenderInterfaceContextFeature? contextSharingFeature)
{
var surface = _compositor.CreateDrawingSurface();
var surface = _compositor!.CreateDrawingSurface();
IGlContext ctx = null;
var contextFactory = AvaloniaLocator.Current.GetService<IPlatformGraphicsOpenGlContextFactory>();
IGlContext? ctx = null;
try
{
if (contextSharingFeature?.CanCreateSharedContext == true)
@ -78,6 +79,7 @@ namespace Avalonia.OpenGL.Controls
if(_resources == null)
{
var contextFactory = AvaloniaLocator.Current.GetRequiredService<IPlatformGraphicsOpenGlContextFactory>();
ctx = contextFactory.CreateContext(null);
if (ctx.TryGetFeature<IGlContextExternalObjectsFeature>(out var externalObjects))
_resources = OpenGlControlBaseResources.TryCreate(ctx, surface, interop, externalObjects);
@ -121,13 +123,14 @@ namespace Avalonia.OpenGL.Controls
base.OnPropertyChanged(change);
}
void ContextLost()
private void ContextLost()
{
_initialization = null;
_resources?.DisposeAsync();
OnOpenGlLost();
}
[MemberNotNullWhen(true, nameof(_resources))]
private bool EnsureInitialized()
{
if (_initialization != null)
@ -170,11 +173,11 @@ namespace Avalonia.OpenGL.Controls
private void Update()
{
_updateQueued = false;
if (VisualRoot == null)
if (VisualRoot is not { } visualRoot)
return;
if(!EnsureInitialized())
return;
using (_resources.BeginDraw(GetPixelSize()))
using (_resources.BeginDraw(GetPixelSize(visualRoot)))
OnOpenGlRender(_resources.Context.GlInterface, _resources.Fbo);
}
@ -190,7 +193,7 @@ namespace Avalonia.OpenGL.Controls
var gpuInteropTask = _compositor.TryGetCompositionGpuInterop();
var contextSharingFeature =
(IOpenGlTextureSharingRenderInterfaceContextFeature)
(IOpenGlTextureSharingRenderInterfaceContextFeature?)
await _compositor.TryGetRenderInterfaceFeature(
typeof(IOpenGlTextureSharingRenderInterfaceContextFeature));
var interop = await gpuInteropTask;
@ -208,7 +211,7 @@ namespace Avalonia.OpenGL.Controls
return false;
}
using (_resources!.Context.MakeCurrent())
using (_resources.Context.MakeCurrent())
OnOpenGlInit(_resources.Context.GlInterface);
return true;
@ -228,9 +231,9 @@ namespace Avalonia.OpenGL.Controls
}
}
private PixelSize GetPixelSize()
private PixelSize GetPixelSize(IRenderRoot visualRoot)
{
var scaling = VisualRoot!.RenderScaling;
var scaling = visualRoot.RenderScaling;
return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)),
Math.Max(1, (int)(Bounds.Height * scaling)));
}

6
src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs

@ -14,14 +14,14 @@ internal class OpenGlControlBaseResources : IAsyncDisposable
public int Fbo { get; private set; }
private PixelSize _depthBufferSize;
public CompositionDrawingSurface Surface { get; }
public CompositionOpenGlSwapchain _swapchain;
private readonly CompositionOpenGlSwapchain _swapchain;
public IGlContext Context { get; private set; }
public static OpenGlControlBaseResources? TryCreate(CompositionDrawingSurface surface,
ICompositionGpuInterop interop,
IOpenGlTextureSharingRenderInterfaceContextFeature feature)
{
IGlContext context;
IGlContext? context;
try
{
context = feature.CreateSharedContext();
@ -71,7 +71,7 @@ internal class OpenGlControlBaseResources : IAsyncDisposable
new CompositionOpenGlSwapchain(context, interop, Surface, externalObjects);
}
void UpdateDepthRenderbuffer(PixelSize size)
private void UpdateDepthRenderbuffer(PixelSize size)
{
if (size == _depthBufferSize && _depthBuffer != 0)
return;

22
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -12,15 +12,15 @@ namespace Avalonia.OpenGL.Egl
{
private readonly EglDisplay _disp;
private readonly EglInterface _egl;
private readonly EglContext _sharedWith;
private readonly EglContext? _sharedWith;
private bool _isLost;
private IntPtr _context;
private readonly Action _disposeCallback;
private readonly Action? _disposeCallback;
private readonly Dictionary<Type, object> _features;
private readonly object _lock;
internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface,
GlVersion version, int sampleCount, int stencilSize, Action disposeCallback,
internal EglContext(EglDisplay display, EglInterface egl, EglContext? sharedWith, IntPtr ctx, EglSurface? offscreenSurface,
GlVersion version, int sampleCount, int stencilSize, Action? disposeCallback,
Dictionary<Type, Func<EglContext, object>> features)
{
_disp = display;
@ -42,19 +42,19 @@ namespace Avalonia.OpenGL.Egl
public IntPtr Context =>
_context == IntPtr.Zero ? throw new ObjectDisposedException(nameof(EglContext)) : _context;
public EglSurface OffscreenSurface { get; }
public EglSurface? OffscreenSurface { get; }
public GlVersion Version { get; }
public GlInterface GlInterface { get; }
public int SampleCount { get; }
public int StencilSize { get; }
public EglDisplay Display => _disp;
class RestoreContext : IDisposable
private class RestoreContext : IDisposable
{
private readonly EglInterface _egl;
private readonly object _l;
private readonly IntPtr _display;
public IntPtr _context, _read, _draw;
private readonly IntPtr _context, _read, _draw;
public RestoreContext(EglInterface egl, IntPtr defDisplay, object l)
{
@ -78,7 +78,7 @@ namespace Avalonia.OpenGL.Egl
public IDisposable MakeCurrent() => MakeCurrent(OffscreenSurface);
public IDisposable MakeCurrent(EglSurface surface)
public IDisposable MakeCurrent(EglSurface? surface)
{
if (IsLost)
throw new PlatformGraphicsContextLostException();
@ -147,7 +147,7 @@ namespace Avalonia.OpenGL.Egl
public bool CanCreateSharedContext => _disp.SupportsSharing;
public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) =>
public IGlContext CreateSharedContext(IEnumerable<GlVersion>? preferredVersions = null) =>
_disp.CreateContext(new EglContextOptions
{
ShareWith = _sharedWith ?? this
@ -174,9 +174,9 @@ namespace Avalonia.OpenGL.Egl
_disposeCallback?.Invoke();
}
public object TryGetFeature(Type featureType)
public object? TryGetFeature(Type featureType)
{
if (_features?.TryGetValue(featureType, out var feature) == true)
if (_features.TryGetValue(featureType, out var feature))
return feature;
return null;
}

11
src/Avalonia.OpenGL/Egl/EglDisplay.cs

@ -14,14 +14,14 @@ namespace Avalonia.OpenGL.Egl
private readonly EglDisplayOptions _options;
private EglConfigInfo _config;
private bool _isLost;
private object _lock = new();
private readonly object _lock = new();
public bool SupportsSharing { get; }
public IntPtr Handle => _display;
public IntPtr Config => _config.Config;
internal bool SingleContext => !_options.SupportsMultipleContexts;
private List<EglContext> _contexts = new();
private readonly List<EglContext> _contexts = new();
public EglDisplay() : this(new EglDisplayCreationOptions
{
@ -38,7 +38,7 @@ namespace Avalonia.OpenGL.Egl
public EglDisplay(IntPtr display, EglDisplayOptions options)
{
_egl = options.Egl;
_egl = options.Egl ?? new EglInterface();
SupportsSharing = options.SupportsContextSharing;
_display = display;
_options = options;
@ -49,7 +49,7 @@ namespace Avalonia.OpenGL.Egl
}
public EglInterface EglInterface => _egl;
public EglContext CreateContext(EglContextOptions options)
public EglContext CreateContext(EglContextOptions? options)
{
if (SingleContext && _contexts.Any())
throw new OpenGlException("This EGLDisplay can only have one active context");
@ -129,7 +129,7 @@ namespace Avalonia.OpenGL.Egl
protected virtual bool DisplayLockIsSharedWithContexts => false;
internal object ContextSharedSyncRoot => DisplayLockIsSharedWithContexts ? _lock : null;
internal object? ContextSharedSyncRoot => DisplayLockIsSharedWithContexts ? _lock : null;
internal void OnContextLost(EglContext context)
{
@ -171,7 +171,6 @@ namespace Avalonia.OpenGL.Egl
if (_display != IntPtr.Zero)
_egl.Terminate(_display);
_display = IntPtr.Zero;
_config = null;
_options.DisposeCallback?.Invoke();
}
}

18
src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs

@ -5,26 +5,26 @@ namespace Avalonia.OpenGL.Egl;
public class EglDisplayOptions
{
public EglInterface Egl { get; set; }
public EglInterface? Egl { get; set; }
public bool SupportsContextSharing { get; set; }
public bool SupportsMultipleContexts { get; set; }
public bool ContextLossIsDisplayLoss { get; set; }
public Func<bool> DeviceLostCheckCallback { get; set; }
public Action DisposeCallback { get; set; }
public IEnumerable<GlVersion> GlVersions { get; set; }
public Func<bool>? DeviceLostCheckCallback { get; set; }
public Action? DisposeCallback { get; set; }
public IEnumerable<GlVersion>? GlVersions { get; set; }
}
public class EglContextOptions
{
public EglContext ShareWith { get; set; }
public EglSurface OffscreenSurface { get; set; }
public Action DisposeCallback { get; set; }
public Dictionary<Type, Func<EglContext, object>> ExtraFeatures { get; set; }
public EglContext? ShareWith { get; set; }
public EglSurface? OffscreenSurface { get; set; }
public Action? DisposeCallback { get; set; }
public Dictionary<Type, Func<EglContext, object>>? ExtraFeatures { get; set; }
}
public class EglDisplayCreationOptions : EglDisplayOptions
{
public int? PlatformType { get; set; }
public IntPtr PlatformDisplay { get; set; }
public int[] PlatformDisplayAttrs { get; set; }
public int[]? PlatformDisplayAttrs { get; set; }
}

11
src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs

@ -1,15 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl;
static class EglDisplayUtils
internal static class EglDisplayUtils
{
public static IntPtr CreateDisplay(EglDisplayCreationOptions options)
{
var egl = options.Egl;
var egl = options.Egl ?? new EglInterface();
var display = IntPtr.Zero;
if (options.PlatformType == null)
{
@ -30,9 +29,9 @@ static class EglDisplayUtils
return display;
}
public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable<GlVersion> versions)
public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable<GlVersion>? versions)
{
if (!egl.Initialize(display, out var major, out var minor))
if (!egl.Initialize(display, out _, out _))
throw OpenGlException.GetFormattedException("eglInitialize", egl);
// TODO: AvaloniaLocator.Current.GetService<AngleOptions>()?.GlProfiles
@ -112,7 +111,7 @@ static class EglDisplayUtils
}
class EglConfigInfo
internal class EglConfigInfo
{
public IntPtr Config { get; }
public GlVersion Version { get; }

8
src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs

@ -27,9 +27,9 @@ namespace Avalonia.OpenGL.Egl
return new RenderTarget(glSurface, eglContext, _info);
}
class RenderTarget : EglPlatformSurfaceRenderTargetBase
private class RenderTarget : EglPlatformSurfaceRenderTargetBase
{
private EglSurface _glSurface;
private EglSurface? _glSurface;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private PixelSize _currentSize;
private readonly IntPtr _handle;
@ -42,12 +42,12 @@ namespace Avalonia.OpenGL.Egl
_handle = _info.Handle;
}
public override void Dispose() => _glSurface.Dispose();
public override void Dispose() => _glSurface?.Dispose();
public override IGlPlatformSurfaceRenderingSession BeginDrawCore()
{
if (_info.Size != _currentSize
|| _handle != _info.Handle
|| _handle != _info.Handle
|| _glSurface == null)
{
_glSurface?.Dispose();

11
src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs

@ -33,7 +33,7 @@ namespace Avalonia.OpenGL.Egl
public abstract IGlPlatformSurfaceRenderingSession BeginDrawCore();
protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface,
PixelSize size, double scaling, Action onFinish = null, bool isYFlipped = false)
PixelSize size, double scaling, Action? onFinish = null, bool isYFlipped = false)
{
var restoreContext = Context.MakeCurrent(surface);
@ -56,19 +56,18 @@ namespace Avalonia.OpenGL.Egl
restoreContext.Dispose();
}
}
class Session : IGlPlatformSurfaceRenderingSession
private class Session : IGlPlatformSurfaceRenderingSession
{
private readonly EglContext _context;
private readonly EglSurface _glSurface;
private readonly EglDisplay _display;
private readonly IDisposable _restoreContext;
private readonly Action _onFinish;
private readonly Action? _onFinish;
public Session(EglDisplay display, EglContext context,
EglSurface glSurface, PixelSize size, double scaling,
IDisposable restoreContext, Action onFinish, bool isYFlipped)
IDisposable restoreContext, Action? onFinish, bool isYFlipped)
{
Size = size;
Scaling = scaling;

10
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -49,7 +49,7 @@ namespace Avalonia.OpenGL.Egl
public partial IntPtr GetDisplay(IntPtr nativeDisplay);
[GetProcAddress("eglGetPlatformDisplayEXT", true)]
public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[] attrs);
public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[]? attrs);
[GetProcAddress("eglInitialize")]
public partial bool Initialize(IntPtr display, out int major, out int minor);
@ -75,7 +75,7 @@ namespace Avalonia.OpenGL.Egl
public partial bool DestroyContext(IntPtr display, IntPtr context);
[GetProcAddress("eglCreatePbufferSurface")]
public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[]? attrs);
[GetProcAddress("eglMakeCurrent")]
public partial bool MakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
@ -96,7 +96,7 @@ namespace Avalonia.OpenGL.Egl
public partial void SwapBuffers(IntPtr display, IntPtr surface);
[GetProcAddress("eglCreateWindowSurface")]
public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[]? attrs);
[GetProcAddress("eglBindTexImage")]
public partial int BindTexImage(IntPtr display, IntPtr surface, int buffer);
@ -116,7 +116,7 @@ namespace Avalonia.OpenGL.Egl
[GetProcAddress("eglQueryString")]
public partial IntPtr QueryStringNative(IntPtr display, int i);
public string QueryString(IntPtr display, int i)
public string? QueryString(IntPtr display, int i)
{
var rv = QueryStringNative(display, i);
if (rv == IntPtr.Zero)
@ -125,7 +125,7 @@ namespace Avalonia.OpenGL.Egl
}
[GetProcAddress("eglCreatePbufferFromClientBuffer")]
public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list);
public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[]? attrib_list);
[GetProcAddress("eglQueryDisplayAttribEXT", true)]
public partial bool QueryDisplayAttribExt(IntPtr display, int attr, out IntPtr res);

5
src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs

@ -1,7 +1,6 @@
using System;
using Avalonia.Logging;
using Avalonia.Platform;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl
{
@ -24,7 +23,7 @@ namespace Avalonia.OpenGL.Egl
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(feature);
}
public static EglPlatformGraphics TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions
public static EglPlatformGraphics? TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions
{
Egl = new EglInterface(),
// Those are expected to be supported by most EGL implementations
@ -32,7 +31,7 @@ namespace Avalonia.OpenGL.Egl
SupportsContextSharing = true
}));
public static EglPlatformGraphics TryCreate(Func<EglDisplay> displayFactory)
public static EglPlatformGraphics? TryCreate(Func<EglDisplay> displayFactory)
{
try
{

43
src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs

@ -63,10 +63,10 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
{
private readonly IGlContext _context;
private readonly ExternalObjectsInterface _ext;
private List<string> _imageTypes = new();
private List<string> _semaphoreTypes = new();
private readonly List<string> _imageTypes = new();
private readonly List<string> _semaphoreTypes = new();
public static ExternalObjectsOpenGlExtensionFeature TryCreate(IGlContext context)
public static ExternalObjectsOpenGlExtensionFeature? TryCreate(IGlContext context)
{
var extensions = context.GlInterface.GetExtensions();
if (extensions.Contains("GL_EXT_memory_object") && extensions.Contains("GL_EXT_semaphore"))
@ -142,10 +142,15 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
public IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
{
if(!_imageTypes.Contains(handle.HandleDescriptor))
throw new ArgumentException(handle.HandleDescriptor + " is not supported");
if (handle.HandleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor)
var handleDescriptor = handle.HandleDescriptor;
if (string.IsNullOrEmpty(handleDescriptor))
throw new ArgumentException("The handle must have a descriptor", nameof(handle));
if (!_imageTypes.Contains(handleDescriptor))
throw new ArgumentException(handleDescriptor + " is not supported", nameof(handle));
if (handleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor)
{
while (_context.GlInterface.GetError() != 0)
{
@ -174,23 +179,27 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
return new ExternalImageTexture(_context, properties, _ext, memoryObject, texture);
}
throw new ArgumentException(handle.HandleDescriptor + " is not supported");
throw new ArgumentException(handleDescriptor + " is not supported", nameof(handle));
}
public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle)
{
if(!_semaphoreTypes.Contains(handle.HandleDescriptor))
throw new ArgumentException(handle.HandleDescriptor + " is not supported");
var handleDescriptor = handle.HandleDescriptor;
if (string.IsNullOrEmpty(handleDescriptor))
throw new ArgumentException("The handle must have a descriptor", nameof(handle));
if (!_semaphoreTypes.Contains(handleDescriptor))
throw new ArgumentException(handleDescriptor + " is not supported");
if (handle.HandleDescriptor ==
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor)
if (handleDescriptor == KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor)
{
_ext.GenSemaphoresEXT(1, out var semaphore);
_ext.ImportSemaphoreFdEXT(semaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, handle.Handle.ToInt32());
return new ExternalSemaphore(_context, _ext, semaphore);
}
throw new ArgumentException(handle.HandleDescriptor + " is not supported");
throw new ArgumentException(handleDescriptor + " is not supported", nameof(handle));
}
public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
@ -200,10 +209,10 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
return default;
}
public byte[] DeviceLuid { get; }
public byte[] DeviceUuid { get; }
public byte[]? DeviceLuid { get; }
public byte[]? DeviceUuid { get; }
unsafe class ExternalSemaphore : IGlExternalSemaphore
private unsafe class ExternalSemaphore : IGlExternalSemaphore
{
private readonly IGlContext _context;
private readonly ExternalObjectsInterface _ext;
@ -242,7 +251,7 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
}
}
class ExternalImageTexture : IGlExternalImageTexture
private class ExternalImageTexture : IGlExternalImageTexture
{
private readonly IGlContext _context;
private readonly ExternalObjectsInterface _ext;

16
src/Avalonia.OpenGL/GlBasicInfoInterface.cs

@ -2,14 +2,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
using Avalonia.SourceGenerator;
namespace Avalonia.OpenGL
{
public unsafe partial class GlBasicInfoInterface
{
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress){
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress)
{
Initialize(getProcAddress);
}
@ -22,7 +22,7 @@ namespace Avalonia.OpenGL
[GetProcAddress("glGetStringi")]
public partial IntPtr GetStringiNative(int v, int v1);
public string GetString(int v)
public string? GetString(int v)
{
var ptr = GetStringNative(v);
if (ptr != IntPtr.Zero)
@ -30,7 +30,7 @@ namespace Avalonia.OpenGL
return null;
}
public string GetString(int v, int index)
public string? GetString(int v, int index)
{
var ptr = GetStringiNative(v, index);
if (ptr != IntPtr.Zero)
@ -46,7 +46,13 @@ namespace Avalonia.OpenGL
GetIntegerv(GlConsts.GL_NUM_EXTENSIONS, out int count);
var rv = new List<string>(count);
for (var c = 0; c < count; c++)
rv.Add(GetString(GlConsts.GL_EXTENSIONS, c));
{
if (GetString(GlConsts.GL_EXTENSIONS, c) is { } extension)
{
rv.Add(extension);
}
}
return rv;
}
}

21
src/Avalonia.OpenGL/GlInterface.cs

@ -11,9 +11,9 @@ namespace Avalonia.OpenGL
public unsafe partial class GlInterface : GlBasicInfoInterface
{
private readonly Func<string, IntPtr> _getProcAddress;
public string Version { get; }
public string Vendor { get; }
public string Renderer { get; }
public string? Version { get; }
public string? Vendor { get; }
public string? Renderer { get; }
public GlContextInfo ContextInfo { get; }
public class GlContextInfo
@ -39,9 +39,9 @@ namespace Avalonia.OpenGL
{
_getProcAddress = getProcAddress;
ContextInfo = info;
Version = GetString(GlConsts.GL_VERSION);
Renderer = GetString(GlConsts.GL_RENDERER);
Vendor = GetString(GlConsts.GL_VENDOR);
Version = GetString(GL_VERSION);
Renderer = GetString(GL_RENDERER);
Vendor = GetString(GL_VENDOR);
Initialize(getProcAddress, ContextInfo);
}
@ -73,9 +73,6 @@ namespace Avalonia.OpenGL
[GetProcAddress("glFinish")]
public partial void Finish();
[GetProcAddress("glGetIntegerv")]
public partial void GetIntegerv(int name, out int rv);
[GetProcAddress("glGenFramebuffers")]
public partial void GenFramebuffers(int count, int* res);
@ -203,7 +200,7 @@ namespace Avalonia.OpenGL
[GetProcAddress("glGetShaderInfoLog")]
public partial void GetShaderInfoLog(int shader, int maxLength, out int length, void* infoLog);
public unsafe string CompileShaderAndGetError(int shader, string source)
public unsafe string? CompileShaderAndGetError(int shader, string source)
{
ShaderSourceString(shader, source);
CompileShader(shader);
@ -238,7 +235,7 @@ namespace Avalonia.OpenGL
[GetProcAddress("glGetProgramInfoLog")]
public partial void GetProgramInfoLog(int program, int maxLength, out int len, void* infoLog);
public unsafe string LinkProgramAndGetError(int program)
public unsafe string? LinkProgramAndGetError(int program)
{
LinkProgram(program);
int compiled;
@ -374,4 +371,4 @@ namespace Avalonia.OpenGL
});
}
}
}
}

3
src/Avalonia.OpenGL/IGlContext.cs

@ -12,10 +12,9 @@ namespace Avalonia.OpenGL
int SampleCount { get; }
int StencilSize { get; }
IDisposable MakeCurrent();
IDisposable EnsureCurrent();
bool IsSharedWith(IGlContext context);
bool CanCreateSharedContext { get; }
IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null);
IGlContext? CreateSharedContext(IEnumerable<GlVersion>? preferredVersions = null);
}
public interface IGlPlatformSurfaceRenderTargetFactory

3
src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition;
@ -7,7 +6,7 @@ namespace Avalonia.OpenGL
public interface IOpenGlTextureSharingRenderInterfaceContextFeature
{
bool CanCreateSharedContext { get; }
IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null);
IGlContext? CreateSharedContext(IEnumerable<GlVersion>? preferredVersions = null);
ICompositionImportableOpenGlSharedTexture CreateSharedTextureForComposition(IGlContext context, PixelSize size);
}

6
src/Avalonia.OpenGL/OpenGlException.cs

@ -5,13 +5,13 @@ namespace Avalonia.OpenGL
{
public class OpenGlException : Exception
{
public int? ErrorCode { get; private set; }
public int? ErrorCode { get; }
public OpenGlException(string message) : base(message)
public OpenGlException(string? message) : base(message)
{
}
private OpenGlException(string message, int errorCode) : base(message)
private OpenGlException(string? message, int errorCode) : base(message)
{
ErrorCode = errorCode;
}

2
src/Avalonia.X11/Glx/Glx.cs

@ -10,7 +10,7 @@ using Avalonia.SourceGenerator;
namespace Avalonia.X11.Glx
{
unsafe partial class GlxInterface
internal unsafe partial class GlxInterface
{
private const string libGL = "libGL.so.1";
[GetProcAddress("glXMakeContextCurrent")]

2
src/Avalonia.X11/Glx/GlxConsts.cs

@ -4,7 +4,7 @@
#pragma warning disable 414
namespace Avalonia.X11.Glx
{
class GlxConsts
internal class GlxConsts
{
public const int GLX_USE_GL = 1;
public const int GLX_BUFFER_SIZE = 2;

6
src/Avalonia.X11/Glx/GlxContext.cs

@ -7,7 +7,7 @@ using Avalonia.Reactive;
namespace Avalonia.X11.Glx
{
class GlxContext : IGlContext
internal class GlxContext : IGlContext
{
public IntPtr Handle { get; }
public GlxInterface Glx { get; }
@ -46,8 +46,8 @@ namespace Avalonia.X11.Glx
public GlInterface GlInterface { get; }
public int SampleCount { get; }
public int StencilSize { get; }
class RestoreContext : IDisposable
private class RestoreContext : IDisposable
{
private GlxInterface _glx;
private IntPtr _defaultDisplay;

8
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -6,7 +6,7 @@ using static Avalonia.X11.Glx.GlxConsts;
namespace Avalonia.X11.Glx
{
unsafe class GlxDisplay
internal unsafe class GlxDisplay
{
private readonly X11Info _x11;
private readonly GlVersion[] _probeProfiles;
@ -106,7 +106,7 @@ namespace Avalonia.X11.Glx
}
}
IntPtr CreatePBuffer()
private IntPtr CreatePBuffer()
{
return Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 });
}
@ -116,8 +116,8 @@ namespace Avalonia.X11.Glx
public GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share,
share.SampleCount, share.StencilSize, true);
GlxContext CreateContext(IntPtr defaultXid, IGlContext share,
private GlxContext CreateContext(IntPtr defaultXid, IGlContext share,
int sampleCount, int stencilSize, bool ownsPBuffer)
{
var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero;

8
src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

@ -6,7 +6,7 @@ using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.X11.Glx
{
class GlxGlPlatformSurface: IGlPlatformSurface
internal class GlxGlPlatformSurface: IGlPlatformSurface
{
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
@ -21,7 +21,7 @@ namespace Avalonia.X11.Glx
return new RenderTarget((GlxContext)context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
private class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
@ -46,8 +46,8 @@ namespace Avalonia.X11.Glx
return new Session(_context, _info, oldContext);
}
class Session : IGlPlatformSurfaceRenderingSession
private class Session : IGlPlatformSurfaceRenderingSession
{
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save