Browse Source

Merge branch 'master' into fixes/headlessSkiaRendering

pull/10350/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
62d64f6df9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  2. 17
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  3. 39
      native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm
  4. 2
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  5. 5
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  6. 29
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  7. 6
      native/Avalonia.Native/src/OSX/WindowImpl.h
  8. 101
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  9. 1
      native/Avalonia.Native/src/OSX/common.h
  10. 11
      native/Avalonia.Native/src/OSX/main.mm
  11. 2
      readme.md
  12. 10
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  13. 2
      samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
  14. 2
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  15. 32
      samples/GpuInterop/VulkanDemo/VulkanImage.cs
  16. 1
      samples/IntegrationTestApp/MainWindow.axaml
  17. 2
      samples/IntegrationTestApp/MainWindow.axaml.cs
  18. 4
      samples/MobileSandbox/MainView.xaml
  19. 11
      samples/interop/WindowsInteropTest/Program.cs
  20. 7
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  21. 42
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  22. 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  23. 127
      src/Android/Avalonia.Android/InputEditable.cs
  24. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
  25. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  26. 141
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  27. 4
      src/Android/Avalonia.Android/PlatformIconLoader.cs
  28. 6
      src/Android/Avalonia.Android/Stubs.cs
  29. 64
      src/Avalonia.Base/AvaloniaObject.cs
  30. 50
      src/Avalonia.Base/AvaloniaProperty.cs
  31. 23
      src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
  32. 5
      src/Avalonia.Base/DirectPropertyBase.cs
  33. 23
      src/Avalonia.Base/Input/TextInput/ITextEditable.cs
  34. 11
      src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
  35. 12
      src/Avalonia.Base/Platform/IPlatformBehaviorInhibition.cs
  36. 2
      src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
  37. 4
      src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs
  38. 47
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  39. 6
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  40. 36
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  41. 49
      src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
  42. 25
      src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
  43. 2
      src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
  44. 2
      src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
  45. 5
      src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs
  46. 67
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  47. 5
      src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
  48. 5
      src/Avalonia.Base/Rendering/IRenderLoop.cs
  49. 1
      src/Avalonia.Base/Rendering/IRenderTimer.cs
  50. 1
      src/Avalonia.Base/Rendering/RenderLoop.cs
  51. 9
      src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs
  52. 4
      src/Avalonia.Base/StyledElement.cs
  53. 46
      src/Avalonia.Base/StyledProperty.cs
  54. 2
      src/Avalonia.Controls/Border.cs
  55. 2
      src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
  56. 2
      src/Avalonia.Controls/Grid.cs
  57. 2
      src/Avalonia.Controls/Image.cs
  58. 7
      src/Avalonia.Controls/ItemsControl.cs
  59. 2
      src/Avalonia.Controls/Panel.cs
  60. 2
      src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
  61. 8
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  62. 13
      src/Avalonia.Controls/PlatformInhibitionType.cs
  63. 2
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  64. 35
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  65. 5
      src/Avalonia.Controls/Primitives/AccessText.cs
  66. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  67. 2
      src/Avalonia.Controls/Shapes/Shape.cs
  68. 8
      src/Avalonia.Controls/TextBlock.cs
  69. 117
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  70. 2
      src/Avalonia.Controls/TickBar.cs
  71. 25
      src/Avalonia.Controls/TopLevel.cs
  72. 2
      src/Avalonia.Controls/WindowBase.cs
  73. 2
      src/Avalonia.FreeDesktop/DBusHelper.cs
  74. 2
      src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs
  75. 2
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  76. 6
      src/Avalonia.FreeDesktop/IX11InputMethod.cs
  77. 2
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs
  78. 21
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  79. 2
      src/Avalonia.Native/CallbackBase.cs
  80. 2
      src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs
  81. 2
      src/Avalonia.Native/MenuActionCallback.cs
  82. 20
      src/Avalonia.Native/PlatformBehaviorInhibition.cs
  83. 2
      src/Avalonia.Native/PredicateCallback.cs
  84. 2
      src/Avalonia.Native/ScreenImpl.cs
  85. 7
      src/Avalonia.Native/WindowImplBase.cs
  86. 7
      src/Avalonia.Native/avn.idl
  87. 2
      src/Avalonia.X11/Glx/Glx.cs
  88. 2
      src/Avalonia.X11/Glx/GlxConsts.cs
  89. 6
      src/Avalonia.X11/Glx/GlxContext.cs
  90. 8
      src/Avalonia.X11/Glx/GlxDisplay.cs
  91. 8
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
  92. 2
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  93. 2
      src/Avalonia.X11/Keysyms.cs
  94. 29
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  95. 2
      src/Avalonia.X11/TransparencyHelper.cs
  96. 16
      src/Avalonia.X11/X11Clipboard.cs
  97. 4
      src/Avalonia.X11/X11CursorFactory.cs
  98. 8
      src/Avalonia.X11/X11Enums.cs
  99. 2
      src/Avalonia.X11/X11Exception.cs
  100. 2
      src/Avalonia.X11/X11Framebuffer.cs

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -43,6 +43,7 @@
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
@ -95,6 +96,7 @@
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = "<group>"; };
AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
@ -140,6 +142,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */,
BC11A5BC2608D58F0017BAD0 /* automation.h */,
BC11A5BD2608D58F0017BAD0 /* automation.mm */,
1A1852DB23E05814008F0DED /* deadlock.mm */,
@ -288,6 +291,7 @@
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,

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

