Browse Source

Merge branch 'master' into feature/toggleswitch-dragging

pull/4241/head
Jumar Macato 6 years ago
committed by GitHub
parent
commit
48172b1dc0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitmodules
  2. 52
      Avalonia.sln
  3. 13
      native/Avalonia.Native/inc/avalonia-native.h
  4. 13
      native/Avalonia.Native/src/OSX/window.h
  5. 289
      native/Avalonia.Native/src/OSX/window.mm
  6. 2
      nukebuild/Build.cs
  7. 76
      nukebuild/BuildTasksPatcher.cs
  8. 3
      nukebuild/_build.csproj
  9. 5
      samples/BindingDemo/MainWindow.xaml
  10. 4
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  11. 3
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  12. 55
      samples/ControlCatalog.NetCore/Program.cs
  13. 16
      samples/ControlCatalog/App.xaml.cs
  14. 6
      samples/ControlCatalog/MainView.xaml
  15. 7
      samples/ControlCatalog/MainView.xaml.cs
  16. 41
      samples/ControlCatalog/MainWindow.xaml
  17. 139
      samples/ControlCatalog/Pages/AcrylicPage.xaml
  18. 19
      samples/ControlCatalog/Pages/AcrylicPage.xaml.cs
  19. 12
      samples/ControlCatalog/Pages/BorderPage.xaml
  20. 14
      samples/ControlCatalog/Pages/ButtonPage.xaml
  21. 2
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  22. 4
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  23. 6
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  24. 14
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  25. 20
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  26. 8
      samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
  27. 69
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  28. 4
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  29. 19
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  30. 19
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs
  31. 6
      samples/ControlCatalog/SideBar.xaml
  32. 65
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  33. 4
      samples/RenderDemo/Pages/ClippingPage.xaml
  34. 4
      samples/RenderDemo/SideBar.xaml
  35. 1
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  36. 8
      src/Avalonia.Base/AvaloniaProperty.cs
  37. 73
      src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
  38. 32
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  39. 14
      src/Avalonia.Base/Data/Core/IPropertyInfo.cs
  40. 2
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  41. 32
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  42. 91
      src/Avalonia.Base/Data/Core/PropertyPath.cs
  43. 37
      src/Avalonia.Base/Data/Core/StreamNode.cs
  44. 20
      src/Avalonia.Base/Utilities/CharacterReader.cs
  45. 46
      src/Avalonia.Base/Utilities/KeywordParser.cs
  46. 42
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  47. 2
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  48. 60
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  49. 2
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  50. 5
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  51. 5
      src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
  52. 2
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  53. 23
      src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs
  54. 86
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  55. 117
      src/Avalonia.Controls/Chrome/TitleBar.cs
  56. 31
      src/Avalonia.Controls/ComboBox.cs
  57. 3
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  58. 117
      src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
  59. 38
      src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
  60. 5
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  61. 46
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  62. 1
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  63. 15
      src/Avalonia.Controls/Primitives/AccessText.cs
  64. 29
      src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
  65. 15
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  66. 1
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  67. 35
      src/Avalonia.Controls/TextBlock.cs
  68. 2
      src/Avalonia.Controls/TopLevel.cs
  69. 135
      src/Avalonia.Controls/Window.cs
  70. 24
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  71. 26
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  72. 4
      src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs
  73. 3
      src/Avalonia.Desktop/Avalonia.Desktop.csproj
  74. 12
      src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
  75. 96
      src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  76. 48
      src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
  77. 8
      src/Avalonia.Headless/Avalonia.Headless.csproj
  78. 94
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  79. 434
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  80. 201
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  81. 86
      src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs
  82. 342
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  83. 17
      src/Avalonia.Headless/IHeadlessWindow.cs
  84. 37
      src/Avalonia.Layout/WrapLayout/UvBounds.cs
  85. 45
      src/Avalonia.Layout/WrapLayout/UvMeasure.cs
  86. 25
      src/Avalonia.Layout/WrapLayout/WrapItem.cs
  87. 327
      src/Avalonia.Layout/WrapLayout/WrapLayout.cs
  88. 143
      src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs
  89. 86
      src/Avalonia.Native/WindowImpl.cs
  90. 16
      src/Avalonia.Native/WindowImplBase.cs
  91. 1
      src/Avalonia.Styling/Styling/Setter.cs
  92. 69
      src/Avalonia.Themes.Default/CaptionButtons.xaml
  93. 40
      src/Avalonia.Themes.Default/ComboBox.xaml
  94. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  95. 53
      src/Avalonia.Themes.Default/TitleBar.xaml
  96. 3
      src/Avalonia.Themes.Default/ToggleSwitch.xaml
  97. 4
      src/Avalonia.Themes.Default/Window.xaml
  98. 2
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  99. 14
      src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml
  100. 14
      src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml

2
.gitmodules

@ -3,4 +3,4 @@
url = https://github.com/kekekeks/Numerge.git
[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
url = https://github.com/kekekeks/XamlIl.git
url = https://github.com/kekekeks/XamlX.git

52
Avalonia.sln

@ -207,6 +207,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "sample
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1826,6 +1830,54 @@ Global
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhone.Build.0 = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU

13
native/Avalonia.Native/inc/avalonia-native.h

@ -205,6 +205,15 @@ enum AvnMenuItemToggleType
Radio
};
enum AvnExtendClientAreaChromeHints
{
AvnNoChrome = 0,
AvnSystemChrome = 0x01,
AvnPreferSystemChrome = 0x02,
AvnOSXThickTitleBar = 0x08,
AvnDefaultChrome = AvnSystemChrome,
};
AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{
public:
@ -279,6 +288,10 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
virtual HRESULT TakeFocusFromChildren() = 0;
virtual HRESULT SetExtendClientArea (bool enable) = 0;
virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) = 0;
virtual HRESULT GetExtendTitleBarHeight (double*ret) = 0;
virtual HRESULT SetExtendTitleBarHeight (double value) = 0;
};
AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown

13
native/Avalonia.Native/src/OSX/window.h

@ -3,9 +3,6 @@
class WindowBaseImpl;
@interface AutoFitContentVisualEffectView : NSVisualEffectView
@end
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
@ -15,6 +12,14 @@ class WindowBaseImpl;
-(AvnPixelSize) getPixelSize;
@end
@interface AutoFitContentView : NSView
-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
-(void) ShowTitleBar: (bool) show;
-(void) SetTitleBarHeightHint: (double) height;
-(void) SetContent: (NSView* _Nonnull) content;
-(void) ShowBlur: (bool) show;
@end
@interface AvnWindow : NSWindow <NSWindowDelegate>
+(void) closeAll;
-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
@ -27,6 +32,8 @@ class WindowBaseImpl;
-(void) showWindowMenuWithAppMenu;
-(void) applyMenu:(NSMenu* _Nullable)menu;
-(double) getScaling;
-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
@end
struct INSWindowHolder

289
native/Avalonia.Native/src/OSX/window.mm

@ -6,8 +6,6 @@
#include <OpenGL/gl.h>
#include "rendertarget.h"
class WindowBaseImpl : public virtual ComSingleObject<IAvnWindowBase, &IID_IAvnWindowBase>, public INSWindowHolder
{
private:
@ -20,7 +18,7 @@ public:
View = NULL;
Window = NULL;
}
NSVisualEffectView* VisualEffect;
AutoFitContentView* StandardContainer;
AvnView* View;
AvnWindow* Window;
ComPtr<IAvnWindowBaseEvents> BaseEvents;
@ -39,6 +37,7 @@ public:
_glContext = gl;
renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl];
View = [[AvnView alloc] initWithParent:this];
StandardContainer = [[AutoFitContentView new] initWithContent:View];
Window = [[AvnWindow alloc] initWithParent:this];
@ -49,12 +48,8 @@ public:
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
VisualEffect = [AutoFitContentVisualEffectView new];
[VisualEffect setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
[VisualEffect setMaterial:NSVisualEffectMaterialLight];
[VisualEffect setAutoresizesSubviews:true];
[Window setContentView: View];
[Window setOpaque:false];
[Window setContentView: StandardContainer];
}
virtual HRESULT ObtainNSWindowHandle(void** ret) override
@ -410,12 +405,7 @@ public:
virtual HRESULT SetBlurEnabled (bool enable) override
{
[Window setContentView: enable ? VisualEffect : View];
if(enable)
{
[VisualEffect addSubview:View];
}
[StandardContainer ShowBlur:enable];
return S_OK;
}
@ -492,6 +482,8 @@ private:
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
bool _isClientAreaExtended;
AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP()
@ -505,6 +497,8 @@ private:
ComPtr<IAvnWindowEvents> WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
_isClientAreaExtended = false;
_extendClientHints = AvnDefaultChrome;
_fullScreenActive = false;
_canResize = true;
_decorations = SystemDecorationsFull;
@ -523,8 +517,20 @@ private:
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
NSView *titlebarView = [subview subviews][0];
for (id button in titlebarView.subviews) {
if ([button isKindOfClass:[NSButton class]]) {
[button setHidden: (_decorations != SystemDecorationsFull)];
if ([button isKindOfClass:[NSButton class]])
{
if(_isClientAreaExtended)
{
auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
[button setHidden: !wantsChrome];
}
else
{
[button setHidden: (_decorations != SystemDecorationsFull)];
}
[button setWantsLayer:true];
}
}
}
@ -600,6 +606,35 @@ private:
if(_lastWindowState != state)
{
if(_isClientAreaExtended)
{
if(_lastWindowState == FullScreen)
{
// we exited fs.
if(_extendClientHints & AvnOSXThickTitleBar)
{
Window.toolbar = [NSToolbar new];
Window.toolbar.showsBaselineSeparator = false;
}
[Window setTitlebarAppearsTransparent:true];
[StandardContainer setFrameSize: StandardContainer.frame.size];
}
else if(state == FullScreen)
{
// we entered fs.
if(_extendClientHints & AvnOSXThickTitleBar)
{
Window.toolbar = nullptr;
}
[Window setTitlebarAppearsTransparent:false];
[StandardContainer setFrameSize: StandardContainer.frame.size];
}
}
_lastWindowState = state;
WindowEvents->WindowStateChanged(state);
}
@ -656,8 +691,6 @@ private:
return S_OK;
}
auto currentFrame = [Window frame];
UpdateStyle();
HideOrShowTrafficLights();
@ -790,6 +823,81 @@ private:
return S_OK;
if([Window isKeyWindow])
[Window makeFirstResponder: View];
return S_OK;
}
virtual HRESULT SetExtendClientArea (bool enable) override
{
_isClientAreaExtended = enable;
if(enable)
{
Window.titleVisibility = NSWindowTitleHidden;
[Window setTitlebarAppearsTransparent:true];
auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
if (wantsTitleBar)
{
[StandardContainer ShowTitleBar:true];
}
else
{
[StandardContainer ShowTitleBar:false];
}
if(_extendClientHints & AvnOSXThickTitleBar)
{
Window.toolbar = [NSToolbar new];
Window.toolbar.showsBaselineSeparator = false;
}
else
{
Window.toolbar = nullptr;
}
}
else
{
Window.titleVisibility = NSWindowTitleVisible;
Window.toolbar = nullptr;
[Window setTitlebarAppearsTransparent:false];
View.layer.zPosition = 0;
}
[Window setIsExtended:enable];
HideOrShowTrafficLights();
UpdateStyle();
return S_OK;
}
virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override
{
_extendClientHints = hints;
SetExtendClientArea(_isClientAreaExtended);
return S_OK;
}
virtual HRESULT GetExtendTitleBarHeight (double*ret) override
{
if(ret == nullptr)
{
return E_POINTER;
}
*ret = [Window getExtendedTitleBarHeight];
return S_OK;
}
virtual HRESULT SetExtendTitleBarHeight (double value) override
{
[StandardContainer SetTitleBarHeightHint:value];
return S_OK;
}
@ -802,8 +910,9 @@ private:
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
[Window setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable];
Window.styleMask = Window.styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
Window.styleMask = Window.styleMask & ~NSWindowStyleMaskFullSizeContentView;
[Window toggleFullScreen:nullptr];
}
@ -951,19 +1060,120 @@ protected:
{
s |= NSWindowStyleMaskMiniaturizable;
}
if(_isClientAreaExtended)
{
s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
}
return s;
}
};
NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil];
@implementation AutoFitContentVisualEffectView
@implementation AutoFitContentView
{
NSVisualEffectView* _titleBarMaterial;
NSBox* _titleBarUnderline;
NSView* _content;
NSVisualEffectView* _blurBehind;
double _titleBarHeightHint;
bool _settingSize;
}
-(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
{
_titleBarHeightHint = -1;
_content = content;
_settingSize = false;
[self setAutoresizesSubviews:true];
[self setWantsLayer:true];
_titleBarMaterial = [NSVisualEffectView new];
[_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
[_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
[_titleBarMaterial setWantsLayer:true];
_titleBarMaterial.hidden = true;
_titleBarUnderline = [NSBox new];
_titleBarUnderline.boxType = NSBoxSeparator;
_titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
_titleBarUnderline.hidden = true;
[self addSubview:_titleBarMaterial];
[self addSubview:_titleBarUnderline];
_blurBehind = [NSVisualEffectView new];
[_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
[_blurBehind setMaterial:NSVisualEffectMaterialLight];
[_blurBehind setWantsLayer:true];
_blurBehind.hidden = true;
[self addSubview:_blurBehind];
[self addSubview:_content];
[self setWantsLayer:true];
return self;
}
-(void) ShowBlur:(bool)show
{
_blurBehind.hidden = !show;
}
-(void) ShowTitleBar: (bool) show
{
_titleBarMaterial.hidden = !show;
_titleBarUnderline.hidden = !show;
}
-(void) SetTitleBarHeightHint: (double) height
{
_titleBarHeightHint = height;
[self setFrameSize:self.frame.size];
}
-(void)setFrameSize:(NSSize)newSize
{
[super setFrameSize:newSize];
if([[self subviews] count] == 0)
if(_settingSize)
{
return;
[[self subviews][0] setFrameSize: newSize];
}
_settingSize = true;
[super setFrameSize:newSize];
[_blurBehind setFrameSize:newSize];
[_content setFrameSize:newSize];
auto window = objc_cast<AvnWindow>([self window]);
// TODO get actual titlebar size
double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
NSRect tbar;
tbar.origin.x = 0;
tbar.origin.y = newSize.height - height;
tbar.size.width = newSize.width;
tbar.size.height = height;
[_titleBarMaterial setFrame:tbar];
tbar.size.height = height < 1 ? 0 : 1;
[_titleBarUnderline setFrame:tbar];
_settingSize = false;
}
-(void) SetContent: (NSView* _Nonnull) content
{
if(content != nullptr)
{
[content removeFromSuperview];
[self addSubview:content];
_content = content;
}
}
@end
@ -1523,15 +1733,43 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _canBecomeKeyAndMain;
bool _closed;
bool _isEnabled;
bool _isExtended;
AvnMenu* _menu;
double _lastScaling;
}
-(void) setIsExtended:(bool)value;
{
_isExtended = value;
}
-(double) getScaling
{
return _lastScaling;
}
-(double) getExtendedTitleBarHeight
{
if(_isExtended)
{
for (id subview in self.contentView.superview.subviews)
{
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
{
NSView *titlebarView = [subview subviews][0];
return (double)titlebarView.frame.size.height;
}
}
return -1;
}
else
{
return 0;
}
}
+(void)closeAll
{
NSArray<NSWindow*>* windows = [NSArray arrayWithArray:[NSApp windows]];
@ -1650,6 +1888,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self setOpaque:NO];
[self setBackgroundColor: [NSColor clearColor]];
[self invalidateShadow];
_isExtended = false;
return self;
}

2
nukebuild/Build.cs

@ -268,6 +268,8 @@ partial class Build : NukeBuild
.DependsOn(CreateIntermediateNugetPackages)
.Executes(() =>
{
BuildTasksPatcher.PatchBuildTasksInPackage(Parameters.NugetIntermediateRoot / "Avalonia.Build.Tasks." +
Parameters.Version + ".nupkg");
var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config");
EnsureCleanDirectory(Parameters.NugetRoot);
if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,

76
nukebuild/BuildTasksPatcher.cs

@ -0,0 +1,76 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using ILRepacking;
using Mono.Cecil;
public class BuildTasksPatcher
{
public static void PatchBuildTasksInPackage(string packagePath)
{
using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
ZipArchiveMode.Update))
{
foreach (var entry in archive.Entries.ToList())
{
if (entry.Name == "Avalonia.Build.Tasks.dll")
{
var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll");
var output = temp + ".output";
var patched = new MemoryStream();
try
{
entry.ExtractToFile(temp, true);
var repack = new ILRepacking.ILRepack(new RepackOptions()
{
Internalize = true,
InputAssemblies = new[]
{
temp, typeof(Mono.Cecil.AssemblyDefinition).Assembly.GetModules()[0]
.FullyQualifiedName
},
SearchDirectories = new string[0],
OutputFile = output
});
repack.Repack();
// 'hurr-durr assembly with the same name is already loaded' prevention
using (var asm = AssemblyDefinition.ReadAssembly(output,
new ReaderParameters { ReadWrite = true, InMemory = true, }))
{
asm.Name = new AssemblyNameDefinition(
"Avalonia.Build.Tasks."
+ Guid.NewGuid().ToString().Replace("-", ""),
new Version(0, 0, 0));
asm.Write(patched);
patched.Position = 0;
}
}
finally
{
try
{
if (File.Exists(temp))
File.Delete(temp);
if (File.Exists(output))
File.Delete(output);
}
catch
{
//ignore
}
}
var fn = entry.FullName;
entry.Delete();
var newEntry = archive.CreateEntry(fn, CompressionLevel.Optimal);
using (var s = newEntry.Open())
patched.CopyTo(s);
}
}
}
}
}

3
nukebuild/_build.csproj

@ -14,6 +14,9 @@
<PackageReference Include="xunit.runner.console" Version="2.3.1" />
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" />
</ItemGroup>
<ItemGroup>

5
samples/BindingDemo/MainWindow.xaml

@ -5,7 +5,8 @@
xmlns:local="clr-namespace:BindingDemo"
Title="AvaloniaUI Bindings Test"
Width="800"
Height="600">
Height="600"
x:DataType="vm:MainWindowViewModel">
<Window.Styles>
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="18"/>
@ -60,7 +61,7 @@
<TextBlock FontSize="16" Text="Scheduler"/>
<TextBox Watermark="Background Thread" Text="{Binding CurrentTime, Mode=OneWay}"/>
<TextBlock FontSize="16" Text="Stream Operator"/>
<TextBox Watermark="StreamOperator" Text="{Binding CurrentTimeObservable^, Mode=OneWay}"/>
<TextBox Watermark="StreamOperator" Text="{CompiledBinding CurrentTimeObservable^, Mode=OneWay}"/>
</StackPanel>
</StackPanel>
</StackPanel>

4
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -53,7 +53,7 @@ namespace BindingDemo.ViewModels
});
CurrentTimeObservable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
.Select(x => DateTimeOffset.Now.ToString());
.Select(x => DateTimeOffset.Now);
}
public ObservableCollection<TestItem> Items { get; }
@ -90,7 +90,7 @@ namespace BindingDemo.ViewModels
private set { this.RaiseAndSetIfChanged(ref _currentTime, value); }
}
public IObservable<string> CurrentTimeObservable { get; }
public IObservable<DateTimeOffset> CurrentTimeObservable { get; }
public ReactiveCommand<object, Unit> StringValueCommand { get; }
public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();