39
native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm

@ -0,0 +1,39 @@
#include "common.h"
namespace
{
id<NSObject> s_inhibitAppSleepHandle{};
}
class PlatformBehaviorInhibition : public ComSingleObject<IAvnPlatformBehaviorInhibition, &IID_IAvnCursorFactory>
{
public:
FORWARD_IUNKNOWN()
virtual void SetInhibitAppSleep(bool inhibitAppSleep, char* reason) override
{
START_COM_CALL;
@autoreleasepool
{
if (inhibitAppSleep && s_inhibitAppSleepHandle == nullptr)
{
NSActivityOptions options = NSActivityUserInitiatedAllowingIdleSystemSleep;
s_inhibitAppSleepHandle = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:[NSString stringWithUTF8String: reason]];
}
if (!inhibitAppSleep)
{
s_inhibitAppSleepHandle = nullptr;
}
}
}
};
extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition()
{
@autoreleasepool
{
return new PlatformBehaviorInhibition();
}
}

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];
}

1
native/Avalonia.Native/src/OSX/common.h

@ -26,6 +26,7 @@ extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings();
extern void SetAppMenu(IAvnMenu *menu);

11
native/Avalonia.Native/src/OSX/main.mm

@ -408,6 +408,17 @@ public:
return S_OK;
}
}
virtual HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreatePlatformBehaviorInhibition();
return S_OK;
}
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

2
readme.md

@ -1,3 +1,5 @@
[![GH_Banner](https://user-images.githubusercontent.com/552074/218457976-92e76834-9e22-4e35-acfa-aa50281bc0f9.png)](https://avaloniaui.net/xpf)
[![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg)
<br />

10
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@ -59,10 +59,12 @@ namespace ControlCatalog.Pages
};
StreamGeometry sg = new StreamGeometry();
var cntx = sg.Open();
cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
cntx.EndFigure(true);
using (var cntx = sg.Open())
{
cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
cntx.EndFigure(true);
}
_smileGeometry = sg.Clone();
}

2
samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs

@ -47,7 +47,7 @@ public class D3DMemoryHelper
MipLevels = 1,
SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
CpuAccessFlags = default,
OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
OptionFlags = ResourceOptionFlags.SharedKeyedmutex|ResourceOptionFlags.SharedNthandle,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
});
}

2
samples/GpuInterop/VulkanDemo/VulkanContext.cs

@ -173,7 +173,7 @@ public unsafe class VulkanContext : IDisposable
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties);
for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
{
var family = familyProperties[c];
var family = familyProperties[queueFamilyIndex];
if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit))
continue;

32
samples/GpuInterop/VulkanDemo/VulkanImage.cs

@ -4,10 +4,13 @@ using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Vulkan;
using SharpDX.DXGI;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
using Device = Silk.NET.Vulkan.Device;
using Format = Silk.NET.Vulkan.Format;
namespace GpuInterop.VulkanDemo;
@ -24,7 +27,6 @@ public unsafe class VulkanImage : IDisposable
private ImageView? _imageView { get; set; }
private DeviceMemory _imageMemory { get; set; }
private SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
private IntPtr _win32ShareHandle;
internal Image? InternalHandle { get; private set; }
internal Format Format { get; }
@ -60,7 +62,7 @@ public unsafe class VulkanImage : IDisposable
//MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
var handleType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit :
ExternalMemoryHandleTypeFlags.D3D11TextureBit :
ExternalMemoryHandleTypeFlags.OpaqueFDBit;
var externalMemoryCreateInfo = new ExternalMemoryImageCreateInfo
{
@ -108,14 +110,14 @@ public unsafe class VulkanImage : IDisposable
if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format);
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource>();
_win32ShareHandle = dxgi.SharedHandle;
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource1>();
handleImport = new ImportMemoryWin32HandleInfoKHR
{
PNext = &dedicatedAllocation,
SType = StructureType.ImportMemoryWin32HandleInfoKhr,
HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit,
Handle = _win32ShareHandle,
HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit,
Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
};
}
@ -185,11 +187,19 @@ public unsafe class VulkanImage : IDisposable
return fd;
}
public IPlatformHandle Export() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
new PlatformHandle(_win32ShareHandle,
KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) :
new PlatformHandle(new IntPtr(ExportFd()),
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
public IPlatformHandle Export()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using var dxgi = _d3dTexture2D!.QueryInterface<Resource1>();
return new PlatformHandle(
dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle);
}
else
return new PlatformHandle(new IntPtr(ExportFd()),
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
}
public ImageTiling Tiling => ImageTiling.Optimal;