3
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -7,12 +7,13 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001"/>
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />
</ItemGroup>

55
samples/ControlCatalog.NetCore/Program.cs

@ -3,9 +3,16 @@ using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Dialogs;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Headless;
using Avalonia.LogicalTree;
using Avalonia.Skia;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using Avalonia.Dialogs;
namespace ControlCatalog.NetCore
{
@ -40,6 +47,52 @@ namespace ControlCatalog.NetCore
SilenceConsole();
return builder.StartLinuxFbDev(args, scaling: GetScaling());
}
else if (args.Contains("--vnc"))
{
return builder.StartWithHeadlessVncPlatform(null, 5901, args, ShutdownMode.OnMainWindowClose);
}
else if (args.Contains("--full-headless"))
{
return builder
.UseHeadless(true)
.AfterSetup(_ =>
{
DispatcherTimer.RunOnce(async () =>
{
var window = ((IClassicDesktopStyleApplicationLifetime)Application.Current.ApplicationLifetime)
.MainWindow;
var tc = window.GetLogicalDescendants().OfType<TabControl>().First();
foreach (var page in tc.Items.Cast<TabItem>().ToList())
{
// Skip DatePicker because of some layout bug in grid
if (page.Header.ToString() == "DatePicker")
continue;
Console.WriteLine("Selecting " + page.Header);
tc.SelectedItem = page;
await Task.Delay(500);
}
Console.WriteLine("Selecting the first page");
tc.SelectedItem = tc.Items.OfType<object>().First();
await Task.Delay(500);
Console.WriteLine("Clicked through all pages, triggering GC");
for (var c = 0; c < 3; c++)
{
GC.Collect(2, GCCollectionMode.Forced);
await Task.Delay(500);
}
void FormatMem(string metric, long bytes)
{
Console.WriteLine(metric + ": " + bytes / 1024 / 1024 + "MB");
}
FormatMem("GC allocated bytes", GC.GetTotalMemory(true));
FormatMem("WorkingSet64", Process.GetCurrentProcess().WorkingSet64);
}, TimeSpan.FromSeconds(1));
})
.StartWithClassicDesktopLifetime(args);
}
else if (args.Contains("--drm"))
{
SilenceConsole();

16
samples/ControlCatalog/App.xaml.cs

@ -11,25 +11,17 @@ namespace ControlCatalog
{
public static Styles FluentDark = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("resm:Avalonia.Themes.Fluent.Accents.FluentDark.xaml?assembly=Avalonia.Themes.Fluent")
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentDark.xaml")
},
};
public static Styles FluentLight = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("resm:Avalonia.Themes.Fluent.Accents.FluentLight.xaml?assembly=Avalonia.Themes.Fluent")
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentLight.xaml")
},
};

6
samples/ControlCatalog/MainView.xaml

@ -2,7 +2,7 @@
xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.MainView"
Background="Transparent"
Background="Black"
Foreground="{DynamicResource ThemeForegroundBrush}"
FontSize="{DynamicResource FontSizeNormal}">
<Grid>
@ -14,6 +14,7 @@
</Style>
</Grid.Styles>
<TabControl Classes="sidebar" Name="Sidebar">
<TabItem Header="Acrylic"><pages:AcrylicPage/></TabItem>
<TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
<TabItem Header="Border"><pages:BorderPage/></TabItem>
<TabItem Header="Button"><pages:ButtonPage/></TabItem>
@ -66,6 +67,7 @@
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabItem Header="Window Customizations"><pages:WindowCustomizationsPage/></TabItem>
<TabControl.Tag>
<StackPanel Width="115" Spacing="4" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="8">
<ComboBox x:Name="Decorations" SelectedIndex="0">
@ -79,7 +81,7 @@
<ComboBoxItem>Simple - Light</ComboBoxItem>
<ComboBoxItem>Simple - Dark</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="TransparencyLevels" SelectedIndex="0">
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>

7
samples/ControlCatalog/MainView.xaml.cs

@ -58,13 +58,6 @@ namespace ControlCatalog
if (VisualRoot is Window window)
window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex;
};
var transparencyLevels = this.Find<ComboBox>("TransparencyLevels");
transparencyLevels.SelectionChanged += (sender, e) =>
{
if (VisualRoot is Window window)
window.TransparencyLevelHint = (WindowTransparencyLevel)transparencyLevels.SelectedIndex;
};
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)

41
samples/ControlCatalog/MainWindow.xaml

@ -7,7 +7,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}">
ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}"
ExtendClientAreaChromeHints="{Binding ChromeHints}"
ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
TransparencyLevelHint="{Binding TransparencyLevel}"
x:Name="MainWindow"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="File">
@ -56,20 +61,30 @@
</NativeMenu>
</NativeMenu.Menu>
<Window.DataTemplates>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView />
</DataTemplate>
</Window.DataTemplates>
<DockPanel LastChildFill="True">
<Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" />
</MenuItem>
</Menu>
<local:MainView />
</DockPanel>
<Panel>
<Panel Margin="{Binding #MainWindow.OffScreenMargin}">
<DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}">
<Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" />
</MenuItem>
</Menu>
<local:MainView />
</DockPanel>
</Panel>
<Border IsVisible="{Binding ExtendClientAreaEnabled}" BorderThickness="1 1 1 0" CornerRadius="4 4 0 0" BorderBrush="#55000000" Height="22" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="250 8 0 0">
<Border.Background>
<SolidColorBrush Color="White" Opacity="0.7" />
</Border.Background>
<TextBlock Margin="5 5 5 0" Text="Content In Title Bar" />
</Border>
</Panel>
</Window>

139
samples/ControlCatalog/Pages/AcrylicPage.xaml

@ -0,0 +1,139 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.AcrylicPage">
<Border Padding="20" HorizontalAlignment="Center">
<StackPanel Spacing="20">
<ExperimentalAcrylicBorder Width="660" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="White"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
<StackPanel Spacing="5" Margin="40 10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="TintOpacity" Foreground="Black" />
<Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" Width="400" />
<TextBlock Text="{Binding #TintOpacitySlider.Value}" Foreground="Black" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MaterialOpacity" Foreground="Black" />
<Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" Width="400" />
<TextBlock Text="{Binding #MaterialOpacitySlider.Value}" Foreground="Black" />
</StackPanel>
</StackPanel>
</ExperimentalAcrylicBorder>
<StackPanel Orientation="Horizontal" Spacing="20">
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#FF0000"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#00FF00"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#000000"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="20">
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#0000FF"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#FFFF00"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#000000"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="20">
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="White"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#3c3c3c"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="White"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
<ExperimentalAcrylicBorder Height="200" Width="660" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="Red"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
</Border>
</UserControl>

19
samples/ControlCatalog/Pages/AcrylicPage.xaml.cs

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class AcrylicPage : UserControl
{
public AcrylicPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

12
samples/ControlCatalog/Pages/BorderPage.xaml

@ -9,27 +9,27 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16">
<Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16">
<TextBlock>Border</TextBlock>
</Border>
<Border Background="{DynamicResource ThemeAccentBrush2}"
BorderBrush="{DynamicResource ThemeAccentBrush}"
<Border Background="{DynamicResource SystemAccentColorDark1}"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderThickness="4"
Padding="16">
<TextBlock>Border and Background</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource ThemeAccentBrush}"
<Border BorderBrush="{DynamicResource SystemAccentColor}"
BorderThickness="4"
CornerRadius="8"
Padding="16">
<TextBlock>Rounded Corners</TextBlock>
</Border>
<Border Background="{DynamicResource ThemeAccentBrush2}"
<Border Background="{DynamicResource SystemAccentColor}"
CornerRadius="8"
Padding="16">
<TextBlock>Rounded Corners</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource ThemeAccentBrush2}" Width="100" Height="100"
<Border Width="100" Height="100"
BorderThickness="0"
Background="White"
CornerRadius="100" ClipToBounds="True">

14
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -9,10 +9,10 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8" Width="150">
<Button>Button</Button>
<StackPanel Orientation="Vertical" Spacing="8" Width="200">
<Button>Standard XAML Button</Button>
<Button Foreground="White">Foreground</Button>
<Button Background="{DynamicResource ThemeAccentBrush}">Background</Button>
<Button Background="{DynamicResource SystemAccentColor}">Background</Button>
<Button IsEnabled="False">Disabled</Button>
<Button Content="Re-themed">
<Button.Styles>
@ -32,10 +32,10 @@
<StackPanel Orientation="Vertical" Spacing="8" Width="150">
<Button BorderThickness="0">No Border</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}">Border Color</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4">Thick Border</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4" IsEnabled="False">Disabled</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
<Button BorderBrush="{DynamicResource SystemAccentColor}">Border Color</Button>
<Button BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="4">Thick Border</Button>
<Button BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="4" IsEnabled="False">Disabled</Button>
<Button BorderBrush="{DynamicResource SystemAccentColor}" KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
</StackPanel>
</StackPanel>
</StackPanel>

2
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@ -16,7 +16,7 @@
<ComboBox SelectedIndex="0">
<ComboBoxItem>
<Panel>
<Rectangle Fill="{DynamicResource ThemeAccentBrush}"/>
<Rectangle Fill="{DynamicResource SystemAccentColor}"/>
<TextBlock Margin="8">Control Items</TextBlock>
</Panel>
</ComboBoxItem>

4
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -9,7 +9,7 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Border Background="{DynamicResource ThemeAccentBrush}"
<Border Background="{DynamicResource SystemAccentColor}"
Margin="16"
Padding="48,48,48,48">
<Border.ContextMenu>
@ -34,7 +34,7 @@
</Border.ContextMenu>
<TextBlock Text="Defined in XAML"/>
</Border>
<Border Background="{DynamicResource ThemeAccentBrush}"
<Border Background="{DynamicResource SystemAccentColor}"
Margin="16"
Padding="48,48,48,48">
<Border.ContextMenu>

6
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@ -10,15 +10,15 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel>
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeText">
<Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16" Name="DragMeText">
<TextBlock Name="DragStateText">Drag Me</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeCustom">
<Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16" Name="DragMeCustom">
<TextBlock Name="DragStateCustom">Drag Me (custom)</TextBlock>
</Border>
</StackPanel>
<Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16"
<Border Background="{DynamicResource SystemAccentColorDark1}" Padding="16"
DragDrop.AllowDrop="True">
<TextBlock Name="DropState">Drop some text or files here</TextBlock>
</Border>

14
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -12,6 +12,8 @@
<ComboBoxItem>Stack - Horizontal</ComboBoxItem>
<ComboBoxItem>UniformGrid - Vertical</ComboBoxItem>
<ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem>
<ComboBoxItem>WrapLayout - Horizontal</ComboBoxItem>
<ComboBoxItem>WrapLayout - Veritcal</ComboBoxItem>
</ComboBox>
<Button Command="{Binding AddItem}">Add Item</Button>
<Button Command="{Binding RandomizeHeights}">Randomize Heights</Button>
@ -24,12 +26,16 @@
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}">
<ItemsRepeater.Styles>
<Style Selector="Border:pointerover">
<Setter Property="Width" Value="200" />
</Style>
</ItemsRepeater.Styles>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Focusable="True"
Background="{Binding Background}"
Height="{Binding Height}"
Text="{Binding Text}"/>
<Border Background="Purple" BorderThickness="2" BorderBrush="Green" MinHeight="100" MinWidth="100">
<TextBlock Focusable="True" Height="{Binding Height}" Text="{Binding Text}" Foreground="White" />
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>

20
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@ -79,6 +79,26 @@ namespace ControlCatalog.Pages
MinItemHeight = 200,
};
break;
case 4:
_scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
_scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
_repeater.Layout = new WrapLayout
{
Orientation = Orientation.Vertical,
HorizontalSpacing = 20,
VerticalSpacing = 20
};
break;
case 5:
_scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
_scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
_repeater.Layout = new WrapLayout
{
Orientation = Orientation.Horizontal,
HorizontalSpacing = 20,
VerticalSpacing = 20
};
break;
}
}

8
samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml

@ -11,10 +11,10 @@
RowDefinitions="24,Auto,24"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="0"/>
<Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="0" Grid.Row="1"/>
<Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="2" Grid.Row="1"/>
<Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="2"/>
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="1" Grid.Row="0"/>
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="0" Grid.Row="1"/>
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="2" Grid.Row="1"/>
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="1" Grid.Row="2"/>
<LayoutTransformControl Name="layoutTransform" Grid.Column="1" Grid.Row="1">
<LayoutTransformControl.LayoutTransform>

69
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -64,51 +64,42 @@
<TextDecorationCollection>
<TextDecoration
Location="Overline"
PenThicknessUnit="Pixel">
<TextDecoration.Pen>
<Pen Thickness="2">
<Pen.Brush>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Green"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
StrokeThicknessUnit="Pixel"
StrokeThickness="2">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Green"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
<TextDecoration
Location="Strikethrough"
PenThicknessUnit="Pixel">
<TextDecoration.Pen>
<Pen Thickness="1">
<Pen.Brush>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
StrokeThicknessUnit="Pixel"
StrokeThickness="1">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
<TextDecoration
Location="Underline"
PenThicknessUnit="Pixel">
<TextDecoration.Pen>
<Pen Thickness="2">
<Pen.Brush>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="1" Color="Red"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
StrokeThicknessUnit="Pixel"
StrokeThickness="2">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="1" Color="Red"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
</TextDecorationCollection>
</TextBlock.TextDecorations>

4
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -12,7 +12,7 @@
HorizontalAlignment="Center">
<Border Grid.Column="0"
Grid.Row="1"
Background="{DynamicResource ThemeAccentBrush}"
Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Tip="This is a ToolTip">
@ -26,7 +26,7 @@
<Border Name="Border"
Grid.Column="1"
Grid.Row="1"
Background="{DynamicResource ThemeAccentBrush}"
Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Placement="Bottom">

19
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@ -0,0 +1,19 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.WindowCustomizationsPage">
<StackPanel Spacing="10" Margin="25">
<CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" />
<CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" />
<CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" />
<Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" />
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>
<ComboBoxItem>AcrylicBlur</ComboBoxItem>
</ComboBox>
</StackPanel>
</UserControl>

19
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class WindowCustomizationsPage : UserControl
{
public WindowCustomizationsPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

6
samples/ControlCatalog/SideBar.xaml

@ -12,7 +12,7 @@
<Style Selector="TabControl.sidebar">
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Padding" Value="8 0 0 0"/>
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}"/>
<Setter Property="Template">
<ControlTemplate>
<Border
@ -74,12 +74,12 @@
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Background" Value="{DynamicResource SystemAccentColorLight2}"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
<Setter Property="Background" Value="{DynamicResource SystemAccentColorLight1}"/>
</Style>
</Styles>

65
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -3,6 +3,8 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Dialogs;
using Avalonia.Platform;
using System;
using ReactiveUI;
namespace ControlCatalog.ViewModels
@ -14,6 +16,12 @@ namespace ControlCatalog.ViewModels
private bool _isMenuItemChecked = true;
private WindowState _windowState;
private WindowState[] _windowStates;
private int _transparencyLevel;
private ExtendClientAreaChromeHints _chromeHints;
private bool _extendClientAreaEnabled;
private bool _systemTitleBarEnabled;
private bool _preferSystemChromeEnabled;
private double _titleBarHeight;
public MainWindowViewModel(IManagedNotificationManager notificationManager)
{
@ -62,6 +70,63 @@ namespace ControlCatalog.ViewModels
WindowState.Maximized,
WindowState.FullScreen,
};
this.WhenAnyValue(x => x.SystemTitleBarEnabled, x=>x.PreferSystemChromeEnabled)
.Subscribe(x =>
{
var hints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
if(x.Item1)
{
hints |= ExtendClientAreaChromeHints.SystemChrome;
}
if(x.Item2)
{
hints |= ExtendClientAreaChromeHints.PreferSystemChrome;
}
ChromeHints = hints;
});
SystemTitleBarEnabled = true;
TitleBarHeight = -1;
}
public int TransparencyLevel
{
get { return _transparencyLevel; }
set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); }
}
public ExtendClientAreaChromeHints ChromeHints
{
get { return _chromeHints; }
set { this.RaiseAndSetIfChanged(ref _chromeHints, value); }
}
public bool ExtendClientAreaEnabled
{
get { return _extendClientAreaEnabled; }
set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); }
}
public bool SystemTitleBarEnabled
{
get { return _systemTitleBarEnabled; }
set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); }
}
public bool PreferSystemChromeEnabled
{
get { return _preferSystemChromeEnabled; }
set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); }
}
public double TitleBarHeight
{
get { return _titleBarHeight; }
set { this.RaiseAndSetIfChanged(ref _titleBarHeight, value); }
}
public WindowState WindowState

4
samples/RenderDemo/Pages/ClippingPage.xaml

@ -43,7 +43,7 @@
C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344
C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812
C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z ">
<Border Name="clipChild" Background="{DynamicResource ThemeAccentBrush}" Margin="4">
<Border Name="clipChild" Background="Red" Margin="4">
<!-- Setting opacity puts the TextBox on a new layer -->
<TextBox Text="Avalonia" Opacity="0.9" VerticalAlignment="Center"/>
<Border.RenderTransform>
@ -53,4 +53,4 @@
</Border>
<CheckBox Name="useMask" IsChecked="True" Grid.Row="1">Apply Geometry Clip</CheckBox>
</Grid>
</UserControl>
</UserControl>

4
samples/RenderDemo/SideBar.xaml

@ -3,7 +3,7 @@
<Style Selector="TabControl.sidebar">
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Padding" Value="8 0 0 0"/>
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}"/>
<Setter Property="Template">
<ControlTemplate>
<Border
@ -60,6 +60,6 @@
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
<Setter Property="Background" Value="{DynamicResource SystemAccentColorLight1}"/>
</Style>
</Styles>

1
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
<ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />

8
src/Avalonia.Base/AvaloniaProperty.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Utilities;
namespace Avalonia
@ -9,7 +10,7 @@ namespace Avalonia
/// <summary>
/// Base class for avalonia properties.
/// </summary>
public abstract class AvaloniaProperty : IEquatable<AvaloniaProperty>
public abstract class AvaloniaProperty : IEquatable<AvaloniaProperty>, IPropertyInfo
{
/// <summary>
/// Represents an unset property value.
@ -582,6 +583,11 @@ namespace Avalonia
return _defaultMetadata;
}
bool IPropertyInfo.CanGet => true;
bool IPropertyInfo.CanSet => true;
object IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
void IPropertyInfo.Set(object target, object value) => ((AvaloniaObject)target).SetValue(this, value);
}
/// <summary>

73
src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs

@ -0,0 +1,73 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace Avalonia.Data.Core
{
public class ClrPropertyInfo : IPropertyInfo
{
private readonly Func<object, object> _getter;
private readonly Action<object, object> _setter;
public ClrPropertyInfo(string name, Func<object, object> getter, Action<object, object> setter, Type propertyType)
{
_getter = getter;
_setter = setter;
PropertyType = propertyType;
Name = name;
}
public string Name { get; }
public Type PropertyType { get; }
public object Get(object target)
{
if (_getter == null)
throw new NotSupportedException("Property " + Name + " doesn't have a getter");
return _getter(target);
}
public void Set(object target, object value)
{
if (_setter == null)
throw new NotSupportedException("Property " + Name + " doesn't have a setter");
_setter(target, value);
}
public bool CanSet => _setter != null;
public bool CanGet => _getter != null;
}
public class ReflectionClrPropertyInfo : ClrPropertyInfo
{
static Action<object, object> CreateSetter(PropertyInfo info)
{
if (info.SetMethod == null)
return null;
var target = Expression.Parameter(typeof(object), "target");
var value = Expression.Parameter(typeof(object), "value");
return Expression.Lambda<Action<object, object>>(
Expression.Call(Expression.Convert(target, info.DeclaringType), info.SetMethod,
Expression.Convert(value, info.SetMethod.GetParameters()[0].ParameterType)),
target, value)
.Compile();
}
static Func<object, object> CreateGetter(PropertyInfo info)
{
if (info.GetMethod == null)
return null;
var target = Expression.Parameter(typeof(object), "target");
return Expression.Lambda<Func<object, object>>(
Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType), info.GetMethod),
typeof(object)))
.Compile();
}
public ReflectionClrPropertyInfo(PropertyInfo info) : base(info.Name,
CreateGetter(info), CreateSetter(info), info.PropertyType)
{
}
}
}

32
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -54,6 +54,7 @@ namespace Avalonia.Data.Core
private object _root;
private IDisposable _rootSubscription;
private WeakReference<object> _value;
private IReadOnlyList<ITransformNode> _transformNodes;
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@ -188,6 +189,24 @@ namespace Avalonia.Data.Core
description ?? expression.ToString());
}
private IReadOnlyList<ITransformNode> GetTransformNodesFromChain()
{
LinkedList<ITransformNode> transforms = new LinkedList<ITransformNode>();
var node = _node;
while (node != null)
{
if (node is ITransformNode transform)
{
transforms.AddFirst(transform);
}
node = node.Next;
}
return new List<ITransformNode>(transforms);
}
private IReadOnlyList<ITransformNode> TransformNodes => (_transformNodes ?? (_transformNodes = GetTransformNodesFromChain()));
/// <summary>
/// Attempts to set the value of a property expression.
/// </summary>
@ -203,18 +222,13 @@ namespace Avalonia.Data.Core
{
if (Leaf is SettableNode settable)
{
var node = _node;
while (node != null)
foreach (var transform in TransformNodes)
{
if (node is ITransformNode transform)
value = transform.Transform(value);
if (value is BindingNotification)
{
value = transform.Transform(value);
if (value is BindingNotification)
{
return false;
}
return false;
}
node = node.Next;
}
return settable.SetTargetValue(value, priority);
}

14
src/Avalonia.Base/Data/Core/IPropertyInfo.cs

@ -0,0 +1,14 @@
using System;
namespace Avalonia.Data.Core
{
public interface IPropertyInfo
{
string Name { get; }
object Get(object target);
void Set(object target, object value);
bool CanSet { get; }
bool CanGet { get; }
Type PropertyType { get; }
}
}

2
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@ -59,7 +59,7 @@ namespace Avalonia.Data.Core.Plugins
return Observable.Empty<object>();
}
protected IObservable<object> HandleCompleted(Task task)
private IObservable<object> HandleCompleted(Task task)
{
var resultProperty = task.GetType().GetRuntimeProperty("Result");

32
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -7,6 +7,7 @@ namespace Avalonia.Data.Core
public class PropertyAccessorNode : SettableNode
{
private readonly bool _enableValidation;
private IPropertyAccessorPlugin _customPlugin;
private IPropertyAccessor _accessor;
public PropertyAccessorNode(string propertyName, bool enableValidation)
@ -15,6 +16,13 @@ namespace Avalonia.Data.Core
_enableValidation = enableValidation;
}
public PropertyAccessorNode(string propertyName, bool enableValidation, IPropertyAccessorPlugin customPlugin)
{
PropertyName = propertyName;
_enableValidation = enableValidation;
_customPlugin = customPlugin;
}
public override string Description => PropertyName;
public string PropertyName { get; }
public override Type PropertyType => _accessor?.PropertyType;
@ -37,17 +45,7 @@ namespace Avalonia.Data.Core
{
reference.TryGetTarget(out object target);
IPropertyAccessorPlugin plugin = null;
foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
{
if (x.Match(target, PropertyName))
{
plugin = x;
break;
}
}
var plugin = _customPlugin ?? GetPropertyAccessorPluginForObject(target);
var accessor = plugin?.Start(reference, PropertyName);
// We need to handle accessor fallback before handling validation. Validators do not support null accessors.
@ -82,6 +80,18 @@ namespace Avalonia.Data.Core
accessor.Subscribe(ValueChanged);
}
private IPropertyAccessorPlugin GetPropertyAccessorPluginForObject(object target)
{
foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
{
if (x.Match(target, PropertyName))
{
return x;
}
}
return null;
}
protected override void StopListeningCore()
{
_accessor.Dispose();

91
src/Avalonia.Base/Data/Core/PropertyPath.cs

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Data.Core
{
public class PropertyPath
{
public IReadOnlyList<IPropertyPathElement> Elements { get; }
public PropertyPath(IEnumerable<IPropertyPathElement> elements)
{
Elements = elements.ToList();
}
}
public class PropertyPathBuilder
{
readonly List<IPropertyPathElement> _elements = new List<IPropertyPathElement>();
public PropertyPathBuilder Property(IPropertyInfo property)
{
_elements.Add(new PropertyPropertyPathElement(property));
return this;
}
public PropertyPathBuilder ChildTraversal()
{
_elements.Add(new ChildTraversalPropertyPathElement());
return this;
}
public PropertyPathBuilder EnsureType(Type type)
{
_elements.Add(new EnsureTypePropertyPathElement(type));
return this;
}
public PropertyPathBuilder Cast(Type type)
{
_elements.Add(new CastTypePropertyPathElement(type));
return this;
}
public PropertyPath Build()
{
return new PropertyPath(_elements);
}
}
public interface IPropertyPathElement
{
}
public class PropertyPropertyPathElement : IPropertyPathElement
{
public IPropertyInfo Property { get; }
public PropertyPropertyPathElement(IPropertyInfo property)
{
Property = property;
}
}
public class ChildTraversalPropertyPathElement : IPropertyPathElement
{
}
public class EnsureTypePropertyPathElement : IPropertyPathElement
{
public Type Type { get; }
public EnsureTypePropertyPathElement(Type type)
{
Type = type;
}
}
public class CastTypePropertyPathElement : IPropertyPathElement
{
public CastTypePropertyPathElement(Type type)
{
Type = type;
}
public Type Type { get; }
}
}

37
src/Avalonia.Base/Data/Core/StreamNode.cs

@ -1,35 +1,54 @@
using System;
using System.Reactive.Linq;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core
{
public class StreamNode : ExpressionNode
{
private IStreamPlugin _customPlugin = null;
private IDisposable _subscription;
public override string Description => "^";
public StreamNode() { }
public StreamNode(IStreamPlugin customPlugin)
{
_customPlugin = customPlugin;
}
protected override void StartListeningCore(WeakReference<object> reference)
{
GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged);
}
protected override void StopListeningCore()
{
_subscription?.Dispose();
_subscription = null;
}
private IStreamPlugin GetPlugin(WeakReference<object> reference)
{
if (_customPlugin != null)
{
return _customPlugin;
}
foreach (var plugin in ExpressionObserver.StreamHandlers)
{
if (plugin.Match(reference))
{
_subscription = plugin.Start(reference).Subscribe(ValueChanged);
return;
return plugin;
}
}
// TODO: Improve error.
// TODO: Improve error
ValueChanged(new BindingNotification(
new MarkupBindingChainException("Stream operator applied to unsupported type", Description),
BindingErrorType.Error));
}
protected override void StopListeningCore()
{
_subscription?.Dispose();
_subscription = null;
return null;
}
}
}

20
src/Avalonia.Base/Utilities/CharacterReader.cs

@ -79,5 +79,25 @@ namespace Avalonia.Utilities
Position += len;
return span;
}
public ReadOnlySpan<char> TryPeek(int count)
{
if (_s.Length < count)
return ReadOnlySpan<char>.Empty;
return _s.Slice(0, count);
}
public ReadOnlySpan<char> PeekWhitespace()
{
var trimmed = _s.TrimStart();
return _s.Slice(0, _s.Length - trimmed.Length);
}
public void Skip(int count)
{
if (_s.Length < count)
throw new IndexOutOfRangeException();
_s = _s.Slice(count);
}
}
}

46
src/Avalonia.Base/Utilities/KeywordParser.cs

@ -0,0 +1,46 @@
using System;
namespace Avalonia.Utilities
{
#if !BUILDTASK
public
#endif
static class KeywordParser
{
public static bool CheckKeyword(this ref CharacterReader r, string keyword)
{
return (CheckKeywordInternal(ref r, keyword) >= 0);
}
static int CheckKeywordInternal(this ref CharacterReader r, string keyword)
{
var ws = r.PeekWhitespace();
var chars = r.TryPeek(ws.Length + keyword.Length);
if (chars.IsEmpty)
return -1;
if (SpanEquals(chars.Slice(ws.Length), keyword.AsSpan()))
return chars.Length;
return -1;
}
static bool SpanEquals(ReadOnlySpan<char> left, ReadOnlySpan<char> right)
{
if (left.Length != right.Length)
return false;
for(var c=0; c<left.Length;c++)
if (left[c] != right[c])
return false;
return true;
}
public static bool TakeIfKeyword(this ref CharacterReader r, string keyword)
{
var l = CheckKeywordInternal(ref r, keyword);
if (l < 0)
return false;
r.Skip(l);
return true;
}
}
}