1
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>

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}" />

11
samples/interop/WindowsInteropTest/Program.cs

@ -1,5 +1,4 @@
using System;
using Avalonia.Controls;
using ControlCatalog;
using Avalonia;
@ -15,7 +14,15 @@ namespace WindowsInteropTest
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting();
AppBuilder.Configure<App>()
.UseWin32()
.UseDirect2D1()
.With(new Win32PlatformOptions
{
UseWindowsUIComposition = false,
ShouldRenderOnUIThread = true // necessary for WPF
})
.SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm());
}
}

7
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net461</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
@ -10,9 +10,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj" />
<ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj">
<Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
<Name>ControlCatalog</Name>
</ProjectReference>
<ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
</Project>

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)

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;

64
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>
@ -277,8 +277,8 @@ namespace Avalonia
/// <param name="property">The property.</param>
/// <returns>True if the property is set, otherwise false.</returns>
/// <remarks>
/// Checks whether a value is assigned to the property, or that there is a binding to the
/// property that is producing a value other than <see cref="AvaloniaProperty.UnsetValue"/>.
/// Returns true if <paramref name="property"/> is a styled property which has a value
/// assigned to it or a binding targeting it; otherwise false.
/// </remarks>
public bool IsSet(AvaloniaProperty property)
{
@ -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);

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>

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

12
src/Avalonia.Base/Platform/IPlatformBehaviorInhibition.cs

@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace Avalonia.Platform
{
/// <summary>
/// Allows to inhibit platform specific behavior.
/// </summary>
public interface IPlatformBehaviorInhibition
{
Task SetInhibitAppSleep(bool inhibitAppSleep, string reason);
}
}

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

@ -26,6 +26,6 @@ namespace Avalonia.Platform
bool CurrentThreadIsLoopThread { get; }
event Action<DispatcherPriority?> Signaled;
event Action<DispatcherPriority?>? Signaled;
}
}

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

@ -7,9 +7,9 @@ namespace Avalonia.Platform.Storage;
/// </summary>
public sealed class FilePickerFileType
{
public FilePickerFileType(string name)
public FilePickerFileType(string? name)
{
Name = name;
Name = name ?? string.Empty;
}
/// <summary>

47
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore
private IDisposable? _subscription;
private bool _hasValue;
private TValue? _value;
private TValue? _defaultValue;
private bool _isDefaultValueInitialized;
protected BindingEntryBase(
ValueFrame frame,
@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore
protected abstract BindingValue<TValue> ConvertAndValidate(TSource value);
protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value);
protected abstract TValue GetDefaultValue(Type ownerType);
protected virtual void Start(bool produceValue)
{
@ -104,17 +107,6 @@ namespace Avalonia.PropertyStore
};
}
private void ClearValue()
{
if (_hasValue)
{
_hasValue = false;
_value = default;
if (_subscription is not null)
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
}
}
private void SetValue(BindingValue<TValue> value)
{
static void Execute(BindingEntryBase<TValue, TSource> instance, BindingValue<TValue> value)
@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore
LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value);
if (value.HasValue)
{
if (!instance._hasValue || !EqualityComparer<TValue>.Default.Equals(instance._value, value.Value))
{
instance._value = value.Value;
instance._hasValue = true;
if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
}
}
else if (value.Type != BindingValueType.DoNothing)
var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue();
if (!instance._hasValue || !EqualityComparer<TValue>.Default.Equals(instance._value, effectiveValue))
{
instance.ClearValue();
instance._value = effectiveValue;
instance._hasValue = true;
if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority);
instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
}
}
if (value.Type == BindingValueType.DoNothing)
return;
if (Dispatcher.UIThread.CheckAccess())
{
Execute(this, value);
@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore
_subscription = null;
Frame.OnBindingCompleted(this);
}
private TValue GetCachedDefaultValue()
{
if (!_isDefaultValueInitialized)
{
_defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType());
_isDefaultValueInitialized = true;
}
return _defaultValue!;
}
}
}

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;

49
src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs

@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
private T? _defaultValue;
private bool _isDefaultValueInitialized;
public LocalValueBindingObserver(ValueStore owner, StyledProperty<T> property)
{
@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore
public void OnNext(T value)
{
static void Execute(ValueStore owner, StyledProperty<T> property, T value)
static void Execute(LocalValueBindingObserver<T> instance, T value)
{
if (property.ValidateValue?.Invoke(value) != false)
owner.SetValue(property, value, BindingPriority.LocalValue);
else
owner.ClearLocalValue(property);
var owner = instance._owner;
var property = instance.Property;
if (property.ValidateValue?.Invoke(value) == false)
value = instance.GetCachedDefaultValue();
owner.SetValue(property, value, BindingPriority.LocalValue);
}
if (Dispatcher.UIThread.CheckAccess())
{
Execute(_owner, Property, value);
Execute(this, value);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = _owner;
var property = Property;
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => Execute(instance, property, newValue));
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
}
}
@ -74,11 +78,21 @@ namespace Avalonia.PropertyStore
LoggingUtils.LogIfNecessary(owner.Owner, property, value);
if (value.HasValue)
owner.SetValue(property, value.Value, BindingPriority.LocalValue);
else if (value.Type != BindingValueType.DataValidationError)
owner.ClearLocalValue(property);
{
var effectiveValue = value.Value;
if (property.ValidateValue?.Invoke(effectiveValue) == false)
effectiveValue = instance.GetCachedDefaultValue();
owner.SetValue(property, effectiveValue, BindingPriority.LocalValue);
}
else
{
owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
}
}
if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError)
return;
if (Dispatcher.UIThread.CheckAccess())
{
Execute(this, value);
@ -92,5 +106,16 @@ namespace Avalonia.PropertyStore
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
}
}
private T GetCachedDefaultValue()
{
if (!_isDefaultValueInitialized)
{
_defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
_isDefaultValueInitialized = true;
}
return _defaultValue!;
}
}
}

25
src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs

@ -1,5 +1,4 @@
using System;
using System.Security.Cryptography;
using Avalonia.Data;
using Avalonia.Threading;
@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
private T? _defaultValue;
private bool _isDefaultValueInitialized;
public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty<T> property)
{
@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore
if (value == AvaloniaProperty.UnsetValue)
{
owner.ClearLocalValue(property);
}
else if (value == BindingOperations.DoNothing)
{
// Do nothing!
owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
}
else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue))
{
@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore
}
else
{
owner.ClearLocalValue(property);
owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value);
}
}
if (value == BindingOperations.DoNothing)
return;
if (Dispatcher.UIThread.CheckAccess())
{
Execute(this, value);
@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
}
}
private T GetCachedDefaultValue()
{
if (!_isDefaultValueInitialized)
{
_defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
_isDefaultValueInitialized = true;
}
return _defaultValue!;
}
}
}

2
src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs

@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore
{
throw new NotSupportedException();
}
protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
}
}

2
src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs

@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore
return value;
}
protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
}
}

5
src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs

@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore
{
throw new NotSupportedException();
}
protected override object? GetDefaultValue(Type ownerType)
{
return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue;
}
}
}

67
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();
@ -380,23 +399,6 @@ namespace Avalonia.PropertyStore
}
}
/// <summary>
/// Called by non-LocalValue binding entries to re-evaluate the effective value when the
/// binding produces an unset value.
/// </summary>
/// <param name="property">The bound property.</param>
/// <param name="priority">The priority of binding which produced a new value.</param>
public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority)
{
Debug.Assert(priority != BindingPriority.LocalValue);
if (TryGetEffectiveValue(property, out var existing))
{
if (priority <= existing.Priority)
ReevaluateEffectiveValue(property, existing);
}
}
/// <summary>
/// Called by a <see cref="ValueFrame"/> when its <see cref="ValueFrame.IsActive"/>
/// state changes.
@ -507,7 +509,7 @@ namespace Avalonia.PropertyStore
if (existing == observer)
{
_localValueBindings?.Remove(property.Id);
ClearLocalValue(property);
ClearValue(property);
}
}
}
@ -633,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))
{
@ -654,7 +658,8 @@ namespace Avalonia.PropertyStore
property,
value,
priority,
null);
null,
overridden);
}
private int InsertFrame(ValueFrame frame)
@ -804,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/DefaultRenderTimer.cs

@ -1,6 +1,4 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Rendering
@ -59,7 +57,8 @@ namespace Avalonia.Rendering
}
}
public bool RunsInBackground => true;
/// <inheritdoc />
public virtual bool RunsInBackground => true;
/// <summary>
/// Starts the timer.

5
src/Avalonia.Base/Rendering/IRenderLoop.cs