42
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -5,7 +5,7 @@
<OutputType>exe</OutputType>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
<DefineConstants>$(DefineConstants);BUILDTASK;XAMLIL_CECIL_INTERNAL;XAMLIL_INTERNAL</DefineConstants>
<DefineConstants>$(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL</DefineConstants>
<CopyLocalLockFileAssemblies Condition="$(TargetFramework) == 'netstandard2.0'">true</CopyLocalLockFileAssemblies>
<NoWarn>NU1605</NoWarn>
</PropertyGroup>
@ -21,15 +21,18 @@
<Link>XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Remove="external/cecil/**/*.*" />
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\**\*.cs">
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX\**\*.cs">
<Link>XamlIl/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs">
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX.IL.Cecil\**\*.cs">
<Link>XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\PropertyPathGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
@ -38,36 +41,25 @@
</Compile>
<Compile Include="../Avalonia.Base/Utilities/CharacterReader.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/KeywordParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/StyleClassParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\TypeSystem\SreTypeSystem.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
<PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />
<PackageReference Condition="$(TargetFramework) == 'netstandard2.0'" Include="ILRepack.MSBuild.Task" Version="2.0.13" PrivateAssets="All" />
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
</ItemGroup>
<Target Name="ILRepack" AfterTargets="Build" Condition="$(TargetFramework) == 'netstandard2.0'">
<PropertyGroup>
<WorkingDirectory>$(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)</WorkingDirectory>
</PropertyGroup>
<ItemGroup>
<InputAssemblies Include="Mono.Cecil.dll" />
</ItemGroup>
<ILRepack OutputType="$(OutputType)" MainAssembly="$(AssemblyName).dll" OutputAssembly="$(AssemblyName).dll" InputAssemblies="@(InputAssemblies)" WorkingDirectory="$(WorkingDirectory)" />
<ItemGroup>
<DeleteNonNeededResults Include="$(WorkingDirectory)\*.dll" />
<DeleteNonNeededResults Remove="$(WorkingDirectory)\Avalonia.Build.Tasks.dll" />
</ItemGroup>
<Delete Files="@(DeleteNonNeededResults)" />
</Target>
</Project>

2
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

@ -5,7 +5,7 @@ using Avalonia.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using XamlIl.TypeSystem;
using XamlX.TypeSystem;
namespace Avalonia.Build.Tasks
{

60
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -7,17 +7,18 @@ using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework;
using Mono.Cecil;
using XamlIl.TypeSystem;
using Avalonia.Utilities;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Parsers;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Parsers;
using XamlX.Transform;
using XamlX.TypeSystem;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using TypeAttributes = Mono.Cecil.TypeAttributes;
using XamlX.IL;
namespace Avalonia.Build.Tasks
{
@ -50,23 +51,32 @@ namespace Avalonia.Build.Tasks
if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
// Nothing to do
return new CompileResult(true);
var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new XamlIlTransformerConfiguration(typeSystem,
var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(clrPropertiesDef);
var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(indexerAccessorClosure);
var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter);
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef);
var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
xamlLanguage);
var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
xamlLanguage, emitConfig);
var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass) { EnableIlVerification = verifyIl };
var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl };
var editorBrowsableAttribute = typeSystem
.GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
@ -126,35 +136,35 @@ namespace Avalonia.Build.Tasks
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlIlParser.Parse(xaml);
var parsed = XDocumentXamlParser.Parse(xaml);
var initialRoot = (XamlIlAstObjectNode)parsed.Root;
var initialRoot = (XamlAstObjectNode)parsed.Root;
var precompileDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
var precompileDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile");
if (precompileDirective != null)
{
var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim()
var precompileText = (precompileDirective.Values[0] as XamlAstTextNode)?.Text.Trim()
.ToLowerInvariant();
if (precompileText == "false")
continue;
if (precompileText != "true")
throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective);
throw new XamlParseException("Invalid value for x:Precompile", precompileDirective);
}
var classDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
IXamlIlType classType = null;
IXamlType classType = null;
if (classDirective != null)
{
if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn))
throw new XamlIlParseException("x:Class should have a string value", classDirective);
if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlAstTextNode tn))
throw new XamlParseException("x:Class should have a string value", classDirective);
classType = typeSystem.TargetAssembly.FindType(tn.Text);
if (classType == null)
throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
throw new XamlParseException($"Unable to find type `{tn.Text}`", classDirective);
compiler.OverrideRootType(parsed,
new XamlIlAstClrTypeReference(classDirective, classType, false));
new XamlAstClrTypeReference(classDirective, classType, false));
initialRoot.Children.Remove(classDirective);
}
@ -323,7 +333,7 @@ namespace Avalonia.Build.Tasks
catch (Exception e)
{
int lineNumber = 0, linePosition = 0;
if (e is XamlIlParseException xe)
if (e is XamlParseException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;

2
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3920,6 +3920,8 @@ namespace Avalonia.Controls
dataGridColumn: CurrentColumn,
dataGridRow: EditingRow,
dataGridCell: editingCell);
EditingRow.InvalidateDesiredHeight();
}
// We're done, so raise the CellEditEnded event

5
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -767,6 +767,11 @@ namespace Avalonia.Controls
}
}
internal void InvalidateDesiredHeight()
{
_cellsElement?.InvalidateDesiredHeight();
}
internal void ResetGridLine()
{
_bottomGridLine = null;

5
src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs

@ -299,6 +299,11 @@ namespace Avalonia.Controls.Primitives
DesiredHeight = 0;
}
internal void InvalidateDesiredHeight()
{
DesiredHeight = 0;
}
private bool ShouldDisplayCell(DataGridColumn column, double frozenLeftEdge, double scrollingLeftEdge)
{
if (!column.IsVisible)

2
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@ -188,7 +188,7 @@
</Style>
<Style Selector="DataGrid">
<Setter Property="RowBackground" Value="{DynamicResource ThemeAccentBrush4}" />
<Setter Property="RowBackground" Value="{DynamicResource SystemAccentColorDark2}" />
<Setter Property="AlternatingRowBackground" Value="#00FFFFFF" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
<Setter Property="HeadersVisibility" Value="Column" />

23
src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs

@ -0,0 +1,23 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines compensation levels for the platform depending on the transparency level.
/// It controls the base opacity level of the 'tracing paper' layer that compensates
/// for low blur radius.
/// </summary>
public struct AcrylicPlatformCompensationLevels
{
public AcrylicPlatformCompensationLevels(double transparent, double blurred, double acrylic)
{
TransparentLevel = transparent;
BlurLevel = blurred;
AcrylicBlurLevel = acrylic;
}
public double TransparentLevel { get; }
public double BlurLevel { get; }
public double AcrylicBlurLevel { get; }
}
}

86
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@ -0,0 +1,86 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Controls.Chrome
{
/// <summary>
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
/// </summary>
public class CaptionButtons : TemplatedControl
{
private CompositeDisposable? _disposables;
private Window? _hostWindow;
public void Attach(Window hostWindow)
{
if (_disposables == null)
{
_hostWindow = hostWindow;
_disposables = new CompositeDisposable
{
_hostWindow.GetObservable(Window.WindowStateProperty)
.Subscribe(x =>
{
PseudoClasses.Set(":minimized", x == WindowState.Minimized);
PseudoClasses.Set(":normal", x == WindowState.Normal);
PseudoClasses.Set(":maximized", x == WindowState.Maximized);
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
})
};
}
}
public void Detach()
{
if (_disposables != null)
{
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
layer?.Children.Remove(this);
_disposables.Dispose();
_disposables = null;
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var closeButton = e.NameScope.Get<Panel>("PART_CloseButton");
var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton");
var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton");
var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton");
closeButton.PointerReleased += (sender, e) => _hostWindow?.Close();
restoreButton.PointerReleased += (sender, e) =>
{
if (_hostWindow != null)
{
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
};
minimiseButton.PointerReleased += (sender, e) =>
{
if (_hostWindow != null)
{
_hostWindow.WindowState = WindowState.Minimized;
}
};
fullScreenButton.PointerReleased += (sender, e) =>
{
if (_hostWindow != null)
{
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen;
}
};
}
}
}

117
src/Avalonia.Controls/Chrome/TitleBar.cs

@ -0,0 +1,117 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Controls.Chrome
{
/// <summary>
/// Draws a titlebar when managed client decorations are enabled.
/// </summary>
public class TitleBar : TemplatedControl
{
private CompositeDisposable? _disposables;
private readonly Window? _hostWindow;
private CaptionButtons? _captionButtons;
public TitleBar(Window hostWindow)
{
_hostWindow = hostWindow;
}
public TitleBar()
{
}
public void Attach()
{
if (_disposables == null)
{
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
layer?.Children.Add(this);
if (_hostWindow != null)
{
_disposables = new CompositeDisposable
{
_hostWindow.GetObservable(Window.WindowDecorationMarginProperty)
.Subscribe(x => UpdateSize()),
_hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
.Subscribe(x => UpdateSize()),
_hostWindow.GetObservable(Window.OffScreenMarginProperty)
.Subscribe(x => UpdateSize()),
_hostWindow.GetObservable(Window.WindowStateProperty)
.Subscribe(x =>
{
PseudoClasses.Set(":minimized", x == WindowState.Minimized);
PseudoClasses.Set(":normal", x == WindowState.Normal);
PseudoClasses.Set(":maximized", x == WindowState.Maximized);
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
})
};
_captionButtons?.Attach(_hostWindow);
}
UpdateSize();
}
}
private void UpdateSize()
{
if (_hostWindow != null)
{
Margin = new Thickness(
_hostWindow.OffScreenMargin.Left,
_hostWindow.OffScreenMargin.Top,
_hostWindow.OffScreenMargin.Right,
_hostWindow.OffScreenMargin.Bottom);
if (_hostWindow.WindowState != WindowState.FullScreen)
{
Height = _hostWindow.WindowDecorationMargin.Top;
if (_captionButtons != null)
{
_captionButtons.Height = Height;
}
}
}
}
public void Detach()
{
if (_disposables != null)
{
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
layer?.Children.Remove(this);
_disposables.Dispose();
_disposables = null;
_captionButtons?.Detach();
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons");
if (_hostWindow != null)
{
_captionButtons.Attach(_hostWindow);
}
UpdateSize();
}
}
}

31
src/Avalonia.Controls/ComboBox.cs

@ -7,6 +7,7 @@ using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
@ -63,6 +64,18 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> PlaceholderForegroundProperty =
AvaloniaProperty.Register<ComboBox, IBrush>(nameof(PlaceholderForeground));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<ComboBox>();
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
@ -133,6 +146,24 @@ namespace Avalonia.Controls
set { SetValue(VirtualizationModeProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get { return GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get { return GetValue(VerticalContentAlignmentProperty); }
set { SetValue(VerticalContentAlignmentProperty, value); }
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{

3
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -52,6 +52,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
/// <inheritdoc/>
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1);

117
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@ -0,0 +1,117 @@
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using System;
namespace Avalonia.Controls
{
public class ExperimentalAcrylicBorder : Decorator
{
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>();
public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty =
AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial>(nameof(Material));
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
private IDisposable _subscription;
static ExperimentalAcrylicBorder()
{
AffectsRender<ExperimentalAcrylicBorder>(
MaterialProperty,
CornerRadiusProperty);
}
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public ExperimentalAcrylicMaterial Material
{
get => GetValue(MaterialProperty);
set => SetValue(MaterialProperty, value);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var tl = (e.Root as TopLevel);
_subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
.Subscribe(x =>
{
switch (x)
{
case WindowTransparencyLevel.Transparent:
case WindowTransparencyLevel.None:
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
break;
case WindowTransparencyLevel.Blur:
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
break;
case WindowTransparencyLevel.AcrylicBlur:
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
break;
}
});
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_subscription?.Dispose();
}
public override void Render(DrawingContext context)
{
if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc)
{
var cornerRadius = CornerRadius;
idc.DrawRectangle(
Material,
new RoundedRect(
new Rect(Bounds.Size),
cornerRadius.TopLeft, cornerRadius.TopRight,
cornerRadius.BottomRight, cornerRadius.BottomLeft));
}
else
{
_borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new SolidColorBrush(Material.FallbackColor), null, default);
}
}
/// <summary>
/// Measures the control.
/// </summary>
/// <param name="availableSize">The available size.</param>
/// <returns>The desired size of the control.</returns>
protected override Size MeasureOverride(Size availableSize)
{
return LayoutHelper.MeasureChild(Child, availableSize, Padding);
}
/// <summary>
/// Arranges the control's child.
/// </summary>
/// <param name="finalSize">The size allocated to the control.</param>
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
return LayoutHelper.ArrangeChild(Child, finalSize, Padding);
}
}
}

38
src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs

@ -0,0 +1,38 @@
using System;
namespace Avalonia.Platform
{
/// <summary>
/// Hint for Window Chrome when ClientArea is Extended.
/// </summary>
[Flags]
public enum ExtendClientAreaChromeHints
{
/// <summary>
/// The will be no chrome at all.
/// </summary>
NoChrome,
/// <summary>
/// The default for the platform.
/// </summary>
Default = SystemChrome,
/// <summary>
/// Use SystemChrome
/// </summary>
SystemChrome = 0x01,
/// <summary>
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
/// This is because Windows Chrome can not be shown ontop of user content.
/// </summary>
PreferSystemChrome = 0x02,
/// <summary>
/// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
/// slightly lower than normal.
/// </summary>
OSXThickTitleBar = 0x08,
}
}

5
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -127,5 +127,10 @@ namespace Avalonia.Platform
/// Gets the current <see cref="WindowTransparencyLevel"/> of the TopLevel.
/// </summary>
WindowTransparencyLevel TransparencyLevel { get; }
/// <summary>
/// Gets the <see cref="AcrylicPlatformCompensationLevels"/> for the platform.
/// </summary>
AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
}
}

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

@ -68,6 +68,34 @@ namespace Avalonia.Platform
/// </summary>
Func<bool> Closing { get; set; }
/// <summary>
/// Gets a value to indicate if the platform was able to extend client area to non-client area.
/// </summary>
bool IsClientAreaExtendedToDecorations { get; }
/// <summary>
/// Gets or Sets an action that is called whenever one of the extend client area properties changed.
/// </summary>
Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
/// <summary>
/// Gets a flag that indicates if Managed decorations i.e. caption buttons are required.
/// This property is used when <see cref="IsClientAreaExtendedToDecorations"/> is set.
/// </summary>
bool NeedsManagedDecorations { get; }
/// <summary>
/// Gets a thickness that describes the amount each side of the non-client area extends into the client area.
/// It includes the titlebar.
/// </summary>
Thickness ExtendedMargins { get; }
/// <summary>
/// Gets a thickness that describes the margin around the window that is offscreen.
/// This may happen when a window is maximized and <see cref="IsClientAreaExtendedToDecorations"/> is set.
/// </summary>
Thickness OffScreenMargin { get; }
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
/// </summary>
@ -94,5 +122,23 @@ namespace Avalonia.Platform
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
/// <summary>
/// Sets if the ClientArea is extended into the non-client area.
/// </summary>
/// <param name="extendIntoClientAreaHint">true to enable, false to disable</param>
void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint);
/// <summary>
/// Sets hints that configure how the client area extends.
/// </summary>
/// <param name="hints"></param>
void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints);
/// <summary>
/// Sets how big the non-client titlebar area should be.
/// </summary>
/// <param name="titleBarHeight">-1 for platform default, otherwise the height in DIPs.</param>
void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight);
}
}

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

@ -95,6 +95,7 @@ namespace Avalonia.Controls.Presenters
static ContentPresenter()
{
AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));

15
src/Avalonia.Controls/Primitives/AccessText.cs

@ -110,7 +110,7 @@ namespace Avalonia.Controls.Primitives
foreach (var textLine in TextLayout.TextLines)
{
if (textLine.Text.End < textPosition)
if (textLine.TextRange.End < textPosition)
{
currentY += textLine.LineMetrics.Size.Height;
@ -121,21 +121,22 @@ namespace Avalonia.Controls.Primitives
foreach (var textRun in textLine.TextRuns)
{
if (!(textRun is ShapedTextRun shapedRun))
if (!(textRun is ShapedTextCharacters shapedTextCharacters))
{
continue;
}
if (shapedRun.GlyphRun.Characters.End < textPosition)
if (shapedTextCharacters.GlyphRun.Characters.End < textPosition)
{
currentX += shapedRun.GlyphRun.Bounds.Width;
currentX += shapedTextCharacters.GlyphRun.Bounds.Width;
continue;
}
var characterHit = shapedRun.GlyphRun.FindNearestCharacterHit(textPosition, out var width);
var characterHit =
shapedTextCharacters.GlyphRun.FindNearestCharacterHit(textPosition, out var width);
var distance = shapedRun.GlyphRun.GetDistanceFromCharacterHit(characterHit);
var distance = shapedTextCharacters.GlyphRun.GetDistanceFromCharacterHit(characterHit);
currentX += distance - width;
@ -144,7 +145,7 @@ namespace Avalonia.Controls.Primitives
width = 0.0;
}
return new Rect(currentX, currentY, width, shapedRun.GlyphRun.Bounds.Height);
return new Rect(currentX, currentY, width, shapedTextCharacters.GlyphRun.Bounds.Height);
}
}