@ -27,7 +27,10 @@ namespace Avalonia.Rendering
/// </summary>
/// <param name="i">The update task.</param>
void Remove(IRenderLoopTask i);
/// <summary>
/// Indicates if the rendering is done on a non-UI thread.
/// </summary>
bool RunsInBackground { get; }
}
}

1
src/Avalonia.Base/Rendering/IRenderTimer.cs

@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using Avalonia.Metadata;
namespace Avalonia.Rendering

1
src/Avalonia.Base/Rendering/RenderLoop.cs

@ -87,6 +87,7 @@ namespace Avalonia.Rendering
}
}
/// <inheritdoc />
public bool RunsInBackground => Timer.RunsInBackground;
private void TimerTick(TimeSpan time)

9
src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs

@ -8,13 +8,20 @@ namespace Avalonia.Rendering
/// <summary>
/// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms
/// </summary>
public class UiThreadRenderTimer : DefaultRenderTimer
{
/// <summary>
/// Initializes a new instance of the <see cref="UiThreadRenderTimer"/> class.
/// </summary>
/// <param name="framesPerSecond">The number of frames per second at which the loop should run.</param>
public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond)
{
}
/// <inheritdoc />
public override bool RunsInBackground => false;
/// <inheritdoc />
protected override IDisposable StartCore(Action<TimeSpan> tick)
{
bool cancelled = false;

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

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

2
src/Avalonia.Controls/Border.cs

@ -225,7 +225,7 @@ namespace Avalonia.Controls
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
_borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray);

2
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@ -80,7 +80,7 @@ namespace Avalonia.Controls
_subscription?.Dispose();
}
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc)
{

2
src/Avalonia.Controls/Grid.cs

@ -3256,7 +3256,7 @@ namespace Avalonia.Controls
/// <summary>
/// UpdateRenderBounds.
/// </summary>
public override void Render(DrawingContext drawingContext)
public sealed override void Render(DrawingContext drawingContext)
{
var grid = this.GetVisualParent<Grid>();

2
src/Avalonia.Controls/Image.cs

@ -73,7 +73,7 @@ namespace Avalonia.Controls
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
var source = Source;

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>

2
src/Avalonia.Controls/Panel.cs

@ -68,7 +68,7 @@ namespace Avalonia.Controls
/// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
var background = Background;
if (background != null)

2
src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs

@ -13,6 +13,6 @@ namespace Avalonia.Platform
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
/// </remarks>
event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
}
}

8
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -19,7 +19,7 @@ namespace Avalonia.Platform
/// <summary>
/// Gets or sets a method called when the minimized/maximized state of the window changes.
/// </summary>
Action<WindowState> WindowStateChanged { get; set; }
Action<WindowState>? WindowStateChanged { get; set; }
/// <summary>
/// Sets the title of the window.
@ -42,7 +42,7 @@ namespace Avalonia.Platform
/// <summary>
/// Called when a disabled window received input. Can be used to activate child windows.
/// </summary>
Action GotInputWhenDisabled { get; set; }
Action? GotInputWhenDisabled { get; set; }
/// <summary>
/// Enables or disables system window decorations (title bar, buttons, etc)
@ -68,7 +68,7 @@ namespace Avalonia.Platform
/// Gets or sets a method called before the underlying implementation is destroyed.
/// Return true to prevent the underlying implementation from closing.
/// </summary>
Func<WindowCloseReason, bool> Closing { get; set; }
Func<WindowCloseReason, bool>? Closing { get; set; }
/// <summary>
/// Gets a value to indicate if the platform was able to extend client area to non-client area.
@ -78,7 +78,7 @@ namespace Avalonia.Platform
/// <summary>
/// Gets or Sets an action that is called whenever one of the extend client area properties changed.
/// </summary>
Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
Action<bool>? ExtendClientAreaToDecorationsChanged { get; set; }
/// <summary>
/// Gets a flag that indicates if Managed decorations i.e. caption buttons are required.

13
src/Avalonia.Controls/PlatformInhibitionType.cs

@ -0,0 +1,13 @@
namespace Avalonia.Controls
{
/// <summary>
/// A platform specific behavior that can be inhibited.
/// </summary>
public enum PlatformInhibitionType
{
/// <summary>
/// When inhibited, prevents the app from being put to sleep or being given a lower priority when not in focus.
/// </summary>
AppSleep
}
}

2
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -534,7 +534,7 @@ namespace Avalonia.Controls.Presenters
}
/// <inheritdoc/>
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
_borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow);

35
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>
@ -388,7 +404,7 @@ namespace Avalonia.Controls.Presenters
TextLayout.Draw(context, new Point(left, top));
}
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
@ -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/AccessText.cs

@ -60,10 +60,9 @@ namespace Avalonia.Controls.Primitives
/// Renders the <see cref="AccessText"/> to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
protected internal override void RenderCore(DrawingContext context)
{
base.Render(context);
base.RenderCore(context);
int underscore = Text?.IndexOf('_') ?? -1;
if (underscore != -1 && ShowAccessKey)

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote
base.ArrangeCore(finalRect);
}
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
{

2
src/Avalonia.Controls/Shapes/Shape.cs

@ -193,7 +193,7 @@ namespace Avalonia.Controls.Shapes
set { SetValue(StrokeJoinProperty, value); }
}
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
var geometry = RenderedGeometry;

8
src/Avalonia.Controls/TextBlock.cs

@ -549,7 +549,13 @@ namespace Avalonia.Controls
/// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
public sealed override void Render(DrawingContext context)
{
RenderCore(context);
}
// Workaround to seal Render method, we need to make so because AccessText was overriding Render method which is sealed now.
internal protected virtual void RenderCore(DrawingContext context)
{
var background = Background;

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.Controls/TickBar.cs

@ -213,7 +213,7 @@ namespace Avalonia.Controls
///
/// Brush that use to fill ticks is specified by Fill property.
/// </summary>
public override void Render(DrawingContext dc)
public sealed override void Render(DrawingContext dc)
{
var size = new Size(Bounds.Width, Bounds.Height);
var range = Maximum - Minimum;

25
src/Avalonia.Controls/TopLevel.cs

@ -20,6 +20,7 @@ using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.Input.Platform;
using System.Linq;
using System.Threading.Tasks;
namespace Avalonia.Controls
{
@ -570,6 +571,30 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e);
/// <summary>
/// Requests a <see cref="PlatformInhibitionType"/> to be inhibited.
/// The behavior remains inhibited until the return value is disposed.
/// The available set of <see cref="PlatformInhibitionType"/>s depends on the platform.
/// If a behavior is inhibited on a platform where this type is not supported the request will have no effect.
/// </summary>
protected async Task<IDisposable> RequestPlatformInhibition(PlatformInhibitionType type, string reason)
{
var platformBehaviorInhibition = PlatformImpl?.TryGetFeature<IPlatformBehaviorInhibition>();
if (platformBehaviorInhibition == null)
{
return Disposable.Create(() => { });
}
switch (type)
{
case PlatformInhibitionType.AppSleep:
await platformBehaviorInhibition.SetInhibitAppSleep(true, reason);
return Disposable.Create(() => platformBehaviorInhibition.SetInhibitAppSleep(false, reason).Wait());
default:
return Disposable.Create(() => { });
}
}
/// <summary>
/// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a
/// warning if not found.

2
src/Avalonia.Controls/WindowBase.cs

@ -94,7 +94,7 @@ namespace Avalonia.Controls
private set { SetAndRaise(IsActiveProperty, ref _isActive, value); }
}
public Screens Screens { get; private set; }
public Screens Screens { get; }
/// <summary>
/// Gets or sets the owner of the window.

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

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

21
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -158,25 +158,4 @@ namespace Avalonia.Native
throw new NotImplementedException();
}
}
public class AvaloniaNativeMacOptions
{
private readonly IAvnMacOptions _opts;
private bool _showInDock;
internal AvaloniaNativeMacOptions(IAvnMacOptions opts)
{
_opts = opts;
ShowInDock = true;
}
public bool ShowInDock
{
get => _showInDock;
set
{
_showInDock = value;
_opts.SetShowInDock(value ? 1 : 0);
}
}
}
}

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;

20
src/Avalonia.Native/PlatformBehaviorInhibition.cs

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Avalonia.Native.Interop;
using Avalonia.Platform;
namespace Avalonia.Native
{
internal class PlatformBehaviorInhibition : IPlatformBehaviorInhibition
{
readonly IAvnPlatformBehaviorInhibition _native;
internal PlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition native)
=> _native = native;
public Task SetInhibitAppSleep(bool inhibitAppSleep, string reason)
{
_native.SetInhibitAppSleep(inhibitAppSleep ? 1 : 0, reason);
return Task.CompletedTask;
}
}
}

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;

7
src/Avalonia.Native/WindowImplBase.cs

@ -63,6 +63,7 @@ namespace Avalonia.Native
private GlPlatformSurface _glSurface;
private NativeControlHostImpl _nativeControlHost;
private IStorageProvider _storageProvider;
private PlatformBehaviorInhibition _platformBehaviorInhibition;
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature)
@ -88,6 +89,7 @@ namespace Avalonia.Native
_savedScaling = RenderScaling;
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
_storageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
_platformBehaviorInhibition = new PlatformBehaviorInhibition(_factory.CreatePlatformBehaviorInhibition());
var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
@ -521,6 +523,11 @@ namespace Avalonia.Native
return _storageProvider;
}
if (featureType == typeof(IPlatformBehaviorInhibition))
{
return _platformBehaviorInhibition;
}
return null;
}