29
src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs

@ -0,0 +1,29 @@
using System.Linq;
using Avalonia.Rendering;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls.Primitives
{
public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest
{
public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual)
{
foreach (var v in visual.GetVisualAncestors())
if (v is VisualLayerManager vlm)
if (vlm.OverlayLayer != null)
return vlm.ChromeOverlayLayer;
if (visual is TopLevel tl)
{
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
return layers?.ChromeOverlayLayer;
}
return null;
}
public bool HitTest(Point point) => Children.HitTestCustom(point);
}
}

15
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -6,7 +6,9 @@ namespace Avalonia.Controls.Primitives
public class VisualLayerManager : Decorator
{
private const int AdornerZIndex = int.MaxValue - 100;
private const int OverlayZIndex = int.MaxValue - 99;
private const int ChromeZIndex = int.MaxValue - 99;
private const int OverlayZIndex = int.MaxValue - 98;
private ILogicalRoot _logicalRoot;
private readonly List<Control> _layers = new List<Control>();
@ -24,6 +26,17 @@ namespace Avalonia.Controls.Primitives
}
}
public ChromeOverlayLayer ChromeOverlayLayer
{
get
{
var rv = FindLayer<ChromeOverlayLayer>();
if (rv == null)
AddLayer(rv = new ChromeOverlayLayer(), ChromeZIndex);
return rv;
}
}
public OverlayLayer OverlayLayer
{
get

1
src/Avalonia.Controls/Properties/AssemblyInfo.cs

@ -13,3 +13,4 @@ using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Shapes")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Templates")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Notifications")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Chrome")]

35
src/Avalonia.Controls/TextBlock.cs

@ -70,6 +70,15 @@ namespace Avalonia.Controls
Brushes.Black,
inherits: true);
/// <summary>
/// Defines the <see cref="LineHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> LineHeightProperty =
AvaloniaProperty.Register<TextBlock, double>(
nameof(LineHeight),
double.NaN,
validate: IsValidLineHeight);
/// <summary>
/// Defines the <see cref="MaxLines"/> property.
/// </summary>
@ -122,19 +131,19 @@ namespace Avalonia.Controls
{
ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
AffectsRender<TextBlock>(BackgroundProperty, ForegroundProperty,
AffectsRender<TextBlock>(BackgroundProperty, ForegroundProperty,
TextAlignmentProperty, TextDecorationsProperty);
AffectsMeasure<TextBlock>(FontSizeProperty, FontWeightProperty,
FontStyleProperty, TextWrappingProperty, FontFamilyProperty,
TextTrimmingProperty, TextProperty, PaddingProperty);
AffectsMeasure<TextBlock>(FontSizeProperty, FontWeightProperty,
FontStyleProperty, TextWrappingProperty, FontFamilyProperty,
TextTrimmingProperty, TextProperty, PaddingProperty, LineHeightProperty, MaxLinesProperty);
Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed,
TextAlignmentProperty.Changed, TextWrappingProperty.Changed,
TextTrimmingProperty.Changed, FontSizeProperty.Changed,
FontStyleProperty.Changed, FontWeightProperty.Changed,
FontFamilyProperty.Changed, TextDecorationsProperty.Changed,
PaddingProperty.Changed
PaddingProperty.Changed, MaxLinesProperty.Changed, LineHeightProperty.Changed
).AddClassHandler<TextBlock>((x, _) => x.InvalidateTextLayout());
}
@ -230,6 +239,15 @@ namespace Avalonia.Controls
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// Gets or sets the height of each line of content.
/// </summary>
public double LineHeight
{
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
/// <summary>
/// Gets or sets the maximum number of text lines.
/// </summary>
@ -395,7 +413,7 @@ namespace Avalonia.Controls
var padding = Padding;
TextLayout?.Draw(context.PlatformImpl, new Point(padding.Left, padding.Top));
TextLayout?.Draw(context, new Point(padding.Left, padding.Top));
}
/// <summary>
@ -422,7 +440,8 @@ namespace Avalonia.Controls
TextDecorations,
constraint.Width,
constraint.Height,
MaxLines);
maxLines: MaxLines,
lineHeight: LineHeight);
}
/// <summary>
@ -471,5 +490,7 @@ namespace Avalonia.Controls
}
private static bool IsValidMaxLines(int maxLines) => maxLines >= 0;
private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0;
}
}

2
src/Avalonia.Controls/TopLevel.cs

@ -405,7 +405,7 @@ namespace Avalonia.Controls
}
else
{
_transparencyFallbackBorder.Background = Brushes.Transparent;
_transparencyFallbackBorder.Background = null;
}
}

135
src/Avalonia.Controls/Window.cs

@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Chrome;
using Avalonia.Controls.Platform;
using Avalonia.Data;
using Avalonia.Input;
@ -69,7 +70,11 @@ namespace Avalonia.Controls
/// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
{
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
private TitleBar _managedTitleBar;
private bool _isExtendedIntoWindowDecorations;
private Thickness _windowDecorationMargin;
private Thickness _offScreenMargin;
/// <summary>
/// Defines the <see cref="SizeToContent"/> property.
@ -87,6 +92,37 @@ namespace Avalonia.Controls
o => o.HasSystemDecorations,
(o, v) => o.HasSystemDecorations = v);
/// <summary>
/// Defines the <see cref="ExtendClientAreaToDecorationsHint"/> property.
/// </summary>
public static readonly StyledProperty<bool> ExtendClientAreaToDecorationsHintProperty =
AvaloniaProperty.Register<Window, bool>(nameof(ExtendClientAreaToDecorationsHint), false);
public static readonly StyledProperty<ExtendClientAreaChromeHints> ExtendClientAreaChromeHintsProperty =
AvaloniaProperty.Register<Window, ExtendClientAreaChromeHints>(nameof(ExtendClientAreaChromeHints), ExtendClientAreaChromeHints.Default);
public static readonly StyledProperty<double> ExtendClientAreaTitleBarHeightHintProperty =
AvaloniaProperty.Register<Window, double>(nameof(ExtendClientAreaTitleBarHeightHint), -1);
/// <summary>
/// Defines the <see cref="IsExtendedIntoWindowDecorations"/> property.
/// </summary>
public static readonly DirectProperty<Window, bool> IsExtendedIntoWindowDecorationsProperty =
AvaloniaProperty.RegisterDirect<Window, bool>(nameof(IsExtendedIntoWindowDecorations),
o => o.IsExtendedIntoWindowDecorations,
unsetValue: false);
/// <summary>
/// Defines the <see cref="WindowDecorationMargin"/> property.
/// </summary>
public static readonly DirectProperty<Window, Thickness> WindowDecorationMarginProperty =
AvaloniaProperty.RegisterDirect<Window, Thickness>(nameof(WindowDecorationMargin),
o => o.WindowDecorationMargin);
public static readonly DirectProperty<Window, Thickness> OffScreenMarginProperty =
AvaloniaProperty.RegisterDirect<Window, Thickness>(nameof(OffScreenMargin),
o => o.OffScreenMargin);
/// <summary>
/// Defines the <see cref="SystemDecorations"/> property.
/// </summary>
@ -164,6 +200,21 @@ namespace Avalonia.Controls
WindowStateProperty.Changed.AddClassHandler<Window>(
(w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; });
ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler<Window>(
(w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaToDecorationsHint((bool)e.NewValue); });
ExtendClientAreaChromeHintsProperty.Changed.AddClassHandler<Window>(
(w, e) =>
{
if (w.PlatformImpl != null)
{
w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue);
}
});
ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler<Window>(
(w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaTitleBarHeightHint((double)e.NewValue); });
MinWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
MinHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
@ -189,6 +240,7 @@ namespace Avalonia.Controls
impl.GotInputWhenDisabled = OnGotInputWhenDisabled;
impl.WindowStateChanged = HandleWindowStateChanged;
_maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size);
impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged;
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar);
@ -237,6 +289,66 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border).
/// </summary>
public bool ExtendClientAreaToDecorationsHint
{
get { return GetValue(ExtendClientAreaToDecorationsHintProperty); }
set { SetValue(ExtendClientAreaToDecorationsHintProperty, value); }
}
/// <summary>
/// Gets or Sets the <see cref="Avalonia.Platform.ExtendClientAreaChromeHints"/> that control
/// how the chrome looks when the client area is extended.
/// </summary>
public ExtendClientAreaChromeHints ExtendClientAreaChromeHints
{
get => GetValue(ExtendClientAreaChromeHintsProperty);
set => SetValue(ExtendClientAreaChromeHintsProperty, value);
}
/// <summary>
/// Gets or Sets the TitlebarHeightHint for when the client area is extended.
/// A value of -1 will cause the titlebar to be auto sized to the OS default.
/// Any other positive value will cause the titlebar to assume that height.
/// </summary>
public double ExtendClientAreaTitleBarHeightHint
{
get => GetValue(ExtendClientAreaTitleBarHeightHintProperty);
set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value);
}
/// <summary>
/// Gets if the ClientArea is Extended into the Window Decorations.
/// </summary>
public bool IsExtendedIntoWindowDecorations
{
get => _isExtendedIntoWindowDecorations;
private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value);
}
/// <summary>
/// Gets the WindowDecorationMargin.
/// This tells you the thickness around the window that is used by borders and the titlebar.
/// </summary>
public Thickness WindowDecorationMargin
{
get => _windowDecorationMargin;
private set => SetAndRaise(WindowDecorationMarginProperty, ref _windowDecorationMargin, value);
}
/// <summary>
/// Gets the window margin that is hidden off the screen area.
/// This is generally only the case on Windows when in Maximized where the window border
/// is hidden off the screen. This Margin may be used to ensure user content doesnt overlap this space.
/// </summary>
public Thickness OffScreenMargin
{
get => _offScreenMargin;
private set => SetAndRaise(OffScreenMarginProperty, ref _offScreenMargin, value);
}
/// <summary>
/// Sets the system decorations (title bar, border, etc)
/// </summary>
@ -435,6 +547,27 @@ namespace Avalonia.Controls
}
}
protected virtual void ExtendClientAreaToDecorationsChanged(bool isExtended)
{
IsExtendedIntoWindowDecorations = isExtended;
WindowDecorationMargin = PlatformImpl.ExtendedMargins;
OffScreenMargin = PlatformImpl.OffScreenMargin;
if (PlatformImpl.NeedsManagedDecorations)
{
if (_managedTitleBar == null)
{
_managedTitleBar = new TitleBar(this);
_managedTitleBar.Attach();
}
}
else
{
_managedTitleBar?.Detach();
_managedTitleBar = null;
}
}
/// <summary>
/// Hides the window but does not close it.
/// </summary>

24
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -78,7 +78,17 @@ namespace Avalonia.DesignerSupport.Remote
}
public IScreenImpl Screen { get; } = new ScreenStub();
public Action GotInputWhenDisabled { get; set; }
public Action GotInputWhenDisabled { get; set; }
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
public Thickness ExtendedMargins { get; } = new Thickness();
public bool IsClientAreaExtendedToDecorations { get; }
public Thickness OffScreenMargin { get; } = new Thickness();
public bool NeedsManagedDecorations => false;
public void Activate()
{
@ -119,5 +129,17 @@ namespace Avalonia.DesignerSupport.Remote
public void SetEnabled(bool enable)
{
}
public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
{
}
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
{
}
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
}
}
}

26
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -38,7 +38,13 @@ namespace Avalonia.DesignerSupport.Remote
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
public Thickness ExtendedMargins { get; } = new Thickness();
public Thickness OffScreenMargin { get; } = new Thickness();
public WindowStub(IWindowImpl parent = null)
{
@ -141,6 +147,18 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
{
}
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
{
}
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
}
public IPopupPositioner PopupPositioner { get; }
public Action GotInputWhenDisabled { get; set; }
@ -152,6 +170,12 @@ namespace Avalonia.DesignerSupport.Remote
}
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public bool IsClientAreaExtendedToDecorations { get; }
public bool NeedsManagedDecorations => false;
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
}
class ClipboardStub : IClipboard

4
src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs

@ -46,10 +46,6 @@ namespace Avalonia
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseX11();
static void LoadDirect2D1<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseDirect2D1();
static void LoadSkia<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseSkia();

3
src/Avalonia.Desktop/Avalonia.Desktop.csproj

@ -5,8 +5,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" />
<ProjectReference Include="../../src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj" />
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" />
<ProjectReference Include="../../src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" />
<ProjectReference Include="../../src/Avalonia.Native/Avalonia.Native.csproj" />
<ProjectReference Include="../../packages/Avalonia/Avalonia.csproj" />

12
src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Headless\Avalonia.Headless.csproj" />
<PackageReference Include="Quamotion.RemoteViewing" Version="1.1.21" />
</ItemGroup>
</Project>

96
src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@ -0,0 +1,96 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;
using RemoteViewing.Vnc;
using RemoteViewing.Vnc.Server;
namespace Avalonia.Headless.Vnc
{
public class HeadlessVncFramebufferSource : IVncFramebufferSource
{
public IHeadlessWindow Window { get; set; }
private object _lock = new object();
public VncFramebuffer _framebuffer = new VncFramebuffer("Avalonia", 1, 1, VncPixelFormat.RGB32);
private VncButton _previousButtons;
public HeadlessVncFramebufferSource(VncServerSession session, Window window)
{
Window = (IHeadlessWindow)window.PlatformImpl;
session.PointerChanged += (_, args) =>
{
var pt = new Point(args.X, args.Y);
var buttons = (VncButton)args.PressedButtons;
int TranslateButton(VncButton vncButton) =>
vncButton == VncButton.Left ? 0 : vncButton == VncButton.Right ? 1 : 2;
var modifiers = (RawInputModifiers)(((int)buttons & 7) << 4);
Dispatcher.UIThread.Post(() =>
{
Window?.MouseMove(pt);
foreach (var btn in CheckedButtons)
if (_previousButtons.HasFlag(btn) && !buttons.HasFlag(btn))
Window?.MouseUp(pt, TranslateButton(btn), modifiers);
foreach (var btn in CheckedButtons)
if (!_previousButtons.HasFlag(btn) && buttons.HasFlag(btn))
Window?.MouseDown(pt, TranslateButton(btn), modifiers);
_previousButtons = buttons;
}, DispatcherPriority.Input);
};
}
[Flags]
enum VncButton
{
Left = 1,
Middle = 2,
Right = 4,
ScrollUp = 8,
ScrollDown = 16
}
private static VncButton[] CheckedButtons = new[] {VncButton.Left, VncButton.Middle, VncButton.Right};
public VncFramebuffer Capture()
{
lock (_lock)
{
using (var bmpRef = Window.GetLastRenderedFrame())
{
if (bmpRef?.Item == null)
return _framebuffer;
var bmp = bmpRef.Item;
if (bmp.PixelSize.Width != _framebuffer.Width || bmp.PixelSize.Height != _framebuffer.Height)
{
_framebuffer = new VncFramebuffer("Avalonia", bmp.PixelSize.Width, bmp.PixelSize.Height,
VncPixelFormat.RGB32);
}
using (var fb = bmp.Lock())
{
var buf = _framebuffer.GetBuffer();
if (_framebuffer.Stride == fb.RowBytes)
Marshal.Copy(fb.Address, buf, 0, buf.Length);
else
for (var y = 0; y < fb.Size.Height; y++)
{
var sourceStart = fb.RowBytes * y;
var dstStart = _framebuffer.Stride * y;
var row = fb.Size.Width * 4;
Marshal.Copy(new IntPtr(sourceStart + fb.Address.ToInt64()), buf, dstStart, row);
}
}
}
}
return _framebuffer;
}
}
}

48
src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs

@ -0,0 +1,48 @@
using System.Net;
using System.Net.Sockets;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Headless;
using Avalonia.Headless.Vnc;
using RemoteViewing.Vnc;
using RemoteViewing.Vnc.Server;
namespace Avalonia
{
public static class HeadlessVncPlatformExtensions
{
public static int StartWithHeadlessVncPlatform<T>(
this T builder,
string host, int port,
string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
where T : AppBuilderBase<T>, new()
{
var tcpServer = new TcpListener(host == null ? IPAddress.Loopback : IPAddress.Parse(host), port);
tcpServer.Start();
return builder
.UseHeadless(false)
.AfterSetup(_ =>
{
var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime);
lt.Startup += async delegate
{
while (true)
{
var client = await tcpServer.AcceptTcpClientAsync();
var options = new VncServerSessionOptions
{
AuthenticationMethod = AuthenticationMethod.None
};
var session = new VncServerSession();
session.SetFramebufferSource(new HeadlessVncFramebufferSource(
session, lt.MainWindow));
session.Connect(client.GetStream(), options);
}
};
})
.StartWithClassicDesktopLifetime(args, shutdownMode);
}
}
}

8
src/Avalonia.Headless/Avalonia.Headless.csproj

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
</ItemGroup>
</Project>

94
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -0,0 +1,94 @@
using System;
using System.Diagnostics;
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.Headless
{
public static class AvaloniaHeadlessPlatform
{
class RenderTimer : DefaultRenderTimer
{
private readonly int _framesPerSecond;
private Action _forceTick;
protected override IDisposable StartCore(Action<TimeSpan> tick)
{
bool cancelled = false;
var st = Stopwatch.StartNew();
_forceTick = () => tick(st.Elapsed);
DispatcherTimer.Run(() =>
{
if (cancelled)
return false;
tick(st.Elapsed);
return !cancelled;
}, TimeSpan.FromSeconds(1.0 / _framesPerSecond), DispatcherPriority.Render);
return Disposable.Create(() =>
{
_forceTick = null;
cancelled = true;
});
}
public RenderTimer(int framesPerSecond) : base(framesPerSecond)
{
_framesPerSecond = framesPerSecond;
}
public void ForceTick() => _forceTick?.Invoke();
}
class HeadlessWindowingPlatform : IWindowingPlatform
{
public IWindowImpl CreateWindow() => new HeadlessWindowImpl(false);
public IWindowImpl CreateEmbeddableWindow() => throw new PlatformNotSupportedException();
public IPopupImpl CreatePopup() => new HeadlessWindowImpl(true);
}
internal static void Initialize()
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface())
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>()
.Bind<IStandardCursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<IPlatformSettings>().ToConstant(new HeadlessPlatformSettingsStub())
.Bind<ISystemDialogImpl>().ToSingleton<HeadlessSystemDialogsStub>()
.Bind<IPlatformIconLoader>().ToSingleton<HeadlessIconLoaderStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new RenderTimer(60))
.Bind<IFontManagerImpl>().ToSingleton<HeadlessFontManagerStub>()
.Bind<ITextShaperImpl>().ToSingleton<HeadlessTextShaperStub>()
.Bind<IWindowingPlatform>().ToConstant(new HeadlessWindowingPlatform())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}
public static void ForceRenderTimerTick(int count = 1)
{
var timer = AvaloniaLocator.Current.GetService<IRenderTimer>() as RenderTimer;
for (var c = 0; c < count; c++)
timer?.ForceTick();
}
}
public static class AvaloniaHeadlessPlatformExtensions
{
public static T UseHeadless<T>(this T builder, bool headlessDrawing = true)
where T : AppBuilderBase<T>, new()
{
if (headlessDrawing)
builder.UseRenderingSubsystem(HeadlessPlatformRenderInterface.Initialize, "Headless");
return builder.UseWindowingSubsystem(AvaloniaHeadlessPlatform.Initialize, "Headless");
}
}
}

434
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -0,0 +1,434 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Headless
{
internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface
{
public static void Initialize()
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(new HeadlessPlatformRenderInterface());
}
public IEnumerable<string> InstalledFontNames { get; } = new[] { "Tahoma" };
public bool SupportsIndividualRoundRects => throw new NotImplementedException();
public IFormattedTextImpl CreateFormattedText(string text, Typeface typeface, double fontSize, TextAlignment textAlignment, TextWrapping wrapping, Size constraint, IReadOnlyList<FormattedTextStyleSpan> spans)
{
return new HeadlessFormattedTextStub(text, constraint);
}
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new HeadlessGeometryStub(rect);
public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
{
var tl = new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y));
var br = new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y));
return new HeadlessGeometryStub(new Rect(tl, br));
}
public IGeometryImpl CreateRectangleGeometry(Rect rect)
{
return new HeadlessGeometryStub(rect);
}
public IStreamGeometryImpl CreateStreamGeometry() => new HeadlessStreamingGeometryStub();
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
return new HeadlessBitmapStub(size, dpi);
}
public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
{
return new HeadlessBitmapStub(size, dpi);
}
public IBitmapImpl LoadBitmap(string fileName)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IBitmapImpl LoadBitmap(Stream stream)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new HeadlessBitmapStub(new Size(width, width), new Vector(96, 96));
}
public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new HeadlessBitmapStub(new Size(height, height), new Vector(96, 96));
}
public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new HeadlessBitmapStub(destinationSize, new Vector(96, 96));
}
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width)
{
width = 100;
return new HeadlessGlyphRunStub();
}
class HeadlessGeometryStub : IGeometryImpl
{
public HeadlessGeometryStub(Rect bounds)
{
Bounds = bounds;
}
public Rect Bounds { get; set; }
public virtual bool FillContains(Point point) => Bounds.Contains(point);
public Rect GetRenderBounds(IPen pen)
{
if(pen is null)
{
return Bounds;
}
return Bounds.Inflate(pen.Thickness / 2);
}
public bool StrokeContains(IPen pen, Point point)
{
return false;
}
public IGeometryImpl Intersect(IGeometryImpl geometry)
=> new HeadlessGeometryStub(geometry.Bounds.Intersect(Bounds));
public ITransformedGeometryImpl WithTransform(Matrix transform) =>
new HeadlessTransformedGeometryStub(this, transform);
}
class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
{
public HeadlessTransformedGeometryStub(IGeometryImpl b, Matrix transform) : this(Fix(b, transform))
{
}
static (IGeometryImpl, Matrix, Rect) Fix(IGeometryImpl b, Matrix transform)
{
if (b is HeadlessTransformedGeometryStub transformed)
{
b = transformed.SourceGeometry;
transform = transformed.Transform * transform;
}
return (b, transform, b.Bounds.TransformToAABB(transform));
}
private HeadlessTransformedGeometryStub((IGeometryImpl b, Matrix transform, Rect bounds) fix) : base(fix.bounds)
{
SourceGeometry = fix.b;
Transform = fix.transform;
}
public IGeometryImpl SourceGeometry { get; }
public Matrix Transform { get; }
}
class HeadlessGlyphRunStub : IGlyphRunImpl
{
public void Dispose()
{
}
}
class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
{
public HeadlessStreamingGeometryStub() : base(Rect.Empty)
{
}
public IStreamGeometryImpl Clone()
{
return this;
}
public IStreamGeometryContextImpl Open()
{
return new HeadlessStreamingGeometryContextStub(this);
}
class HeadlessStreamingGeometryContextStub : IStreamGeometryContextImpl
{
private readonly HeadlessStreamingGeometryStub _parent;
private double _x1, _y1, _x2, _y2;
public HeadlessStreamingGeometryContextStub(HeadlessStreamingGeometryStub parent)
{
_parent = parent;
}
void Track(Point pt)
{
if (_x1 > pt.X)
_x1 = pt.X;
if (_x2 < pt.X)
_x2 = pt.X;
if (_y1 > pt.Y)
_y1 = pt.Y;
if (_y2 < pt.Y)
_y2 = pt.Y;
}
public void Dispose()
{
_parent.Bounds = new Rect(_x1, _y1, _x2 - _x1, _y2 - _y1);
}
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
=> Track(point);
public void BeginFigure(Point startPoint, bool isFilled = true) => Track(startPoint);
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
Track(point1);
Track(point2);
Track(point3);
}
public void QuadraticBezierTo(Point control, Point endPoint)
{
Track(control);
Track(endPoint);
}
public void LineTo(Point point) => Track(point);
public void EndFigure(bool isClosed)
{
Dispose();
}
public void SetFillRule(FillRule fillRule)
{
}
}
}
class HeadlessBitmapStub : IBitmapImpl, IRenderTargetBitmapImpl, IWriteableBitmapImpl
{
public Size Size { get; }
public HeadlessBitmapStub(Size size, Vector dpi)
{
Size = size;
Dpi = dpi;
var pixel = Size * (Dpi / 96);
PixelSize = new PixelSize(Math.Max(1, (int)pixel.Width), Math.Max(1, (int)pixel.Height));
}
public HeadlessBitmapStub(PixelSize size, Vector dpi)
{
PixelSize = size;
Dpi = dpi;
Size = PixelSize.ToSizeWithDpi(dpi);
}
public void Dispose()
{
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new HeadlessDrawingContextStub();
}
public Vector Dpi { get; }
public PixelSize PixelSize { get; }
public int Version { get; set; }
public void Save(string fileName)
{
}
public void Save(Stream stream)
{
}
public ILockedFramebuffer Lock()
{
Version++;
var mem = Marshal.AllocHGlobal(PixelSize.Width * PixelSize.Height * 4);
return new LockedFramebuffer(mem, PixelSize, PixelSize.Width * 4, Dpi, PixelFormat.Rgba8888,
() => Marshal.FreeHGlobal(mem));
}
}
class HeadlessDrawingContextStub : IDrawingContextImpl
{
public void Dispose()
{
}
public Matrix Transform { get; set; }
public void Clear(Color color)
{
}
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return new HeadlessBitmapStub(size, new Vector(96, 96));
}
public void PushClip(Rect clip)
{
}
public void PopClip()
{
}
public void PushOpacity(double opacity)
{
}
public void PopOpacity()
{
}
public void PushOpacityMask(IBrush mask, Rect bounds)
{
}
public void PopOpacityMask()
{
}
public void PushGeometryClip(IGeometryImpl clip)
{
}
public void PopGeometryClip()
{
}
public void Custom(ICustomDrawOperation custom)
{
}
public void DrawLine(IPen pen, Point p1, Point p2)
{
throw new NotImplementedException();
}
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
}
public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0)
{
}
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{
}
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
}
public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadow = default)
{
}
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin)
{
}
public void PushClip(RoundedRect clip)
{
}
}
class HeadlessRenderTarget : IRenderTarget
{
public void Dispose()
{
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new HeadlessDrawingContextStub();
}
}
class HeadlessFormattedTextStub : IFormattedTextImpl
{
public HeadlessFormattedTextStub(string text, Size constraint)
{
Text = text;
Constraint = constraint;
Bounds = new Rect(Constraint.Constrain(new Size(50, 50)));
}
public Size Constraint { get; }
public Rect Bounds { get; }
public string Text { get; }
public IEnumerable<FormattedTextLine> GetLines()
{
return new[] { new FormattedTextLine(Text.Length, 10) };
}
public TextHitTestResult HitTestPoint(Point point) => new TextHitTestResult();
public Rect HitTestTextPosition(int index) => new Rect();
public IEnumerable<Rect> HitTestTextRange(int index, int length) => new Rect[length];
}
}
}

201
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Headless
{
class HeadlessClipboardStub : IClipboard
{
private string _text;
private IDataObject _data;
public Task<string> GetTextAsync()
{
return Task.Run(() => _text);
}
public Task SetTextAsync(string text)
{
return Task.Run(() => _text = text);
}
public Task ClearAsync()
{
return Task.Run(() => _text = null);
}
public Task SetDataObjectAsync(IDataObject data)
{
return Task.Run(() => _data = data);
}
public Task<string[]> GetFormatsAsync()
{
throw new NotImplementedException();
}
public async Task<object> GetDataAsync(string format)
{
return await Task.Run(() => _data);
}
}
class HeadlessCursorFactoryStub : IStandardCursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
{
return new PlatformHandle(new IntPtr((int)cursorType), "STUB");
}
}
class HeadlessPlatformSettingsStub : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
}
class HeadlessSystemDialogsStub : ISystemDialogImpl
{
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{
return Task.Run(() => (string[])null);
}
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{
return Task.Run(() => (string)null);
}
}
class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl
{
public short DesignEmHeight => 10;
public int Ascent => 5;
public int Descent => 5;
public int LineGap => 2;
public int UnderlinePosition => 5;
public int UnderlineThickness => 5;
public int StrikethroughPosition => 5;
public int StrikethroughThickness => 2;
public bool IsFixedPitch => true;
public void Dispose()
{
}
public ushort GetGlyph(uint codepoint)
{
return 1;
}
public int GetGlyphAdvance(ushort glyph)
{
return 1;
}
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
return glyphs.ToArray().Select(x => (int)x).ToArray();
}
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{
return codepoints.ToArray().Select(x => (ushort)x).ToArray();
}
}
class HeadlessTextShaperStub : ITextShaperImpl
{
public GlyphRun ShapeText(ReadOnlySlice<char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture)
{
return new GlyphRun(new GlyphTypeface(typeface), 10,
new ReadOnlySlice<ushort>(new ushort[] { 1, 2, 3 }),
new ReadOnlySlice<double>(new double[] { 1, 2, 3 }),
new ReadOnlySlice<Vector>(new Vector[] { new Vector(1, 1), new Vector(2, 2), new Vector(3, 3) }),
text,
new ReadOnlySlice<ushort>(new ushort[] { 1, 2, 3 }));
}
}
class HeadlessFontManagerStub : IFontManagerImpl
{
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
{
return new HeadlessGlyphTypefaceImpl();
}
public string GetDefaultFontFamilyName()
{
return "Arial";
}
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false)
{
return new List<string> { "Arial" };
}
public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
{
fontKey = new FontKey("Arial", fontWeight, fontStyle);
return true;
}
}
class HeadlessIconLoaderStub : IPlatformIconLoader
{
class IconStub : IWindowIconImpl
{
public void Save(Stream outputStream)
{
}
}
public IWindowIconImpl LoadIcon(string fileName)
{
return new IconStub();
}
public IWindowIconImpl LoadIcon(Stream stream)
{
return new IconStub();
}
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
return new IconStub();
}
}
class HeadlessScreensStub : IScreenImpl
{
public int ScreenCount { get; } = 1;
public IReadOnlyList<Screen> AllScreens { get; } = new[]
{
new Screen(1, new PixelRect(0, 0, 1920, 1280),
new PixelRect(0, 0, 1920, 1280), true),
};
}
}

86
src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs

@ -0,0 +1,86 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Headless
{
class HeadlessPlatformThreadingInterface : IPlatformThreadingInterface
{
public HeadlessPlatformThreadingInterface()
{
_thread = Thread.CurrentThread;
}
private AutoResetEvent _event = new AutoResetEvent(false);
private Thread _thread;
private object _lock = new object();
private DispatcherPriority? _signaledPriority;
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
DispatcherPriority? signaled = null;
lock (_lock)
{
signaled = _signaledPriority;
_signaledPriority = null;
}
if(signaled.HasValue)
Signaled?.Invoke(signaled);
WaitHandle.WaitAny(new[] {cancellationToken.WaitHandle, _event}, TimeSpan.FromMilliseconds(20));
}
}
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
var cancelled = false;
var enqueued = false;
var l = new object();
var timer = new Timer(_ =>
{
lock (l)
{
if (cancelled || enqueued)
return;
enqueued = true;
Dispatcher.UIThread.Post(() =>
{
lock (l)
{
enqueued = false;
if (cancelled)
return;
tick();
}
}, priority);
}
}, null, interval, interval);
return Disposable.Create(() =>
{
lock (l)
{
timer.Dispose();
cancelled = true;
}
});
}
public void Signal(DispatcherPriority priority)
{
lock (_lock)
{
if (_signaledPriority == null || _signaledPriority.Value > priority)
{
_signaledPriority = priority;
}
_event.Set();
}
}
public bool CurrentThreadIsLoopThread => _thread == Thread.CurrentThread;
public event Action<DispatcherPriority?> Signaled;
}
}