7
src/Avalonia.Native/avn.idl

@ -503,6 +503,7 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateTrayIcon(IAvnTrayIcon** ppv);
HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv);
HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv);
HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv);
}
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -924,3 +925,9 @@ interface IAvnPlatformSettings : IUnknown
uint GetAccentColor();
void RegisterColorsChange(IAvnActionCallback* callback);
}
[uuid(12edf00d-5803-4d3f-9947-b4840e5e9372)]
interface IAvnPlatformBehaviorInhibition : IUnknown
{
void SetInhibitAppSleep(bool inhibitAppSleep, char* reason);
}

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;

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

@ -6,7 +6,7 @@ using Avalonia.Platform;
namespace Avalonia.X11.Glx
{
class GlxPlatformGraphics : IPlatformGraphics
internal class GlxPlatformGraphics : IPlatformGraphics
{
public GlxDisplay Display { get; private set; }
public bool CanCreateContexts => true;

2
src/Avalonia.X11/Keysyms.cs

@ -3,7 +3,7 @@
// ReSharper disable CommentTypo
namespace Avalonia.X11
{
enum X11Key
internal enum X11Key
{
VoidSymbol = 0xffffff /* Void symbol */,
BackSpace = 0xff08 /* Back space, back char */,

29
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -7,8 +7,7 @@ using Avalonia.Platform.Interop;
// ReSharper disable IdentifierTypo
namespace Avalonia.X11.NativeDialogs
{
static unsafe class Glib
internal static unsafe class Glib
{
private const string GlibName = "libglib-2.0.so.0";
private const string GObjectName = "libgobject-2.0.so.0";
@ -36,7 +35,7 @@ namespace Avalonia.X11.NativeDialogs
IntPtr destroy);
class ConnectedSignal : IDisposable
private class ConnectedSignal : IDisposable
{
private readonly IntPtr _instance;
private GCHandle _handle;
@ -75,7 +74,7 @@ namespace Avalonia.X11.NativeDialogs
}
static bool TimeoutHandler(IntPtr data)
private static bool TimeoutHandler(IntPtr data)
{
var handle = GCHandle.FromIntPtr(data);
var cb = (Func<bool>)handle.Target;
@ -95,7 +94,7 @@ namespace Avalonia.X11.NativeDialogs
s_pinnedHandler = TimeoutHandler;
}
static void AddTimeout(int priority, uint interval, Func<bool> callback)
private static void AddTimeout(int priority, uint interval, Func<bool> callback)
{
var handle = GCHandle.Alloc(callback);
g_timeout_add_full(priority, interval, s_pinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero);
@ -123,13 +122,13 @@ namespace Avalonia.X11.NativeDialogs
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct GSList
internal unsafe struct GSList
{
public readonly IntPtr Data;
public readonly GSList* Next;
}
enum GtkFileChooserAction
internal enum GtkFileChooserAction
{
Open,
Save,
@ -137,7 +136,7 @@ namespace Avalonia.X11.NativeDialogs
}
// ReSharper disable UnusedMember.Global
enum GtkResponseType
internal enum GtkResponseType
{
Help = -11,
Apply = -10,
@ -153,14 +152,14 @@ namespace Avalonia.X11.NativeDialogs
}
// ReSharper restore UnusedMember.Global
static unsafe class Gtk
internal static unsafe class Gtk
{
private static IntPtr s_display;
private const string GdkName = "libgdk-3.so.0";
private const string GtkName = "libgtk-3.so.0";
[DllImport(GtkName)]
static extern void gtk_main_iteration();
private static extern void gtk_main_iteration();
[DllImport(GtkName)]
@ -231,10 +230,10 @@ namespace Avalonia.X11.NativeDialogs
public static extern void gtk_widget_hide(IntPtr gtkWidget);
[DllImport(GtkName)]
static extern bool gtk_init_check(int argc, IntPtr argv);
private static extern bool gtk_init_check(int argc, IntPtr argv);
[DllImport(GdkName)]
static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
private static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
[DllImport(GdkName)]
public static extern IntPtr gdk_x11_window_get_xid(IntPtr window);
@ -244,13 +243,13 @@ namespace Avalonia.X11.NativeDialogs
public static extern IntPtr gtk_container_add(IntPtr container, IntPtr widget);
[DllImport(GdkName)]
static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);
private static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);
[DllImport(GdkName)]
static extern IntPtr gdk_display_get_default();
private static extern IntPtr gdk_display_get_default();
[DllImport(GtkName)]
static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags);
private static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags);
[DllImport(GdkName)]
public static extern void gdk_window_set_transient_for(IntPtr window, IntPtr parent);

2
src/Avalonia.X11/TransparencyHelper.cs

@ -3,7 +3,7 @@ using Avalonia.Controls;
namespace Avalonia.X11
{
class TransparencyHelper : IDisposable, X11Globals.IGlobalsSubscriber
internal class TransparencyHelper : IDisposable, X11Globals.IGlobalsSubscriber
{
private readonly X11Info _x11;
private readonly IntPtr _window;

16
src/Avalonia.X11/X11Clipboard.cs

@ -9,7 +9,7 @@ using Avalonia.Input.Platform;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
class X11Clipboard : IClipboard
internal class X11Clipboard : IClipboard
{
private readonly X11Info _x11;
private IDataObject _storedDataObject;
@ -33,12 +33,12 @@ namespace Avalonia.X11
}.Where(a => a != IntPtr.Zero).ToArray();
}
bool IsStringAtom(IntPtr atom)
private bool IsStringAtom(IntPtr atom)
{
return _textAtoms.Contains(atom);
}
Encoding GetStringEncoding(IntPtr atom)
private Encoding GetStringEncoding(IntPtr atom)
{
return (atom == _x11.Atoms.XA_STRING
|| atom == _x11.Atoms.OEMTEXT)
@ -213,7 +213,7 @@ namespace Avalonia.X11
}
}
Task<IntPtr[]> SendFormatRequest()
private Task<IntPtr[]> SendFormatRequest()
{
if (_requestedFormatsTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedFormatsTcs = new TaskCompletionSource<IntPtr[]>();
@ -222,7 +222,7 @@ namespace Avalonia.X11
return _requestedFormatsTcs.Task;
}
Task<object> SendDataRequest(IntPtr format)
private Task<object> SendDataRequest(IntPtr format)
{
if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedDataTcs = new TaskCompletionSource<object>();
@ -230,7 +230,7 @@ namespace Avalonia.X11
return _requestedDataTcs.Task;
}
bool HasOwner => XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) != IntPtr.Zero;
private bool HasOwner => XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) != IntPtr.Zero;
public async Task<string> GetTextAsync()
{
@ -252,7 +252,7 @@ namespace Avalonia.X11
return (string)await SendDataRequest(target);
}
void StoreAtomsInClipboardManager(IntPtr[] atoms)
private void StoreAtomsInClipboardManager(IntPtr[] atoms)
{
if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{

4
src/Avalonia.X11/X11CursorFactory.cs

@ -12,7 +12,7 @@ using Avalonia.Utilities;
namespace Avalonia.X11
{
partial class X11CursorFactory : ICursorFactory
internal partial class X11CursorFactory : ICursorFactory
{
private static readonly byte[] NullCursorData = new byte[] { 0 };
@ -142,7 +142,7 @@ namespace Avalonia.X11
}
}
class CursorImpl : ICursorImpl
internal class CursorImpl : ICursorImpl
{
public CursorImpl() { }
public CursorImpl(IntPtr handle) => Handle = handle;

8
src/Avalonia.X11/X11Enums.cs

@ -3,7 +3,7 @@ using System;
namespace Avalonia.X11
{
public enum Status
internal enum Status
{
Success = 0, /* everything's okay */
BadRequest = 1, /* bad request code */
@ -38,7 +38,7 @@ namespace Avalonia.X11
}
[Flags]
public enum XEventMask : int
internal enum XEventMask : int
{
NoEventMask = 0,
KeyPressMask = (1 << 0),
@ -69,7 +69,7 @@ namespace Avalonia.X11
}
[Flags]
public enum XModifierMask
internal enum XModifierMask
{
ShiftMask = (1 << 0),
LockMask = (1 << 1),
@ -89,7 +89,7 @@ namespace Avalonia.X11
}
[Flags]
public enum XCreateWindowFlags
internal enum XCreateWindowFlags
{
CWBackPixmap = (1 << 0),
CWBackPixel = (1 << 1),

2
src/Avalonia.X11/X11Exception.cs

@ -2,7 +2,7 @@ using System;
namespace Avalonia.X11
{
public class X11Exception : Exception
internal class X11Exception : Exception
{
public X11Exception(string message) : base(message)
{

2
src/Avalonia.X11/X11Framebuffer.cs

@ -5,7 +5,7 @@ using SkiaSharp;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
class X11Framebuffer : ILockedFramebuffer
internal class X11Framebuffer : ILockedFramebuffer
{
private readonly IntPtr _display;
private readonly IntPtr _xid;

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

Loading…
Cancel
Save