342
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -0,0 +1,342 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Headless
{
class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
{
private IKeyboardDevice _keyboard;
private Stopwatch _st = Stopwatch.StartNew();
private Pointer _mousePointer;
private WriteableBitmap _lastRenderedFrame;
private object _sync = new object();
public bool IsPopup { get; }
public HeadlessWindowImpl(bool isPopup)
{
IsPopup = isPopup;
Surfaces = new object[] { this };
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mousePointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
MouseDevice = new MouseDevice(_mousePointer);
ClientSize = new Size(1024, 768);
}
public void Dispose()
{
Closed?.Invoke();
_lastRenderedFrame?.Dispose();
_lastRenderedFrame = null;
}
public Size ClientSize { get; set; }
public double Scaling { get; } = 1;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root)
=> new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>());
public void Invalidate(Rect rect)
{
}
public void SetInputRoot(IInputRoot inputRoot)
{
InputRoot = inputRoot;
}
public IInputRoot InputRoot { get; set; }
public Point PointToClient(PixelPoint point) => point.ToPoint(Scaling);
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, Scaling);
public void SetCursor(IPlatformHandle cursor)
{
}
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; }
public void Show()
{
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
}
public void Hide()
{
Dispatcher.UIThread.Post(() => Deactivated?.Invoke(), DispatcherPriority.Input);
}
public void BeginMoveDrag()
{
}
public void BeginResizeDrag(WindowEdge edge)
{
}
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public void Activate()
{
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
}
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB");
public Size MaxClientSize { get; } = new Size(1920, 1280);
public void Resize(Size clientSize)
{
// Emulate X11 behavior here
if (IsPopup)
DoResize(clientSize);
else
Dispatcher.UIThread.Post(() =>
{
DoResize(clientSize);
});
}
void DoResize(Size clientSize)
{
// Uncomment this check and experience a weird bug in layout engine
if (ClientSize != clientSize)
{
ClientSize = clientSize;
Resized?.Invoke(clientSize);
}
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public void SetTopmost(bool value)
{
}
public IScreenImpl Screen { get; } = new HeadlessScreensStub();
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public void SetTitle(string title)
{
}
public void ShowDialog(IWindowImpl parent)
{
Show();
}
public void SetSystemDecorations(bool enabled)
{
}
public void SetIcon(IWindowIconImpl icon)
{
}
public void ShowTaskbarIcon(bool value)
{
}
public void CanResize(bool value)
{
}
public Func<bool> Closing { get; set; }
class FramebufferProxy : ILockedFramebuffer
{
private readonly ILockedFramebuffer _fb;
private readonly Action _onDispose;
private bool _disposed;
public FramebufferProxy(ILockedFramebuffer fb, Action onDispose)
{
_fb = fb;
_onDispose = onDispose;
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
_fb.Dispose();
_onDispose();
}
public IntPtr Address => _fb.Address;
public PixelSize Size => _fb.Size;
public int RowBytes => _fb.RowBytes;
public Vector Dpi => _fb.Dpi;
public PixelFormat Format => _fb.Format;
}
public ILockedFramebuffer Lock()
{
var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, Scaling), new Vector(96, 96) * Scaling);
var fb = bmp.Lock();
return new FramebufferProxy(fb, () =>
{
lock (_sync)
{
_lastRenderedFrame?.Dispose();
_lastRenderedFrame = bmp;
}
});
}
public IRef<IWriteableBitmapImpl> GetLastRenderedFrame()
{
lock (_sync)
return _lastRenderedFrame?.PlatformImpl?.CloneAs<IWriteableBitmapImpl>();
}
private ulong Timestamp => (ulong)_st.ElapsedMilliseconds;
// TODO: Hook recent Popup changes.
IPopupPositioner IPopupImpl.PopupPositioner => null;
public Size MaxAutoSizeHint => new Size(1920, 1080);
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public Action GotInputWhenDisabled { get; set; }
public bool IsClientAreaExtendedToDecorations => false;
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
public bool NeedsManagedDecorations => false;
public Thickness ExtendedMargins => new Thickness();
public Thickness OffScreenMargin => new Thickness();
public Action LostFocus { get; set; }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers)
{
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyDown, key, modifiers));
}
void IHeadlessWindow.KeyRelease(Key key, RawInputModifiers modifiers)
{
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyUp, key, modifiers));
}
void IHeadlessWindow.MouseDown(Point point, int button, RawInputModifiers modifiers)
{
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
button == 0 ? RawPointerEventType.LeftButtonDown :
button == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.RightButtonDown,
point, modifiers));
}
void IHeadlessWindow.MouseMove(Point point, RawInputModifiers modifiers)
{
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
RawPointerEventType.Move, point, modifiers));
}
void IHeadlessWindow.MouseUp(Point point, int button, RawInputModifiers modifiers)
{
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
button == 0 ? RawPointerEventType.LeftButtonUp :
button == 1 ? RawPointerEventType.MiddleButtonUp : RawPointerEventType.RightButtonUp,
point, modifiers));
}
void IWindowImpl.Move(PixelPoint point)
{
}
public IPopupImpl CreatePopup()
{
// TODO: Hook recent Popup changes.
return null;
}
public void SetWindowManagerAddShadowHint(bool enabled)
{
}
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
}
public void SetParent(IWindowImpl parent)
{
}
public void SetEnabled(bool enable)
{
}
public void SetSystemDecorations(SystemDecorations enabled)
{
}
public void BeginMoveDrag(PointerPressedEventArgs e)
{
}
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e)
{
}
public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
{
}
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
{
}
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
}
}
}

17
src/Avalonia.Headless/IHeadlessWindow.cs

@ -0,0 +1,17 @@
using Avalonia.Input;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Headless
{
public interface IHeadlessWindow
{
IRef<IWriteableBitmapImpl> GetLastRenderedFrame();
void KeyPress(Key key, RawInputModifiers modifiers);
void KeyRelease(Key key, RawInputModifiers modifiers);
void MouseDown(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseUp(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
}
}

37
src/Avalonia.Layout/WrapLayout/UvBounds.cs

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Avalonia;
using Avalonia.Layout;
namespace Avalonia.Layout
{
internal struct UvBounds
{
public UvBounds(Orientation orientation, Rect rect)
{
if (orientation == Orientation.Horizontal)
{
UMin = rect.Left;
UMax = rect.Right;
VMin = rect.Top;
VMax = rect.Bottom;
}
else
{
UMin = rect.Top;
UMax = rect.Bottom;
VMin = rect.Left;
VMax = rect.Right;
}
}
public double UMin { get; }
public double UMax { get; }
public double VMin { get; }
public double VMax { get; }
}
}

45
src/Avalonia.Layout/WrapLayout/UvMeasure.cs

@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Avalonia.Layout;
namespace Avalonia.Layout
{
internal struct UvMeasure
{
internal static readonly UvMeasure Zero = default(UvMeasure);
internal double U { get; set; }
internal double V { get; set; }
public UvMeasure(Orientation orientation, double width, double height)
{
if (orientation == Orientation.Horizontal)
{
U = width;
V = height;
}
else
{
U = height;
V = width;
}
}
public override bool Equals(object obj)
{
if (obj is UvMeasure measure)
{
return (measure.U == U) && (measure.V == V);
}
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

25
src/Avalonia.Layout/WrapLayout/WrapItem.cs

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Avalonia.Controls;
using Avalonia.Layout;
namespace Avalonia.Layout
{
internal class WrapItem
{
public WrapItem(int index)
{
this.Index = index;
}
public int Index { get; }
public UvMeasure? Measure { get; internal set; }
public UvMeasure? Position { get; internal set; }
public ILayoutable Element { get; internal set; }
}
}

327
src/Avalonia.Layout/WrapLayout/WrapLayout.cs

@ -0,0 +1,327 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Layout;
using System;
using System.Collections.Specialized;
namespace Avalonia.Layout
{
/// <summary>
/// Arranges elements by wrapping them to fit the available space.
/// When <see cref="Orientation"/> is set to Orientation.Horizontal, element are arranged in rows until the available width is reached and then to a new row.
/// When <see cref="Orientation"/> is set to Orientation.Vertical, element are arranged in columns until the available height is reached.
/// </summary>
public class WrapLayout : VirtualizingLayout
{
/// <summary>
/// Gets or sets a uniform Horizontal distance (in pixels) between items when <see cref="Orientation"/> is set to Horizontal,
/// or between columns of items when <see cref="Orientation"/> is set to Vertical.
/// </summary>
public double HorizontalSpacing
{
get { return (double)GetValue(HorizontalSpacingProperty); }
set { SetValue(HorizontalSpacingProperty, value); }
}
/// <summary>
/// Identifies the <see cref="HorizontalSpacing"/> dependency property.
/// </summary>
public static readonly StyledProperty<double> HorizontalSpacingProperty =
AvaloniaProperty.Register<WrapLayout, double>(nameof(HorizontalSpacing), 0);
/// <summary>
/// Gets or sets a uniform Vertical distance (in pixels) between items when <see cref="Orientation"/> is set to Vertical,
/// or between rows of items when <see cref="Orientation"/> is set to Horizontal.
/// </summary>
public double VerticalSpacing
{
get { return (double)GetValue(VerticalSpacingProperty); }
set { SetValue(VerticalSpacingProperty, value); }
}
/// <summary>
/// Identifies the <see cref="VerticalSpacing"/> dependency property.
/// </summary>
public static readonly StyledProperty<double> VerticalSpacingProperty =
AvaloniaProperty.Register<WrapLayout, double>(
nameof(VerticalSpacing), 0d);
/// <summary>
/// Gets or sets the orientation of the WrapLayout.
/// Horizontal means that child controls will be added horizontally until the width of the panel is reached, then a new row is added to add new child controls.
/// Vertical means that children will be added vertically until the height of the panel is reached, then a new column is added.
/// </summary>
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Identifies the <see cref="Orientation"/> dependency property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<WrapLayout, Orientation>(
nameof(Orientation),
Orientation.Horizontal);
/// <inheritdoc />
protected internal override void InitializeForContextCore(VirtualizingLayoutContext context)
{
var state = new WrapLayoutState(context);
context.LayoutState = state;
base.InitializeForContextCore(context);
}
/// <inheritdoc />
protected internal override void UninitializeForContextCore(VirtualizingLayoutContext context)
{
context.LayoutState = null;
base.UninitializeForContextCore(context);
}
/// <inheritdoc />
protected internal override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args)
{
var state = (WrapLayoutState)context.LayoutState;
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
state.RemoveFromIndex(args.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Move:
int minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex);
state.RemoveFromIndex(minIndex);
state.RecycleElementAt(args.OldStartingIndex);
state.RecycleElementAt(args.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
state.RemoveFromIndex(args.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Replace:
state.RemoveFromIndex(args.NewStartingIndex);
state.RecycleElementAt(args.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
state.Clear();
break;
}
base.OnItemsChangedCore(context, source, args);
}
/// <inheritdoc />
protected internal override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
{
var totalMeasure = UvMeasure.Zero;
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
var realizationBounds = new UvBounds(Orientation, context.RealizationRect);
var position = UvMeasure.Zero;
var state = (WrapLayoutState)context.LayoutState;
if (state.Orientation != Orientation)
{
state.SetOrientation(Orientation);
}
if (spacingMeasure.Equals(state.Spacing) == false)
{
state.ClearPositions();
state.Spacing = spacingMeasure;
}
if (state.AvailableU != parentMeasure.U)
{
state.ClearPositions();
state.AvailableU = parentMeasure.U;
}
double currentV = 0;
for (int i = 0; i < context.ItemCount; i++)
{
bool measured = false;
WrapItem item = state.GetItemAt(i);
if (item.Measure == null)
{
item.Element = context.GetOrCreateElementAt(i);
item.Element.Measure(availableSize);
item.Measure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height);
measured = true;
}
UvMeasure currentMeasure = item.Measure.Value;
if (currentMeasure.U == 0)
{
continue; // ignore collapsed items
}
if (item.Position == null)
{
if (parentMeasure.U < position.U + currentMeasure.U)
{
// New Row
position.U = 0;
position.V += currentV + spacingMeasure.V;
currentV = 0;
}
item.Position = position;
}
position = item.Position.Value;
double vEnd = position.V + currentMeasure.V;
if (vEnd < realizationBounds.VMin)
{
// Item is "above" the bounds
if (item.Element != null)
{
context.RecycleElement(item.Element);
item.Element = null;
}
}
else if (position.V > realizationBounds.VMax)
{
// Item is "below" the bounds.
if (item.Element != null)
{
context.RecycleElement(item.Element);
item.Element = null;
}
// We don't need to measure anything below the bounds
break;
}
else if (measured == false)
{
// Always measure elements that are within the bounds
item.Element = context.GetOrCreateElementAt(i);
item.Element.Measure(availableSize);
currentMeasure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height);
if (currentMeasure.Equals(item.Measure) == false)
{
// this item changed size; we need to recalculate layout for everything after this
state.RemoveFromIndex(i + 1);
item.Measure = currentMeasure;
// did the change make it go into the new row?
if (parentMeasure.U < position.U + currentMeasure.U)
{
// New Row
position.U = 0;
position.V += currentV + spacingMeasure.V;
currentV = 0;
}
item.Position = position;
}
}
position.U += currentMeasure.U + spacingMeasure.U;
currentV = Math.Max(currentMeasure.V, currentV);
}
// update value with the last line
// if the the last loop is(parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it
// if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here
// for the last condition it is zeros so adding it will make no difference
// this way is faster than an if condition in every loop for checking the last item
totalMeasure.U = parentMeasure.U;
totalMeasure.V = state.GetHeight();
totalMeasure.U = Math.Ceiling(totalMeasure.U);
return Orientation == Orientation.Horizontal ? new Size(totalMeasure.U, totalMeasure.V) : new Size(totalMeasure.V, totalMeasure.U);
}
/// <inheritdoc />
protected internal override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
{
if (context.ItemCount > 0)
{
var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height);
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
var realizationBounds = new UvBounds(Orientation, context.RealizationRect);
var state = (WrapLayoutState)context.LayoutState;
bool Arrange(WrapItem item, bool isLast = false)
{
if (item.Measure.HasValue == false)
{
return false;
}
if (item.Position == null)
{
return false;
}
var desiredMeasure = item.Measure.Value;
if (desiredMeasure.U == 0)
{
return true; // if an item is collapsed, avoid adding the spacing
}
UvMeasure position = item.Position.Value;
// Stretch the last item to fill the available space
if (isLast)
{
desiredMeasure.U = parentMeasure.U - position.U;
}
if (((position.V + desiredMeasure.V) >= realizationBounds.VMin) && (position.V <= realizationBounds.VMax))
{
// place the item
var child = context.GetOrCreateElementAt(item.Index);
if (Orientation == Orientation.Horizontal)
{
child.Arrange(new Rect(position.U, position.V, desiredMeasure.U, desiredMeasure.V));
}
else
{
child.Arrange(new Rect(position.V, position.U, desiredMeasure.V, desiredMeasure.U));
}
}
else if (position.V > realizationBounds.VMax)
{
return false;
}
return true;
}
for (var i = 0; i < context.ItemCount; i++)
{
bool continueArranging = Arrange(state.GetItemAt(i));
if (continueArranging == false)
{
break;
}
}
}
return finalSize;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == OrientationProperty || change.Property == HorizontalSpacingProperty || change.Property == VerticalSpacingProperty)
{
InvalidateMeasure();
InvalidateArrange();
}
}
}
}

143
src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs

@ -0,0 +1,143 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Layout
{
internal class WrapLayoutState
{
private List<WrapItem> _items = new List<WrapItem>();
private VirtualizingLayoutContext _context;
public WrapLayoutState(VirtualizingLayoutContext context)
{
this._context = context;
}
public Orientation Orientation { get; private set; }
public UvMeasure Spacing { get; internal set; }
public double AvailableU { get; internal set; }
internal WrapItem GetItemAt(int index)
{
if (index < 0)
{
throw new IndexOutOfRangeException();
}
if (index <= (_items.Count - 1))
{
return _items[index];
}
else
{
WrapItem item = new WrapItem(index);
_items.Add(item);
return item;
}
}
internal void Clear()
{
_items.Clear();
}
internal void RemoveFromIndex(int index)
{
if (index >= _items.Count)
{
// Item was added/removed but we haven't realized that far yet
return;
}
int numToRemove = _items.Count - index;
_items.RemoveRange(index, numToRemove);
}
internal void SetOrientation(Orientation orientation)
{
foreach (var item in _items.Where(i => i.Measure.HasValue))
{
UvMeasure measure = item.Measure.Value;
double v = measure.V;
measure.V = measure.U;
measure.U = v;
item.Measure = measure;
item.Position = null;
}
Orientation = orientation;
AvailableU = 0;
}
internal void ClearPositions()
{
foreach (var item in _items)
{
item.Position = null;
}
}
internal double GetHeight()
{
if (_items.Count == 0)
{
return 0;
}
bool calculateAverage = true;
if ((_items.Count == _context.ItemCount) && _items[_items.Count - 1].Position.HasValue)
{
calculateAverage = false;
}
UvMeasure? lastPosition = null;
double maxV = 0;
int itemCount = _items.Count;
for (int i = _items.Count - 1; i >= 0; i--)
{
var item = _items[i];
if (item.Position == null)
{
itemCount--;
continue;
}
if (lastPosition != null)
{
if (lastPosition.Value.V > item.Position.Value.V)
{
// This is a row above the last item. Exit and calculate the average
break;
}
}
lastPosition = item.Position;
maxV = Math.Max(maxV, item.Measure.Value.V);
}
double totalHeight = lastPosition.Value.V + maxV;
if (calculateAverage)
{
return (totalHeight / itemCount) * _context.ItemCount;
}
else
{
return totalHeight;
}
}
internal void RecycleElementAt(int index)
{
var element = _context.GetOrCreateElementAt(index);
_context.RecycleElement(element);
}
}
}

86
src/Avalonia.Native/WindowImpl.cs

@ -1,6 +1,8 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Native.Interop;
using Avalonia.OpenGL;
using Avalonia.Platform;
@ -14,6 +16,8 @@ namespace Avalonia.Native
private readonly AvaloniaNativePlatformOptions _opts;
private readonly GlPlatformFeature _glFeature;
IAvnWindow _native;
private double _extendTitleBarHeight = -1;
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
GlPlatformFeature glFeature) : base(opts, glFeature)
{
@ -50,6 +54,8 @@ namespace Avalonia.Native
void IAvnWindowEvents.WindowStateChanged(AvnWindowState state)
{
_parent.InvalidateExtendedMargins();
_parent.WindowStateChanged?.Invoke((WindowState)state);
}
@ -96,7 +102,85 @@ namespace Avalonia.Native
}
}
public Action<WindowState> WindowStateChanged { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
public Thickness ExtendedMargins { get; private set; }
public Thickness OffScreenMargin { get; } = new Thickness();
private bool _isExtended;
public bool IsClientAreaExtendedToDecorations => _isExtended;
protected override bool ChromeHitTest (RawPointerEventArgs e)
{
if(_isExtended)
{
if(e.Type == RawPointerEventType.LeftButtonDown)
{
var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x =>
{
if (x is IInputElement ie && !ie.IsHitTestVisible)
{
return false;
}
return true;
});
if(visual == null)
{
_native.BeginMoveDrag();
}
}
}
return false;
}
private void InvalidateExtendedMargins()
{
if (WindowState == WindowState.FullScreen)
{
ExtendedMargins = new Thickness();
}
else
{
ExtendedMargins = _isExtended ? new Thickness(0, _extendTitleBarHeight == -1 ? _native.GetExtendTitleBarHeight() : _extendTitleBarHeight, 0, 0) : new Thickness();
}
ExtendClientAreaToDecorationsChanged?.Invoke(_isExtended);
}
/// <inheritdoc/>
public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
{
_isExtended = extendIntoClientAreaHint;
_native.SetExtendClientArea(extendIntoClientAreaHint);
InvalidateExtendedMargins();
}
/// <inheritdoc/>
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
{
_native.SetExtendClientAreaHints ((AvnExtendClientAreaChromeHints)hints);
}
/// <inheritdoc/>
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
_extendTitleBarHeight = titleBarHeight;
_native.SetExtendTitleBarHeight(titleBarHeight);
ExtendedMargins = _isExtended ? new Thickness(0, titleBarHeight == -1 ? _native.GetExtendTitleBarHeight() : titleBarHeight, 0, 0) : new Thickness();
ExtendClientAreaToDecorationsChanged?.Invoke(_isExtended);
}
/// <inheritdoc/>
public bool NeedsManagedDecorations => false;
public void ShowTaskbarIcon(bool value)
{

16
src/Avalonia.Native/WindowImplBase.cs

@ -46,7 +46,7 @@ namespace Avalonia.Native
public abstract class WindowBaseImpl : IWindowBaseImpl,
IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost
{
IInputRoot _inputRoot;
protected IInputRoot _inputRoot;
IAvnWindowBase _native;
private object _syncRoot = new object();
private bool _deferredRendering = false;
@ -266,6 +266,11 @@ namespace Avalonia.Native
return args.Handled;
}
protected virtual bool ChromeHitTest(RawPointerEventArgs e)
{
return false;
}
public void RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
@ -277,7 +282,12 @@ namespace Avalonia.Native
break;
default:
Input?.Invoke(new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (RawInputModifiers)modifiers));
var e = new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (RawInputModifiers)modifiers);
if(!ChromeHitTest(e))
{
Input?.Invoke(e);
}
break;
}
}
@ -429,6 +439,8 @@ namespace Avalonia.Native
public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent;
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
public IPlatformHandle Handle { get; private set; }
}
}

1
src/Avalonia.Styling/Styling/Setter.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.Animation;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Metadata;
using Avalonia.Utilities;

69
src/Avalonia.Themes.Default/CaptionButtons.xaml

@ -0,0 +1,69 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="CaptionButtons">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="MaxHeight" Value="30" />
<Setter Property="Template">
<ControlTemplate>
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Panel">
<Setter Property="Width" Value="45" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Panel:pointerover">
<Setter Property="Background" Value="#7F7f7f7f" />
</Style>
<Style Selector="Panel#PART_CloseButton:pointerover">
<Setter Property="Background" Value="#7FFF0000" />
</Style>
<Style Selector="Viewbox">
<Setter Property="Width" Value="11" />
<Setter Property="Margin" Value="2" />
</Style>
</StackPanel.Styles>
<Panel x:Name="PART_FullScreenButton">
<Viewbox>
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" />
</Viewbox>
</Panel>
<Panel x:Name="PART_MinimiseButton">
<Viewbox>
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" />
</Viewbox>
</Panel>
<Panel x:Name="PART_RestoreButton">
<Viewbox>
<Viewbox.RenderTransform>
<RotateTransform Angle="-90" />
</Viewbox.RenderTransform>
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/>
</Viewbox>
</Panel>
<Panel x:Name="PART_CloseButton">
<Viewbox>
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" />
</Viewbox>
</Panel>
</StackPanel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path">
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" />
</Style>
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path">
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" />
</Style>
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path">
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" />
</Style>
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton">
<Setter Property="IsVisible" Value="False" />
</Style>
</Styles>

40
src/Avalonia.Themes.Default/ComboBox.xaml

@ -1,10 +1,29 @@
<Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="10">
<ComboBox PlaceholderText="Select an item">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
<ComboBox IsEnabled="False"
Width="200"
SelectedIndex="1"
HorizontalContentAlignment="Center">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="ComboBox">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="4" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="border"
@ -15,8 +34,8 @@
<ContentControl Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding ItemTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
<ToggleButton Name="toggle"
BorderThickness="0"
Background="Transparent"
@ -30,7 +49,7 @@
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z"/>
Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z" />
</ToggleButton>
<Popup Name="PART_Popup"
IsOpen="{TemplateBinding IsDropDownOpen, Mode=TwoWay}"
@ -45,8 +64,7 @@
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
VirtualizationMode="{TemplateBinding VirtualizationMode}"
/>
VirtualizationMode="{TemplateBinding VirtualizationMode}" />
</ScrollViewer>
</Border>
</Popup>
@ -56,7 +74,7 @@
</Setter>
</Style>
<Style Selector="ComboBox:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
</Style>
<Style Selector="ComboBox:disabled /template/ Border#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />

1
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -9,6 +9,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.Button.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Carousel.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CheckBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CaptionButtons.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ComboBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ComboBoxItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ContentControl.xaml?assembly=Avalonia.Themes.Default"/>

53
src/Avalonia.Themes.Default/TitleBar.xaml

@ -0,0 +1,53 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border>
<TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" />
</Border>
</Design.PreviewWith>
<Style Selector="TitleBar">
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate>
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch">
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" />
<Panel x:Name="PART_Container">
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" />
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" />
</Panel>
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TitleBar:fullscreen">
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" />
</Style>
<Style Selector="TitleBar /template/ Border#PART_Background">
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
<Style Selector="TitleBar:fullscreen /template/ Border#PART_Background">
<Setter Property="IsHitTestVisible" Value="True" />
</Style>
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container">
<Setter Property="RenderTransform" Value="translateY(-30px)" />
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" />
</Transitions>
</Setter>
</Style>
<Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container">
<Setter Property="RenderTransform" Value="none" />
</Style>
</Styles>

3
src/Avalonia.Themes.Default/ToggleSwitch.xaml

@ -43,8 +43,7 @@
<Setter Property="Foreground" Value="{DynamicResource ToggleSwitchContentForeground}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<ControlTemplate>

4
src/Avalonia.Themes.Default/Window.xaml

@ -5,7 +5,9 @@
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
<Panel IsHitTestVisible="False" Margin="{TemplateBinding OffScreenMargin}">
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
</Panel>
<Border Background="{TemplateBinding Background}">
<VisualLayerManager>
<ContentPresenter Name="PART_ContentPresenter"

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

@ -12,7 +12,7 @@
<Color x:Key="SystemColorHotlight">#FF0066CC</Color>
<Color x:Key="SystemColorWindow">#FFFFFFFF</Color>
<Color x:Key="SystemColorWindowText">#FF000000</Color>
<FontFamily x:Key="ContentControlThemeFontFamily">Segoe UI</FontFamily>
<FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Themes.Fluent/Assets#Roboto</FontFamily>
<sys:Double x:Key="ControlContentThemeFontSize">14</sys:Double>
<SolidColorBrush x:Key="SystemControlTransparentBrush" Color="Transparent" />

14
src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml

@ -158,8 +158,6 @@
<FontFamily x:Key="MTCMediaFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PhoneFontFamilyNormal">Segoe WP</FontFamily>
<FontFamily x:Key="PhoneFontFamilySemiLight">Segoe WP SemiLight</FontFamily>
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="SettingsFlyoutHeaderThemeFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="SymbolThemeFontFamily">Segoe MDL2 Assets</FontFamily>
<FontFamily x:Key="KeyTipFontFamily">XamlAutoFontFamily</FontFamily>
@ -217,9 +215,6 @@
<x:Double x:Key="MTCVerticalVolumeSliderMinHeight">96</x:Double>
<x:Double x:Key="MTCVerticalVolumeSliderTopGap">8</x:Double>
<x:Double x:Key="MTCVerticalVolumeSliderTopPadding">16</x:Double>
<x:Double x:Key="PivotHeaderItemFontSize">24</x:Double>
<x:Double x:Key="PivotHeaderItemLockedTranslation">40</x:Double>
<x:Double x:Key="PivotTitleFontSize">14</x:Double>
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<x:Double x:Key="RadioButtonBorderThemeThickness">2</x:Double>
@ -262,7 +257,6 @@
<x:Double x:Key="GridViewItemMinWidth">44</x:Double>
<x:Double x:Key="GridViewItemMinHeight">44</x:Double>
<x:Double x:Key="KeyTipContentThemeFontSize">12</x:Double>
<x:Int32 x:Key="PivotHeaderItemCharacterSpacing">-25</x:Int32>
<Thickness x:Key="AppBarBottomBorderThemeThickness">0,0,0,0</Thickness>
<Thickness x:Key="AppBarBottomThemePadding">0,0,0,0</Thickness>
<Thickness x:Key="AppBarTopBorderThemeThickness">0,0,0,0</Thickness>
@ -312,12 +306,6 @@
<Thickness x:Key="PickerFlyoutContentPanelLandscapeThemeMargin">19,19,19,0</Thickness>
<Thickness x:Key="PickerFlyoutContentPanelPortraitThemeMargin">19,37,19,0</Thickness>
<Thickness x:Key="PickerFlyoutTitleThemeMargin">0,0,0,32.5</Thickness>
<Thickness x:Key="PivotHeaderItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotLandscapeThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="PivotNavButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<Thickness x:Key="RepeatButtonBorderThemeThickness">2</Thickness>
<Thickness x:Key="ScrollBarPanningBorderThemeThickness">1</Thickness>
@ -346,8 +334,6 @@
<FontWeight x:Key="ComboBoxHeaderThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="ComboBoxPlaceholderTextThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="DatePickerHeaderThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="PivotHeaderItemThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="PivotTitleThemeFontWeight">Bold</FontWeight>
<FontWeight x:Key="SearchBoxButtonThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="SearchBoxContentThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="TimePickerHeaderThemeFontWeight">Normal</FontWeight>

14
src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml

@ -157,8 +157,6 @@
<FontFamily x:Key="MTCMediaFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PhoneFontFamilyNormal">Segoe WP</FontFamily>
<FontFamily x:Key="PhoneFontFamilySemiLight">Segoe WP SemiLight</FontFamily>
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="SettingsFlyoutHeaderThemeFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="SymbolThemeFontFamily">Segoe MDL2 Assets</FontFamily>
<FontFamily x:Key="KeyTipFontFamily">XamlAutoFontFamily</FontFamily>
@ -216,9 +214,6 @@
<x:Double x:Key="MTCVerticalVolumeSliderMinHeight">96</x:Double>
<x:Double x:Key="MTCVerticalVolumeSliderTopGap">8</x:Double>
<x:Double x:Key="MTCVerticalVolumeSliderTopPadding">16</x:Double>
<x:Double x:Key="PivotHeaderItemFontSize">24</x:Double>
<x:Double x:Key="PivotHeaderItemLockedTranslation">40</x:Double>
<x:Double x:Key="PivotTitleFontSize">14</x:Double>
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<x:Double x:Key="RadioButtonBorderThemeThickness">2</x:Double>
@ -261,7 +256,6 @@
<x:Double x:Key="GridViewItemMinWidth">44</x:Double>
<x:Double x:Key="GridViewItemMinHeight">44</x:Double>
<x:Double x:Key="KeyTipContentThemeFontSize">12</x:Double>
<x:Int32 x:Key="PivotHeaderItemCharacterSpacing">-25</x:Int32>
<Thickness x:Key="AppBarBottomBorderThemeThickness">0,0,0,0</Thickness>
<Thickness x:Key="AppBarBottomThemePadding">0,0,0,0</Thickness>
<Thickness x:Key="AppBarTopBorderThemeThickness">0,0,0,0</Thickness>
@ -311,12 +305,6 @@
<Thickness x:Key="PickerFlyoutContentPanelLandscapeThemeMargin">19,19,19,0</Thickness>
<Thickness x:Key="PickerFlyoutContentPanelPortraitThemeMargin">19,37,19,0</Thickness>
<Thickness x:Key="PickerFlyoutTitleThemeMargin">0,0,0,32.5</Thickness>
<Thickness x:Key="PivotHeaderItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotLandscapeThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="PivotNavButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<Thickness x:Key="RepeatButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="ScrollBarPanningBorderThemeThickness">1</Thickness>
@ -345,8 +333,6 @@
<FontWeight x:Key="ComboBoxHeaderThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="ComboBoxPlaceholderTextThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="DatePickerHeaderThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="PivotHeaderItemThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="PivotTitleThemeFontWeight">Bold</FontWeight>
<FontWeight x:Key="SearchBoxButtonThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="SearchBoxContentThemeFontWeight">Normal</FontWeight>
<FontWeight x:Key="TimePickerHeaderThemeFontWeight">Normal</FontWeight>

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

Loading…
Cancel
Save